Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions api/v1/codebase_types.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package v1

import (
"fmt"
"strconv"
"strings"

corev1 "k8s.io/api/core/v1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand Down Expand Up @@ -129,6 +131,24 @@ type CodebaseSpec struct {
// +optional
// +kubebuilder:default:=true
Private bool `json:"private"`

// CloneRepositoryCredentials contains reference to secret with credentials used to clone repository.
// +nullable
// +optional
CloneRepositoryCredentials *CloneRepositoryCredentials `json:"cloneRepositoryCredentials,omitempty"`
}

type CloneRepositoryCredentials struct {
// SecretRef is a reference to secret that contains credentials for cloning repository.
// The secret must contain "username" and "password" keys.
// +required
SecretRef corev1.LocalObjectReference `json:"secretRef"`

// ClearSecretAfterUse indicates whether the secret should be deleted after use.
// For backward compatibility, the default value is true.
// +optional
// +kubebuilder:default:=true
ClearSecretAfterUse bool `json:"clearSecretAfterUse"`
}

// GetProjectID returns project id from GitUrlPath codebase spec. It removes the leading slash.
Expand All @@ -141,6 +161,16 @@ func (in *CodebaseSpec) IsVersionTypeSemver() bool {
return in.Versioning.Type == VersioningTypeSemver || in.Versioning.Type == VersioningTypeEDP
}

func (in *Codebase) GetCloneRepositoryCredentialSecret() string {
if in.Spec.CloneRepositoryCredentials != nil && in.Spec.CloneRepositoryCredentials.SecretRef.Name != "" {
return in.Spec.CloneRepositoryCredentials.SecretRef.Name
}

// Fallback to generate secret name for backward compatibility.
// Deprecated: Fallback for backward compatibility. Will be removed after new field is fully adopted.
return fmt.Sprintf("repository-codebase-%s-temp", in.Name)
}

type ActionType string

const (
Expand Down
21 changes: 21 additions & 0 deletions api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions config/crd/bases/v2.edp.epam.com_codebases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,36 @@ spec:
default: tekton
description: A name of tool which should be used as CI.
type: string
cloneRepositoryCredentials:
description: CloneRepositoryCredentials contains reference to secret
with credentials used to clone repository.
nullable: true
properties:
clearSecretAfterUse:
default: true
description: |-
ClearSecretAfterUse indicates whether the secret should be deleted after use.
For backward compatibility, the default value is true.
type: boolean
secretRef:
description: |-
SecretRef is a reference to secret that contains credentials for cloning repository.
The secret must contain "username" and "password" keys.
properties:
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
type: object
x-kubernetes-map-type: atomic
required:
- secretRef
type: object
commitMessagePattern:
nullable: true
type: string
Expand Down
34 changes: 7 additions & 27 deletions controllers/codebase/service/chain/checkout_branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,14 @@ import (
"context"
"fmt"

k8sErrors "k8s.io/apimachinery/pkg/api/errors"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1"
codebaseutil "github.com/epam/edp-codebase-operator/v2/pkg/codebase"
gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2"
"github.com/epam/edp-codebase-operator/v2/pkg/util"
)

func GetRepositoryCredentialsIfExists(cb *codebaseApi.Codebase, c client.Client) (userName, password *string, err error) {
if cb.Spec.Repository == nil {
return nil, nil, nil
}

secret := fmt.Sprintf("repository-codebase-%v-temp", cb.Name)

repositoryUsername, repositoryPassword, err := util.GetVcsBasicAuthConfig(c, cb.Namespace, secret)
if err != nil {
return nil, nil, fmt.Errorf("failed to fetch VCS auth config: %w", err)
}

userName = &repositoryUsername
password = &repositoryPassword

return
}

func CheckoutBranch(
ctx context.Context,
branchName string,
Expand Down Expand Up @@ -59,15 +40,14 @@ func CheckoutBranch(
}

case "clone":
user, password, err := GetRepositoryCredentialsIfExists(cb, c)
if err != nil && !k8sErrors.IsNotFound(err) {
return err
user, password, _, err := codebaseutil.GetRepositoryCredentialsIfExists(ctx, cb, c)
if err != nil {
return fmt.Errorf("failed to get repository credentials for checkout (clone strategy): %w", err)
}

cfg := gitproviderv2.Config{}
if user != nil && password != nil {
cfg.Username = *user
cfg.Token = *password
cfg := gitproviderv2.Config{
Username: user,
Token: password,
}

if err := gitProviderFactory(cfg).Checkout(ctx, repoContext.WorkDir, branchName, true); err != nil {
Expand Down
64 changes: 6 additions & 58 deletions controllers/codebase/service/chain/checkout_branch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,63 +24,6 @@ const (
fakeName = "fake-name"
)

func TestGetRepositoryCredentialsIfExists_ShouldPass(t *testing.T) {
c := &codebaseApi.Codebase{
ObjectMeta: metaV1.ObjectMeta{
Name: "fake-name",
Namespace: fakeNamespace,
},
Spec: codebaseApi.CodebaseSpec{
Repository: &codebaseApi.Repository{
Url: "repo",
},
},
}
s := &coreV1.Secret{
ObjectMeta: metaV1.ObjectMeta{
Name: "repository-codebase-fake-name-temp",
Namespace: fakeNamespace,
},
Data: map[string][]byte{
"username": []byte("user"),
"password": []byte("pass"),
},
}
scheme := runtime.NewScheme()
scheme.AddKnownTypes(coreV1.SchemeGroupVersion, s)
scheme.AddKnownTypes(codebaseApi.GroupVersion, c)
fakeCl := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(s, c).Build()
u, p, err := GetRepositoryCredentialsIfExists(c, fakeCl)
assert.Equal(t, u, util.GetStringP("user"))
assert.Equal(t, p, util.GetStringP("pass"))
assert.NoError(t, err)
}

func TestGetRepositoryCredentialsIfExists_ShouldFail(t *testing.T) {
c := &codebaseApi.Codebase{
ObjectMeta: metaV1.ObjectMeta{
Name: "fake-name",
Namespace: fakeNamespace,
},
Spec: codebaseApi.CodebaseSpec{
Repository: &codebaseApi.Repository{
Url: "repo",
},
},
}

scheme := runtime.NewScheme()
scheme.AddKnownTypes(codebaseApi.GroupVersion, c)
fakeCl := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(c).Build()

_, _, err := GetRepositoryCredentialsIfExists(c, fakeCl)
assert.Error(t, err)

if !strings.Contains(err.Error(), "failed to get secret repository-codebase-fake-name-temp") {
t.Fatalf("wrong error returned: %s", err.Error())
}
}

func TestCheckoutBranch_ShouldFailOnGetCurrentBranchName(t *testing.T) {
c := &codebaseApi.Codebase{
ObjectMeta: metaV1.ObjectMeta{
Expand Down Expand Up @@ -236,6 +179,11 @@ func TestCheckoutBranch_ShouldPassForCloneStrategy(t *testing.T) {
Url: "repo",
},
Strategy: codebaseApi.Import,
CloneRepositoryCredentials: &codebaseApi.CloneRepositoryCredentials{
SecretRef: coreV1.LocalObjectReference{
Name: "repository-creds",
},
},
},
}
gs := &codebaseApi.GitServer{
Expand All @@ -252,7 +200,7 @@ func TestCheckoutBranch_ShouldPassForCloneStrategy(t *testing.T) {
}
s := &coreV1.Secret{
ObjectMeta: metaV1.ObjectMeta{
Name: "repository-codebase-fake-name-temp",
Name: "repository-creds",
Namespace: fakeNamespace,
},
Data: map[string][]byte{
Expand Down
32 changes: 20 additions & 12 deletions controllers/codebase/service/chain/cleaner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package chain

import (
"context"
"errors"
"fmt"

v1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -39,39 +40,46 @@ func (h *Cleaner) ServeRequest(ctx context.Context, codebase *codebaseApi.Codeba
}

func (h *Cleaner) clean(ctx context.Context, codebase *codebaseApi.Codebase) error {
log := ctrl.LoggerFrom(ctx)

s := fmt.Sprintf("repository-codebase-%v-temp", codebase.Name)
var errs []error

if err := h.deleteSecret(ctx, s, codebase.Namespace); err != nil {
return fmt.Errorf("failed to delete secret %v: %w", s, err)
if err := h.deleteSecret(ctx, codebase); err != nil {
errs = append(errs, err)
}

wd := util.GetWorkDir(codebase.Name, codebase.Namespace)

log.Info("Deleting work directory", "directory", wd)
ctrl.LoggerFrom(ctx).Info("Deleting work directory", "directory", wd)

if err := deleteWorkDirectory(wd); err != nil {
errs = append(errs, err)
}

return deleteWorkDirectory(wd)
return errors.Join(errs...)
}

func (h *Cleaner) deleteSecret(ctx context.Context, secretName, namespace string) error {
func (h *Cleaner) deleteSecret(ctx context.Context, codebase *codebaseApi.Codebase) error {
if codebase.Spec.CloneRepositoryCredentials != nil && !codebase.Spec.CloneRepositoryCredentials.ClearSecretAfterUse {
return nil
}

secretName := codebase.GetCloneRepositoryCredentialSecret()
log := ctrl.LoggerFrom(ctx).WithValues("secret", secretName)

log.Info("Deleting secret")
log.Info("Deleting secret with repository credentials for clone")

if err := h.client.Delete(ctx, &v1.Secret{
ObjectMeta: metaV1.ObjectMeta{
Name: secretName,
Namespace: namespace,
Namespace: codebase.Namespace,
},
}); err != nil {
if k8sErrors.IsNotFound(err) {
log.Info("Secret is not found. Skip deleting")
log.Info("Secret not found. Skipping deletion")

return nil
}

return fmt.Errorf("failed to Delete 'Secret' resource %q: %w", secretName, err)
return fmt.Errorf("failed to delete secret with repository credentials for clone: %w", err)
}

log.Info("Secret was deleted")
Expand Down
Loading