diff --git a/api/v1/codebase_types.go b/api/v1/codebase_types.go
index 7649139c..72e42556 100644
--- a/api/v1/codebase_types.go
+++ b/api/v1/codebase_types.go
@@ -1,9 +1,11 @@
package v1
import (
+ "fmt"
"strconv"
"strings"
+ corev1 "k8s.io/api/core/v1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -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.
@@ -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 (
diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go
index 64177428..a2803ee0 100644
--- a/api/v1/zz_generated.deepcopy.go
+++ b/api/v1/zz_generated.deepcopy.go
@@ -103,6 +103,22 @@ func (in *CDStageDeployStatus) DeepCopy() *CDStageDeployStatus {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CloneRepositoryCredentials) DeepCopyInto(out *CloneRepositoryCredentials) {
+ *out = *in
+ out.SecretRef = in.SecretRef
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CloneRepositoryCredentials.
+func (in *CloneRepositoryCredentials) DeepCopy() *CloneRepositoryCredentials {
+ if in == nil {
+ return nil
+ }
+ out := new(CloneRepositoryCredentials)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Codebase) DeepCopyInto(out *Codebase) {
*out = *in
@@ -412,6 +428,11 @@ func (in *CodebaseSpec) DeepCopyInto(out *CodebaseSpec) {
*out = new(string)
**out = **in
}
+ if in.CloneRepositoryCredentials != nil {
+ in, out := &in.CloneRepositoryCredentials, &out.CloneRepositoryCredentials
+ *out = new(CloneRepositoryCredentials)
+ **out = **in
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CodebaseSpec.
diff --git a/config/crd/bases/v2.edp.epam.com_codebases.yaml b/config/crd/bases/v2.edp.epam.com_codebases.yaml
index c1fea16a..1ad2996b 100644
--- a/config/crd/bases/v2.edp.epam.com_codebases.yaml
+++ b/config/crd/bases/v2.edp.epam.com_codebases.yaml
@@ -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
diff --git a/controllers/codebase/service/chain/checkout_branch.go b/controllers/codebase/service/chain/checkout_branch.go
index f89c2b0c..03fbd184 100644
--- a/controllers/codebase/service/chain/checkout_branch.go
+++ b/controllers/codebase/service/chain/checkout_branch.go
@@ -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,
@@ -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 {
diff --git a/controllers/codebase/service/chain/checkout_branch_test.go b/controllers/codebase/service/chain/checkout_branch_test.go
index 371be406..6a158f88 100644
--- a/controllers/codebase/service/chain/checkout_branch_test.go
+++ b/controllers/codebase/service/chain/checkout_branch_test.go
@@ -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{
@@ -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{
@@ -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{
diff --git a/controllers/codebase/service/chain/cleaner.go b/controllers/codebase/service/chain/cleaner.go
index b371fca3..4c550dfb 100644
--- a/controllers/codebase/service/chain/cleaner.go
+++ b/controllers/codebase/service/chain/cleaner.go
@@ -2,6 +2,7 @@ package chain
import (
"context"
+ "errors"
"fmt"
v1 "k8s.io/api/core/v1"
@@ -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")
diff --git a/controllers/codebase/service/chain/cleaner_test.go b/controllers/codebase/service/chain/cleaner_test.go
index 11062bb0..aa9bcce7 100644
--- a/controllers/codebase/service/chain/cleaner_test.go
+++ b/controllers/codebase/service/chain/cleaner_test.go
@@ -10,111 +10,269 @@ import (
coreV1 "k8s.io/api/core/v1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1"
+ "github.com/epam/edp-codebase-operator/v2/pkg/util"
)
-func TestCleaner_ShouldPass(t *testing.T) {
- ctx := context.Background()
-
- dir, err := os.MkdirTemp("/tmp", "codebase")
- require.NoError(t, err, "failed to create temp directory for testing")
+func TestCleaner_ServeRequest(t *testing.T) {
+ scheme := runtime.NewScheme()
+ require.NoError(t, coreV1.AddToScheme(scheme))
+ require.NoError(t, codebaseApi.AddToScheme(scheme))
- defer func() {
- err = os.RemoveAll(dir)
- require.NoError(t, err)
- }()
+ secretName := "test-secret"
- t.Setenv("WORKING_DIR", dir)
+ tests := []struct {
+ name string
+ codebase *codebaseApi.Codebase
+ prepare func(t *testing.T, testDir string)
+ wantErr require.ErrorAssertionFunc
+ objects []client.Object
+ verify func(t *testing.T, cl client.Client, testDir string)
+ }{
+ {
+ name: "successfully deletes secret and work directory when ClearSecretAfterUse is true",
+ codebase: &codebaseApi.Codebase{
+ ObjectMeta: metaV1.ObjectMeta{
+ Name: fakeName,
+ Namespace: fakeNamespace,
+ },
+ Spec: codebaseApi.CodebaseSpec{
+ CloneRepositoryCredentials: &codebaseApi.CloneRepositoryCredentials{
+ SecretRef: coreV1.LocalObjectReference{
+ Name: secretName,
+ },
+ ClearSecretAfterUse: true,
+ },
+ },
+ },
+ prepare: prepareWorkDir,
+ wantErr: require.NoError,
+ objects: []client.Object{
+ &coreV1.Secret{
+ ObjectMeta: metaV1.ObjectMeta{
+ Name: secretName,
+ Namespace: fakeNamespace,
+ },
+ },
+ },
+ verify: func(t *testing.T, cl client.Client, testDir string) {
+ // Secret should be deleted
+ secret := &coreV1.Secret{}
+ err := cl.Get(context.Background(), client.ObjectKey{Name: secretName, Namespace: fakeNamespace}, secret)
+ assert.Error(t, err, "secret should be deleted")
- c := &codebaseApi.Codebase{
- ObjectMeta: metaV1.ObjectMeta{
- Name: fakeName,
- Namespace: fakeNamespace,
+ verifyWorkDirDeleted(t)
+ },
},
- }
- ssh := &coreV1.Secret{
- ObjectMeta: metaV1.ObjectMeta{
- Name: "repository-codebase-fake-name-temp",
- Namespace: fakeNamespace,
+ {
+ name: "successfully completes when secret not found but ClearSecretAfterUse is true",
+ codebase: &codebaseApi.Codebase{
+ ObjectMeta: metaV1.ObjectMeta{
+ Name: fakeName,
+ Namespace: fakeNamespace,
+ },
+ Spec: codebaseApi.CodebaseSpec{
+ CloneRepositoryCredentials: &codebaseApi.CloneRepositoryCredentials{
+ SecretRef: coreV1.LocalObjectReference{
+ Name: secretName,
+ },
+ ClearSecretAfterUse: true,
+ },
+ },
+ },
+ prepare: prepareWorkDir,
+ wantErr: require.NoError,
+ objects: []client.Object{},
+ verify: func(t *testing.T, cl client.Client, testDir string) {
+ verifyWorkDirDeleted(t)
+ },
},
- }
-
- scheme := runtime.NewScheme()
- scheme.AddKnownTypes(coreV1.SchemeGroupVersion, ssh)
- scheme.AddKnownTypes(codebaseApi.GroupVersion, c)
-
- fakeCl := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(c, ssh).Build()
+ {
+ name: "deletes fallback secret when CloneRepositoryCredentials is nil",
+ codebase: &codebaseApi.Codebase{
+ ObjectMeta: metaV1.ObjectMeta{
+ Name: fakeName,
+ Namespace: fakeNamespace,
+ },
+ Spec: codebaseApi.CodebaseSpec{
+ CloneRepositoryCredentials: nil,
+ },
+ },
+ prepare: prepareWorkDir,
+ wantErr: require.NoError,
+ objects: []client.Object{
+ &coreV1.Secret{
+ ObjectMeta: metaV1.ObjectMeta{
+ // Fallback secret should be deleted even when CloneRepositoryCredentials is nil
+ Name: "repository-codebase-fake-name-temp",
+ Namespace: fakeNamespace,
+ },
+ },
+ },
+ verify: func(t *testing.T, cl client.Client, testDir string) {
+ // Fallback secret should be deleted
+ secret := &coreV1.Secret{}
+ err := cl.Get(context.Background(), client.ObjectKey{
+ Name: "repository-codebase-fake-name-temp",
+ Namespace: fakeNamespace,
+ }, secret)
+ assert.Error(t, err, "fallback secret should be deleted")
- cl := NewCleaner(fakeCl)
-
- err = cl.ServeRequest(ctx, c)
- assert.NoError(t, err)
-}
-
-func TestCleaner_ShouldNotFailedIfSecretNotFound(t *testing.T) {
- ctx := context.Background()
-
- dir, err := os.MkdirTemp("/tmp", "codebase")
- require.NoError(t, err, "failed to create temp directory for testing")
-
- defer func() {
- err = os.RemoveAll(dir)
- require.NoError(t, err)
- }()
+ verifyWorkDirDeleted(t)
+ },
+ },
+ {
+ name: "skips secret deletion when ClearSecretAfterUse is false",
+ codebase: &codebaseApi.Codebase{
+ ObjectMeta: metaV1.ObjectMeta{
+ Name: fakeName,
+ Namespace: fakeNamespace,
+ },
+ Spec: codebaseApi.CodebaseSpec{
+ CloneRepositoryCredentials: &codebaseApi.CloneRepositoryCredentials{
+ SecretRef: coreV1.LocalObjectReference{
+ Name: secretName,
+ },
+ ClearSecretAfterUse: false,
+ },
+ },
+ },
+ prepare: prepareWorkDir,
+ wantErr: require.NoError,
+ objects: []client.Object{
+ &coreV1.Secret{
+ ObjectMeta: metaV1.ObjectMeta{
+ Name: secretName,
+ Namespace: fakeNamespace,
+ },
+ },
+ },
+ verify: func(t *testing.T, cl client.Client, testDir string) {
+ // Secret should still exist
+ secret := &coreV1.Secret{}
+ err := cl.Get(context.Background(), client.ObjectKey{Name: secretName, Namespace: fakeNamespace}, secret)
+ assert.NoError(t, err, "secret should still exist")
- t.Setenv("WORKING_DIR", dir)
+ verifyWorkDirDeleted(t)
+ },
+ },
+ {
+ name: "uses GetCloneRepositoryCredentialSecret to get secret name",
+ codebase: &codebaseApi.Codebase{
+ ObjectMeta: metaV1.ObjectMeta{
+ Name: fakeName,
+ Namespace: fakeNamespace,
+ },
+ Spec: codebaseApi.CodebaseSpec{
+ CloneRepositoryCredentials: &codebaseApi.CloneRepositoryCredentials{
+ SecretRef: coreV1.LocalObjectReference{
+ Name: "", // Empty name should trigger fallback
+ },
+ ClearSecretAfterUse: true,
+ },
+ },
+ },
+ prepare: prepareWorkDir,
+ wantErr: require.NoError,
+ objects: []client.Object{
+ &coreV1.Secret{
+ ObjectMeta: metaV1.ObjectMeta{
+ // Fallback secret name generated by GetCloneRepositoryCredentialSecret
+ Name: "repository-codebase-fake-name-temp",
+ Namespace: fakeNamespace,
+ },
+ },
+ },
+ verify: func(t *testing.T, cl client.Client, testDir string) {
+ // Fallback secret should be deleted
+ secret := &coreV1.Secret{}
+ err := cl.Get(context.Background(), client.ObjectKey{
+ Name: "repository-codebase-fake-name-temp",
+ Namespace: fakeNamespace,
+ }, secret)
+ assert.Error(t, err, "fallback secret should be deleted")
- c := &codebaseApi.Codebase{
- ObjectMeta: metaV1.ObjectMeta{
- Name: fakeName,
- Namespace: fakeNamespace,
+ verifyWorkDirDeleted(t)
+ },
+ },
+ {
+ name: "successfully completes when CloneRepositoryCredentials is nil and fallback secret not found",
+ codebase: &codebaseApi.Codebase{
+ ObjectMeta: metaV1.ObjectMeta{
+ Name: fakeName,
+ Namespace: fakeNamespace,
+ },
+ Spec: codebaseApi.CodebaseSpec{
+ CloneRepositoryCredentials: nil,
+ },
+ },
+ prepare: prepareWorkDir,
+ wantErr: require.NoError,
+ objects: []client.Object{},
+ verify: func(t *testing.T, cl client.Client, testDir string) {
+ verifyWorkDirDeleted(t)
+ },
+ },
+ {
+ name: "deletes work directory even when work directory doesn't exist",
+ codebase: &codebaseApi.Codebase{
+ ObjectMeta: metaV1.ObjectMeta{
+ Name: fakeName,
+ Namespace: fakeNamespace,
+ },
+ Spec: codebaseApi.CodebaseSpec{
+ CloneRepositoryCredentials: nil,
+ },
+ },
+ prepare: func(t *testing.T, testDir string) {
+ t.Helper()
+ t.Setenv(util.WorkDirEnv, testDir)
+ // Don't create work directory - it doesn't exist
+ },
+ wantErr: require.NoError,
+ objects: []client.Object{},
+ verify: func(t *testing.T, cl client.Client, testDir string) {
+ verifyWorkDirDeleted(t)
+ },
},
}
- ssh := &coreV1.Secret{}
-
- scheme := runtime.NewScheme()
- scheme.AddKnownTypes(coreV1.SchemeGroupVersion, ssh)
- scheme.AddKnownTypes(codebaseApi.GroupVersion, c)
-
- fakeCl := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(c, ssh).Build()
-
- cl := NewCleaner(fakeCl)
-
- err = cl.ServeRequest(ctx, c)
- assert.NoError(t, err)
-}
-func TestCleaner_ShouldFail(t *testing.T) {
- ctx := context.Background()
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ testDir := t.TempDir()
+ tt.prepare(t, testDir)
- dir, err := os.MkdirTemp("/tmp", "codebase")
- require.NoError(t, err, "failed to create temp directory for testing")
+ fakeCl := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.objects...).Build()
+ cl := NewCleaner(fakeCl)
- defer func() {
- err = os.RemoveAll(dir)
- require.NoError(t, err)
- }()
+ err := cl.ServeRequest(context.Background(), tt.codebase)
+ tt.wantErr(t, err)
- t.Setenv("WORKING_DIR", dir)
-
- c := &codebaseApi.Codebase{
- ObjectMeta: metaV1.ObjectMeta{
- Name: fakeName,
- Namespace: fakeNamespace,
- },
+ if tt.verify != nil {
+ tt.verify(t, fakeCl, testDir)
+ }
+ })
}
+}
- scheme := runtime.NewScheme()
- require.NoError(t, codebaseApi.AddToScheme(scheme))
-
- fakeCl := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(c).Build()
+func prepareWorkDir(t *testing.T, testDir string) {
+ t.Helper()
+ t.Setenv(util.WorkDirEnv, testDir)
- cl := NewCleaner(fakeCl)
+ // Create work directory structure
+ workDir := util.GetWorkDir(fakeName, fakeNamespace)
+ require.NoError(t, os.MkdirAll(workDir, 0755))
+}
- err = cl.ServeRequest(ctx, c)
+func verifyWorkDirDeleted(t *testing.T) {
+ t.Helper()
- require.Error(t, err)
- assert.Contains(t, err.Error(), "failed to delete secret repository-codebase-fake-name-temp")
+ // Work directory should be deleted
+ // Note: util.GetWorkDir() uses the WORKING_DIR env var set by prepareWorkDir
+ workDir := util.GetWorkDir(fakeName, fakeNamespace)
+ _, err := os.Stat(workDir)
+ require.ErrorIs(t, err, os.ErrNotExist, "work directory should be deleted")
}
diff --git a/controllers/codebase/service/chain/put_project.go b/controllers/codebase/service/chain/put_project.go
index 5846da8d..7eeb027a 100644
--- a/controllers/codebase/service/chain/put_project.go
+++ b/controllers/codebase/service/chain/put_project.go
@@ -8,12 +8,12 @@ import (
"slices"
"strconv"
- k8sErrors "k8s.io/apimachinery/pkg/api/errors"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
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"
"github.com/epam/edp-codebase-operator/v2/pkg/gerrit"
gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2"
"github.com/epam/edp-codebase-operator/v2/pkg/gitprovider"
@@ -384,7 +384,7 @@ func (h *PutProject) setDefaultBranch(
func (h *PutProject) tryToCloneRepo(
ctx context.Context,
repoUrl string,
- repositoryUsername, repositoryPassword *string,
+ repositoryUsername, repositoryPassword string,
repoContext *GitRepositoryContext,
) error {
log := ctrl.LoggerFrom(ctx).WithValues("dest", repoContext.WorkDir, "repoUrl", repoUrl)
@@ -397,15 +397,10 @@ func (h *PutProject) tryToCloneRepo(
return nil
}
- config := gitproviderv2.Config{}
-
- if repositoryUsername != nil && repositoryPassword != nil {
- config = gitproviderv2.Config{}
- config.Username = *repositoryUsername
- config.Token = *repositoryPassword
- }
-
- gitProvider := h.gitProviderFactory(config)
+ gitProvider := h.gitProviderFactory(gitproviderv2.Config{
+ Username: repositoryUsername,
+ Token: repositoryPassword,
+ })
if err := gitProvider.Clone(ctx, repoUrl, repoContext.WorkDir); err != nil {
return fmt.Errorf("failed to clone repository: %w", err)
}
@@ -482,22 +477,19 @@ func (h *PutProject) notEmptyProjectProvisioning(ctx context.Context, codebase *
return fmt.Errorf("failed to build repo url: %w", err)
}
- repu, repp, err := GetRepositoryCredentialsIfExists(codebase, h.k8sClient)
- // we are ok if no credentials is found, assuming this is a public repo
- if err != nil && !k8sErrors.IsNotFound(err) {
- return fmt.Errorf("failed to get repository credentials: %w", err)
+ repu, repp, exists, err := codebaseutil.GetRepositoryCredentialsIfExists(ctx, codebase, h.k8sClient)
+ if err != nil {
+ return fmt.Errorf("failed to get repository credentials for project provisioning: %w", err)
}
- // Check permissions if credentials exist
- if repu != nil && repp != nil {
- tempConfig := gitproviderv2.Config{
- Username: *repu,
- Token: *repp,
- }
- tempProvider := h.gitProviderFactory(tempConfig)
+ if exists {
+ cloneGitProvider := h.gitProviderFactory(gitproviderv2.Config{
+ Username: repu,
+ Token: repp,
+ })
- if err := tempProvider.CheckPermissions(ctx, repoUrl); err != nil {
- return fmt.Errorf("failed to get access to the repository %v for user %v: %w", repoUrl, *repu, err)
+ if err := cloneGitProvider.CheckPermissions(ctx, repoUrl); err != nil {
+ return fmt.Errorf("failed to get access to the repository %s for user %s: %w", repoUrl, repu, err)
}
}
diff --git a/deploy-templates/crds/v2.edp.epam.com_codebases.yaml b/deploy-templates/crds/v2.edp.epam.com_codebases.yaml
index c1fea16a..1ad2996b 100644
--- a/deploy-templates/crds/v2.edp.epam.com_codebases.yaml
+++ b/deploy-templates/crds/v2.edp.epam.com_codebases.yaml
@@ -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
diff --git a/docs/api.md b/docs/api.md
index 5b0f3bdb..ed508a66 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -803,6 +803,13 @@ Selected branch will become a default branch for a new codebase (e.g. master, ma
Default: tekton
| Name | +Type | +Description | +Required | +
|---|---|---|---|
| secretRef | +object | +
+ SecretRef is a reference to secret that contains credentials for cloning repository.
+The secret must contain "username" and "password" keys. + |
+ true | +
| clearSecretAfterUse | +boolean | +
+ ClearSecretAfterUse indicates whether the secret should be deleted after use.
+For backward compatibility, the default value is true. + + Default: true + |
+ false | +
| Name | +Type | +Description | +Required | +
|---|---|---|---|
| name | +string | +
+ 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 + + Default: + |
+ false | +