diff --git a/controllers/codebase/service/chain/checkout_branch.go b/controllers/codebase/service/chain/checkout_branch.go index d58e6202..f89c2b0c 100644 --- a/controllers/codebase/service/chain/checkout_branch.go +++ b/controllers/codebase/service/chain/checkout_branch.go @@ -33,25 +33,28 @@ func GetRepositoryCredentialsIfExists(cb *codebaseApi.Codebase, c client.Client) func CheckoutBranch( ctx context.Context, - repository, projectPath, branchName string, - g gitproviderv2.Git, + branchName string, + repoContext *GitRepositoryContext, cb *codebaseApi.Codebase, c client.Client, - createGitProviderWithConfig func(config gitproviderv2.Config) gitproviderv2.Git, + gitProviderFactory func(config gitproviderv2.Config) gitproviderv2.Git, ) error { - currentBranchName, err := g.GetCurrentBranchName(ctx, projectPath) + log := ctrl.LoggerFrom(ctx) + gitProvider := gitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(repoContext.GitServer, repoContext.GitServerSecret)) + + currentBranchName, err := gitProvider.GetCurrentBranchName(ctx, repoContext.WorkDir) if err != nil { return fmt.Errorf("failed to get current branch name: %w", err) } if currentBranchName == branchName { - ctrl.Log.Info("default branch is already active", "name", branchName) + log.Info("Default branch is already active", "name", branchName) return nil } switch cb.Spec.Strategy { case "create": - if err := g.Checkout(ctx, projectPath, branchName, false); err != nil { + if err := gitProvider.Checkout(ctx, repoContext.WorkDir, branchName, false); err != nil { return fmt.Errorf("failed to checkout to default branch %s (create strategy): %w", branchName, err) } @@ -61,20 +64,17 @@ func CheckoutBranch( return err } - cloneRepoGitProvider := g - + cfg := gitproviderv2.Config{} if user != nil && password != nil { - cloneRepoGitProvider = createGitProviderWithConfig(gitproviderv2.Config{ - Username: *user, - Token: *password, - }) + cfg.Username = *user + cfg.Token = *password } - if err := cloneRepoGitProvider.Checkout(ctx, projectPath, branchName, true); err != nil { + if err := gitProviderFactory(cfg).Checkout(ctx, repoContext.WorkDir, branchName, true); err != nil { return fmt.Errorf("failed to checkout to default branch %s (clone strategy): %w", branchName, err) } case "import": - if err := g.CheckoutRemoteBranch(ctx, projectPath, branchName); err != nil { + if err := gitProvider.CheckoutRemoteBranch(ctx, repoContext.WorkDir, branchName); err != nil { return fmt.Errorf("failed to checkout to default branch %s (import strategy): %w", branchName, err) } default: diff --git a/controllers/codebase/service/chain/checkout_branch_test.go b/controllers/codebase/service/chain/checkout_branch_test.go index e11ce894..371be406 100644 --- a/controllers/codebase/service/chain/checkout_branch_test.go +++ b/controllers/codebase/service/chain/checkout_branch_test.go @@ -81,48 +81,30 @@ func TestGetRepositoryCredentialsIfExists_ShouldFail(t *testing.T) { } } -func TestCheckoutBranch_ShouldFailOnGetSecret(t *testing.T) { +func TestCheckoutBranch_ShouldFailOnGetCurrentBranchName(t *testing.T) { c := &codebaseApi.Codebase{ ObjectMeta: metaV1.ObjectMeta{ Name: "fake-name", Namespace: fakeNamespace, }, Spec: codebaseApi.CodebaseSpec{ + GitServer: "git", Repository: &codebaseApi.Repository{ Url: "repo", }, Strategy: codebaseApi.Clone, }, } - - scheme := runtime.NewScheme() - scheme.AddKnownTypes(codebaseApi.GroupVersion, c) - fakeCl := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(c).Build() - - mGit := gitServerMocks.NewMockGit(t) - mGit.On("GetCurrentBranchName", testify.Anything, "project-path").Return("some-other-branch", nil) - - err := CheckoutBranch(context.Background(), "repo", "project-path", "branch", mGit, c, fakeCl, func(config gitproviderv2.Config) gitproviderv2.Git { - return mGit - }) - 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{ + gs := &codebaseApi.GitServer{ ObjectMeta: metaV1.ObjectMeta{ - Name: "fake-name", + Name: "git", Namespace: fakeNamespace, }, - Spec: codebaseApi.CodebaseSpec{ - Repository: &codebaseApi.Repository{ - Url: "repo", - }, - Strategy: codebaseApi.Clone, + Spec: codebaseApi.GitServerSpec{ + NameSshKeySecret: fakeName, + GitHost: fakeName, + SshPort: 22, + GitUser: fakeName, }, } s := &coreV1.Secret{ @@ -135,15 +117,32 @@ func TestCheckoutBranch_ShouldFailOnGetCurrentBranchName(t *testing.T) { "password": []byte("pass"), }, } + ssh := &coreV1.Secret{ + ObjectMeta: metaV1.ObjectMeta{ + Name: fakeName, + Namespace: fakeNamespace, + }, + Data: map[string][]byte{ + util.PrivateSShKeyName: []byte("fake-ssh-key"), + }, + } scheme := runtime.NewScheme() - scheme.AddKnownTypes(coreV1.SchemeGroupVersion, s) - scheme.AddKnownTypes(codebaseApi.GroupVersion, c) - fakeCl := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(s, c).Build() + scheme.AddKnownTypes(coreV1.SchemeGroupVersion, s, ssh) + scheme.AddKnownTypes(codebaseApi.GroupVersion, c, gs) + fakeCl := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(s, c, gs, ssh).Build() mGit := gitServerMocks.NewMockGit(t) mGit.On("GetCurrentBranchName", testify.Anything, "project-path").Return("", errors.New("FATAL:FAILED")) - err := CheckoutBranch(context.Background(), "repo", "project-path", "branch", mGit, c, fakeCl, func(config gitproviderv2.Config) gitproviderv2.Git { + err := CheckoutBranch(context.Background(), "branch", &GitRepositoryContext{ + GitServer: gs, + GitServerSecret: ssh, + PrivateSSHKey: "fake-ssh-key", + UserName: "user", + Token: "pass", + RepoGitUrl: "repo", + WorkDir: "project-path", + }, c, fakeCl, func(config gitproviderv2.Config) gitproviderv2.Git { return mGit }) assert.Error(t, err) @@ -154,19 +153,31 @@ func TestCheckoutBranch_ShouldFailOnGetCurrentBranchName(t *testing.T) { } func TestCheckoutBranch_ShouldFailOnCheckout(t *testing.T) { - repo := "repo" c := &codebaseApi.Codebase{ ObjectMeta: metaV1.ObjectMeta{ Name: "fake-name", Namespace: fakeNamespace, }, Spec: codebaseApi.CodebaseSpec{ + GitServer: "git", Repository: &codebaseApi.Repository{ Url: "repo", }, Strategy: codebaseApi.Clone, }, } + gs := &codebaseApi.GitServer{ + ObjectMeta: metaV1.ObjectMeta{ + Name: "git", + Namespace: fakeNamespace, + }, + Spec: codebaseApi.GitServerSpec{ + NameSshKeySecret: fakeName, + GitHost: fakeName, + SshPort: 22, + GitUser: fakeName, + }, + } s := &coreV1.Secret{ ObjectMeta: metaV1.ObjectMeta{ Name: "repository-codebase-fake-name-temp", @@ -177,16 +188,33 @@ func TestCheckoutBranch_ShouldFailOnCheckout(t *testing.T) { "password": []byte("pass1"), }, } + ssh := &coreV1.Secret{ + ObjectMeta: metaV1.ObjectMeta{ + Name: fakeName, + Namespace: fakeNamespace, + }, + Data: map[string][]byte{ + util.PrivateSShKeyName: []byte("fake-ssh-key"), + }, + } scheme := runtime.NewScheme() - scheme.AddKnownTypes(coreV1.SchemeGroupVersion, s) - scheme.AddKnownTypes(codebaseApi.GroupVersion, c) - fakeCl := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(s, c).Build() + scheme.AddKnownTypes(coreV1.SchemeGroupVersion, s, ssh) + scheme.AddKnownTypes(codebaseApi.GroupVersion, c, gs) + fakeCl := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(s, c, gs, ssh).Build() mGit := gitServerMocks.NewMockGit(t) mGit.On("GetCurrentBranchName", testify.Anything, "project-path").Return("some-other-branch", nil) mGit.On("Checkout", testify.Anything, "project-path", "branch", true).Return(errors.New("FATAL:FAILED")) - err := CheckoutBranch(context.Background(), repo, "project-path", "branch", mGit, c, fakeCl, func(config gitproviderv2.Config) gitproviderv2.Git { + err := CheckoutBranch(context.Background(), "branch", &GitRepositoryContext{ + GitServer: gs, + GitServerSecret: ssh, + PrivateSSHKey: "fake-ssh-key", + UserName: "user1", + Token: "pass1", + RepoGitUrl: "repo", + WorkDir: "project-path", + }, c, fakeCl, func(config gitproviderv2.Config) gitproviderv2.Git { return mGit }) assert.Error(t, err) @@ -197,7 +225,6 @@ func TestCheckoutBranch_ShouldFailOnCheckout(t *testing.T) { } func TestCheckoutBranch_ShouldPassForCloneStrategy(t *testing.T) { - repo := "repo" c := &codebaseApi.Codebase{ ObjectMeta: metaV1.ObjectMeta{ Name: "fake-name", @@ -251,7 +278,15 @@ func TestCheckoutBranch_ShouldPassForCloneStrategy(t *testing.T) { mGit.On("GetCurrentBranchName", testify.Anything, "project-path").Return("some-other-branch", nil) mGit.On("CheckoutRemoteBranch", testify.Anything, "project-path", "branch").Return(nil) - err := CheckoutBranch(context.Background(), repo, "project-path", "branch", mGit, c, fakeCl, func(config gitproviderv2.Config) gitproviderv2.Git { + err := CheckoutBranch(context.Background(), "branch", &GitRepositoryContext{ + GitServer: gs, + GitServerSecret: ssh, + PrivateSSHKey: "fake", + UserName: "user", + Token: "pass", + RepoGitUrl: "repo", + WorkDir: "project-path", + }, c, fakeCl, func(config gitproviderv2.Config) gitproviderv2.Git { return mGit }) assert.NoError(t, err) diff --git a/controllers/codebase/service/chain/common.go b/controllers/codebase/service/chain/common.go index 167f3bf5..eb221fde 100644 --- a/controllers/codebase/service/chain/common.go +++ b/controllers/codebase/service/chain/common.go @@ -105,47 +105,35 @@ func PrepareGitRepository( c client.Client, codebase *codebaseApi.Codebase, gitProviderFactory gitproviderv2.GitProviderFactory, - createGitProviderWithConfig func(config gitproviderv2.Config) gitproviderv2.Git, ) (*GitRepositoryContext, error) { log := ctrl.LoggerFrom(ctx) - // Step 1-2: Get git repository context (GitServer, Secret, and paths) gitRepoCtx, err := GetGitRepositoryContext(ctx, c, codebase) if err != nil { return nil, err } - // Step 3: Create git provider using factory - g := gitProviderFactory(gitRepoCtx.GitServer, gitRepoCtx.GitServerSecret) + gitProvider := gitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(gitRepoCtx.GitServer, gitRepoCtx.GitServerSecret)) - // Step 4: Clone repository if needed if !util.DoesDirectoryExist(gitRepoCtx.WorkDir) || util.IsDirectoryEmpty(gitRepoCtx.WorkDir) { log.Info("Start cloning repository", "url", gitRepoCtx.RepoGitUrl) - if err := g.Clone(ctx, gitRepoCtx.RepoGitUrl, gitRepoCtx.WorkDir, 0); err != nil { + if err := gitProvider.Clone(ctx, gitRepoCtx.RepoGitUrl, gitRepoCtx.WorkDir); err != nil { return nil, fmt.Errorf("failed to clone git repository: %w", err) } log.Info("Repository has been cloned", "url", gitRepoCtx.RepoGitUrl) } - // Step 5: Get repo URL for checkout - repoUrl, err := util.GetRepoUrl(codebase) - if err != nil { - return nil, fmt.Errorf("failed to build repo url: %w", err) - } - - // Step 6: Checkout default branch - log.Info("Start checkout default branch", "branch", codebase.Spec.DefaultBranch, "repo", repoUrl) + log.Info("Start checkout default branch", "branch", codebase.Spec.DefaultBranch) - err = CheckoutBranch(ctx, repoUrl, gitRepoCtx.WorkDir, codebase.Spec.DefaultBranch, g, codebase, c, createGitProviderWithConfig) + err = CheckoutBranch(ctx, codebase.Spec.DefaultBranch, gitRepoCtx, codebase, c, gitProviderFactory) if err != nil { return nil, fmt.Errorf("failed to checkout default branch %v: %w", codebase.Spec.DefaultBranch, err) } - log.Info("Default branch has been checked out", "branch", codebase.Spec.DefaultBranch, "repo", repoUrl) + log.Info("Default branch has been checked out", "branch", codebase.Spec.DefaultBranch) - // Return context for subsequent operations return gitRepoCtx, nil } diff --git a/controllers/codebase/service/chain/common_test.go b/controllers/codebase/service/chain/common_test.go index 359e6484..cc9a54f7 100644 --- a/controllers/codebase/service/chain/common_test.go +++ b/controllers/codebase/service/chain/common_test.go @@ -310,9 +310,7 @@ func TestPrepareGitRepository(t *testing.T) { objects: []client.Object{gitServer, secret}, gitClient: func(t *testing.T) *gitMocks.MockGit { m := gitMocks.NewMockGit(t) - m.On("Clone", - testify.Anything, testify.Anything, testify.Anything, testify.Anything, - ).Return(nil) + m.On("Clone", testify.Anything, testify.Anything, testify.Anything).Return(nil) m.On("GetCurrentBranchName", testify.Anything, testify.Anything).Return("main", nil) return m }, @@ -388,7 +386,7 @@ func TestPrepareGitRepository(t *testing.T) { objects: []client.Object{gitServer, secret}, gitClient: func(t *testing.T) *gitMocks.MockGit { m := gitMocks.NewMockGit(t) - m.On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). + m.On("Clone", testify.Anything, testify.Anything, testify.Anything). Return(assert.AnError) return m }, @@ -431,8 +429,7 @@ func TestPrepareGitRepository(t *testing.T) { }, gitClient: func(t *testing.T) *gitMocks.MockGit { m := gitMocks.NewMockGit(t) - m.On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil) + m.On("Clone", testify.Anything, testify.Anything, testify.Anything).Return(nil) m.On("GetCurrentBranchName", testify.Anything, testify.Anything). Return("", errors.New("failed to get current branch")) return m @@ -463,15 +460,14 @@ func TestPrepareGitRepository(t *testing.T) { WithObjects(allObjects...). Build() + gitProvider := tt.gitClient(t) + gitCtx, err := PrepareGitRepository( context.Background(), k8sClient, tt.codebase, - func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { - return tt.gitClient(t) - }, - func(config gitproviderv2.Config) gitproviderv2.Git { - return tt.gitClient(t) + func(cfg gitproviderv2.Config) gitproviderv2.Git { + return gitProvider }, ) diff --git a/controllers/codebase/service/chain/factory.go b/controllers/codebase/service/chain/factory.go index 37cca04a..06e5c8b0 100644 --- a/controllers/codebase/service/chain/factory.go +++ b/controllers/codebase/service/chain/factory.go @@ -22,22 +22,17 @@ func MakeChain(ctx context.Context, c client.Client) handler.CodebaseHandler { ch := &chain{} gitlabCIManager := gitlabci.NewManager(c) - createGitProviderWithConfig := func(config gitproviderv2.Config) gitproviderv2.Git { - return gitproviderv2.NewGitProvider(config) - } - ch.Use( NewPutGitWebRepoUrl(c), NewPutProject( c, &gerrit.SSHGerritClient{}, gitprovider.NewGitProjectProvider, - gitproviderv2.DefaultGitProviderFactory, - createGitProviderWithConfig, + gitproviderv2.NewGitProviderFactory, ), NewPutWebHook(c, resty.New()), - NewPutGitLabCIConfig(c, gitlabCIManager, gitproviderv2.DefaultGitProviderFactory, createGitProviderWithConfig), - NewPutDeployConfigs(c, gitproviderv2.DefaultGitProviderFactory, createGitProviderWithConfig), + NewPutGitLabCIConfig(c, gitlabCIManager, gitproviderv2.NewGitProviderFactory), + NewPutDeployConfigs(c, gitproviderv2.NewGitProviderFactory), NewPutDefaultCodeBaseBranch(c), NewCleaner(c), ) diff --git a/controllers/codebase/service/chain/put_deploy_configs.go b/controllers/codebase/service/chain/put_deploy_configs.go index 2a3f277a..dc5d3360 100644 --- a/controllers/codebase/service/chain/put_deploy_configs.go +++ b/controllers/codebase/service/chain/put_deploy_configs.go @@ -14,13 +14,15 @@ import ( ) type PutDeployConfigs struct { - client client.Client - gitProviderFactory gitproviderv2.GitProviderFactory - createGitProviderWithConfig func(config gitproviderv2.Config) gitproviderv2.Git + client client.Client + gitProviderFactory gitproviderv2.GitProviderFactory } -func NewPutDeployConfigs(c client.Client, gitProviderFactory gitproviderv2.GitProviderFactory, createGitProviderWithConfig func(config gitproviderv2.Config) gitproviderv2.Git) *PutDeployConfigs { - return &PutDeployConfigs{client: c, gitProviderFactory: gitProviderFactory, createGitProviderWithConfig: createGitProviderWithConfig} +func NewPutDeployConfigs( + c client.Client, + gitProviderFactory gitproviderv2.GitProviderFactory, +) *PutDeployConfigs { + return &PutDeployConfigs{client: c, gitProviderFactory: gitProviderFactory} } func (h *PutDeployConfigs) ServeRequest(ctx context.Context, c *codebaseApi.Codebase) error { @@ -58,14 +60,14 @@ func (h *PutDeployConfigs) tryToPushConfigs(ctx context.Context, codebase *codeb } // Prepare git repository (get server, clone, checkout) - gitCtx, err := PrepareGitRepository(ctx, h.client, codebase, h.gitProviderFactory, h.createGitProviderWithConfig) + gitCtx, err := PrepareGitRepository(ctx, h.client, codebase, h.gitProviderFactory) if err != nil { setFailedFields(codebase, codebaseApi.SetupDeploymentTemplates, err.Error()) return fmt.Errorf("failed to prepare git repository: %w", err) } // Create git provider using factory - g := h.gitProviderFactory(gitCtx.GitServer, gitCtx.GitServerSecret) + g := h.gitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(gitCtx.GitServer, gitCtx.GitServerSecret)) // Add Gerrit-specific commit hooks if needed if gitCtx.GitServer.Spec.GitProvider == codebaseApi.GitProviderGerrit { diff --git a/controllers/codebase/service/chain/put_deploy_configs_test.go b/controllers/codebase/service/chain/put_deploy_configs_test.go index 7379cf8e..8afebf9e 100644 --- a/controllers/codebase/service/chain/put_deploy_configs_test.go +++ b/controllers/codebase/service/chain/put_deploy_configs_test.go @@ -106,13 +106,14 @@ func TestPutDeployConfigs_ShouldPass(t *testing.T) { mGit.On("Checkout", testify.Anything, wd, "fake-defaultBranch", false).Return(nil) mGit.On("Commit", testify.Anything, wd, fmt.Sprintf("Add deployment templates for %v", c.Name)).Return(nil) mGit.On("Push", testify.Anything, wd, gitproviderv2.RefSpecPushAllBranches).Return(nil) - mGit.On("Clone", testify.Anything, testify.Anything, wd, testify.Anything).Return(nil) + mGit.On("Clone", testify.Anything, testify.Anything, wd).Return(nil) - pdc := NewPutDeployConfigs(fakeCl, func(gitServer *codebaseApi.GitServer, secret *coreV1.Secret) gitproviderv2.Git { - return mGit - }, func(config gitproviderv2.Config) gitproviderv2.Git { - return mGit - }) + pdc := NewPutDeployConfigs( + fakeCl, + func(cfg gitproviderv2.Config) gitproviderv2.Git { + return mGit + }, + ) err := pdc.ServeRequest(context.Background(), c) assert.NoError(t, err) @@ -140,11 +141,12 @@ func TestPutDeployConfigs_ShouldPassWithNonApplication(t *testing.T) { mGit := gitServerMocks.NewMockGit(t) - pdc := NewPutDeployConfigs(fakeCl, func(gitServer *codebaseApi.GitServer, secret *coreV1.Secret) gitproviderv2.Git { - return mGit - }, func(config gitproviderv2.Config) gitproviderv2.Git { - return mGit - }) + pdc := NewPutDeployConfigs( + fakeCl, + func(config gitproviderv2.Config) gitproviderv2.Git { + return mGit + }, + ) err := pdc.ServeRequest(context.Background(), c) assert.NoError(t, err) diff --git a/controllers/codebase/service/chain/put_gitlab_ci_config.go b/controllers/codebase/service/chain/put_gitlab_ci_config.go index 451fccc3..c3acb9ab 100644 --- a/controllers/codebase/service/chain/put_gitlab_ci_config.go +++ b/controllers/codebase/service/chain/put_gitlab_ci_config.go @@ -16,14 +16,17 @@ import ( ) type PutGitLabCIConfig struct { - client client.Client - gitlabCIManager gitlabci.Manager - gitProviderFactory gitproviderv2.GitProviderFactory - createGitProviderWithConfig func(config gitproviderv2.Config) gitproviderv2.Git + client client.Client + gitlabCIManager gitlabci.Manager + gitProviderFactory gitproviderv2.GitProviderFactory } -func NewPutGitLabCIConfig(c client.Client, m gitlabci.Manager, gitProviderFactory gitproviderv2.GitProviderFactory, createGitProviderWithConfig func(config gitproviderv2.Config) gitproviderv2.Git) *PutGitLabCIConfig { - return &PutGitLabCIConfig{client: c, gitlabCIManager: m, gitProviderFactory: gitProviderFactory, createGitProviderWithConfig: createGitProviderWithConfig} +func NewPutGitLabCIConfig( + c client.Client, + m gitlabci.Manager, + gitProviderFactory gitproviderv2.GitProviderFactory, +) *PutGitLabCIConfig { + return &PutGitLabCIConfig{client: c, gitlabCIManager: m, gitProviderFactory: gitProviderFactory} } func (h *PutGitLabCIConfig) ServeRequest(ctx context.Context, codebase *codebaseApi.Codebase) error { @@ -83,14 +86,14 @@ func (h *PutGitLabCIConfig) tryToPushGitLabCIConfig(ctx context.Context, codebas log := ctrl.LoggerFrom(ctx) // Prepare git repository (get server, clone, checkout) - gitCtx, err := PrepareGitRepository(ctx, h.client, codebase, h.gitProviderFactory, h.createGitProviderWithConfig) + gitCtx, err := PrepareGitRepository(ctx, h.client, codebase, h.gitProviderFactory) if err != nil { setFailedFields(codebase, codebaseApi.RepositoryProvisioning, err.Error()) return fmt.Errorf("failed to prepare git repository: %w", err) } // Create git provider using factory - g := h.gitProviderFactory(gitCtx.GitServer, gitCtx.GitServerSecret) + g := h.gitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(gitCtx.GitServer, gitCtx.GitServerSecret)) // Inject GitLab CI configuration log.Info("Start injecting GitLab CI config") diff --git a/controllers/codebase/service/chain/put_gitlab_ci_config_test.go b/controllers/codebase/service/chain/put_gitlab_ci_config_test.go index f7184de7..30f87f18 100644 --- a/controllers/codebase/service/chain/put_gitlab_ci_config_test.go +++ b/controllers/codebase/service/chain/put_gitlab_ci_config_test.go @@ -145,7 +145,7 @@ func TestPutGitLabCIConfig_ServeRequest(t *testing.T) { gitClient: func(t *testing.T) gitproviderv2.Git { mock := gitmocks.NewMockGit(t) - mock.On("Clone", testify.Anything, testify.Anything, testify.Anything, 0). + mock.On("Clone", testify.Anything, testify.Anything, testify.Anything). Return(nil) mock.On("GetCurrentBranchName", testify.Anything, testify.Anything). Return("master", nil) @@ -285,7 +285,7 @@ func TestPutGitLabCIConfig_ServeRequest(t *testing.T) { objects: []client.Object{gitlabGitServer, gitlabGitServerSecret}, gitClient: func(t *testing.T) gitproviderv2.Git { mock := gitmocks.NewMockGit(t) - mock.On("Clone", testify.Anything, testify.Anything, testify.Anything, 0). + mock.On("Clone", testify.Anything, testify.Anything, testify.Anything). Return(errors.New("failed to clone git repository")) return mock }, @@ -316,7 +316,7 @@ func TestPutGitLabCIConfig_ServeRequest(t *testing.T) { objects: []client.Object{gitlabGitServer, gitlabGitServerSecret}, gitClient: func(t *testing.T) gitproviderv2.Git { mock := gitmocks.NewMockGit(t) - mock.On("Clone", testify.Anything, testify.Anything, testify.Anything, 0). + mock.On("Clone", testify.Anything, testify.Anything, testify.Anything). Return(nil) mock.On("GetCurrentBranchName", testify.Anything, testify.Anything). Return("", errors.New("failed to get current branch")) @@ -354,7 +354,7 @@ func TestPutGitLabCIConfig_ServeRequest(t *testing.T) { objects: []client.Object{gitlabGitServer, gitlabGitServerSecret}, gitClient: func(t *testing.T) gitproviderv2.Git { mock := gitmocks.NewMockGit(t) - mock.On("Clone", testify.Anything, testify.Anything, testify.Anything, 0). + mock.On("Clone", testify.Anything, testify.Anything, testify.Anything). Return(nil) mock.On("GetCurrentBranchName", testify.Anything, testify.Anything). Return("master", nil) @@ -404,7 +404,7 @@ func TestPutGitLabCIConfig_ServeRequest(t *testing.T) { }, gitClient: func(t *testing.T) gitproviderv2.Git { mock := gitmocks.NewMockGit(t) - mock.On("Clone", testify.Anything, testify.Anything, testify.Anything, 0). + mock.On("Clone", testify.Anything, testify.Anything, testify.Anything). Return(nil) mock.On("GetCurrentBranchName", testify.Anything, testify.Anything). Return("master", nil) @@ -456,7 +456,7 @@ func TestPutGitLabCIConfig_ServeRequest(t *testing.T) { }, gitClient: func(t *testing.T) gitproviderv2.Git { mock := gitmocks.NewMockGit(t) - mock.On("Clone", testify.Anything, testify.Anything, testify.Anything, 0). + mock.On("Clone", testify.Anything, testify.Anything, testify.Anything). Return(nil) mock.On("GetCurrentBranchName", testify.Anything, testify.Anything). Return("master", nil) @@ -505,10 +505,7 @@ func TestPutGitLabCIConfig_ServeRequest(t *testing.T) { h := NewPutGitLabCIConfig( k8sClient, gitlabci.NewManager(k8sClient), - func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { - return gitClient - }, - func(config gitproviderv2.Config) gitproviderv2.Git { + func(gitproviderv2.Config) gitproviderv2.Git { return gitClient }, ) diff --git a/controllers/codebase/service/chain/put_project.go b/controllers/codebase/service/chain/put_project.go index e2ffa20d..5846da8d 100644 --- a/controllers/codebase/service/chain/put_project.go +++ b/controllers/codebase/service/chain/put_project.go @@ -21,11 +21,10 @@ import ( ) type PutProject struct { - k8sClient client.Client - gerritClient gerrit.Client - gitProjectProvider func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) - gitProviderFactory gitproviderv2.GitProviderFactory - createGitProviderWithConfig func(config gitproviderv2.Config) gitproviderv2.Git + k8sClient client.Client + gerritClient gerrit.Client + gitApiProjectProvider func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) + gitProviderFactory gitproviderv2.GitProviderFactory } var ( @@ -38,14 +37,12 @@ func NewPutProject( gerritProvider gerrit.Client, gitProjectProvider func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error), gitProviderFactory gitproviderv2.GitProviderFactory, - createGitProviderWithConfig func(config gitproviderv2.Config) gitproviderv2.Git, ) *PutProject { return &PutProject{ - k8sClient: c, - gerritClient: gerritProvider, - gitProjectProvider: gitProjectProvider, - gitProviderFactory: gitProviderFactory, - createGitProviderWithConfig: createGitProviderWithConfig, + k8sClient: c, + gerritClient: gerritProvider, + gitApiProjectProvider: gitProjectProvider, + gitProviderFactory: gitProviderFactory, } } @@ -125,7 +122,7 @@ func (h *PutProject) createProject( codebase *codebaseApi.Codebase, repoContext *GitRepositoryContext, ) error { - g := h.gitProviderFactory(repoContext.GitServer, repoContext.GitServerSecret) + gitProvider := h.gitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(repoContext.GitServer, repoContext.GitServerSecret)) if repoContext.GitServer.Spec.GitProvider == codebaseApi.GitProviderGerrit { err := h.createGerritProject(ctx, repoContext.GitServer, repoContext.PrivateSSHKey, codebase.Spec.GetProjectID()) @@ -138,7 +135,7 @@ func (h *PutProject) createProject( } } - err := h.pushProject(ctx, g, codebase.Spec.GetProjectID(), repoContext) + err := h.pushProject(ctx, gitProvider, codebase.Spec.GetProjectID(), repoContext) if err != nil { return err } @@ -237,26 +234,19 @@ func (h *PutProject) checkoutBranch(ctx context.Context, codebase *codebaseApi.C codebase.Spec.BranchToCopyInDefaultBranch, ) - g := h.gitProviderFactory(repoContext.GitServer, repoContext.GitServerSecret) - - repoUrl, err := util.GetRepoUrl(codebase) - if err != nil { - return fmt.Errorf("failed to build repo url: %w", err) - } + gitprovider := h.gitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(repoContext.GitServer, repoContext.GitServerSecret)) // TODO: branchToCopyInDefaultBranch is never used. Check if we can remove it. if codebase.Spec.BranchToCopyInDefaultBranch != "" && codebase.Spec.DefaultBranch != codebase.Spec.BranchToCopyInDefaultBranch { log.Info("Start checkout branch to copy") - err = CheckoutBranch(ctx, repoUrl, repoContext.WorkDir, codebase.Spec.BranchToCopyInDefaultBranch, g, codebase, h.k8sClient, h.createGitProviderWithConfig) - if err != nil { + if err := CheckoutBranch(ctx, codebase.Spec.BranchToCopyInDefaultBranch, repoContext, codebase, h.k8sClient, h.gitProviderFactory); err != nil { return fmt.Errorf("failed to checkout default branch %s: %w", codebase.Spec.DefaultBranch, err) } log.Info("Start replace default branch") - err = h.replaceDefaultBranch(ctx, g, repoContext.WorkDir, codebase.Spec.DefaultBranch, codebase.Spec.BranchToCopyInDefaultBranch) - if err != nil { + if err := h.replaceDefaultBranch(ctx, gitprovider, repoContext.WorkDir, codebase.Spec.DefaultBranch, codebase.Spec.BranchToCopyInDefaultBranch); err != nil { return fmt.Errorf("failed to replace master: %w", err) } @@ -265,8 +255,7 @@ func (h *PutProject) checkoutBranch(ctx context.Context, codebase *codebaseApi.C log.Info("Start checkout branch") - err = CheckoutBranch(ctx, repoUrl, repoContext.WorkDir, codebase.Spec.DefaultBranch, g, codebase, h.k8sClient, h.createGitProviderWithConfig) - if err != nil { + if err := CheckoutBranch(ctx, codebase.Spec.DefaultBranch, repoContext, codebase, h.k8sClient, h.gitProviderFactory); err != nil { return fmt.Errorf("failed to checkout default branch %s: %w", codebase.Spec.DefaultBranch, err) } @@ -286,7 +275,7 @@ func (h *PutProject) createGitThirdPartyProject( log.Info("Start creating project in git provider") - gitProvider, err := h.gitProjectProvider(gitServer, gitProviderToken) + gitProvider, err := h.gitApiProjectProvider(gitServer, gitProviderToken) if err != nil { return fmt.Errorf("failed to create git provider: %w", err) } @@ -364,7 +353,7 @@ func (h *PutProject) setDefaultBranch( log.Info("Set default branch in git provider") - gitProvider, err := h.gitProjectProvider(gitServer, gitProviderToken) + gitProvider, err := h.gitApiProjectProvider(gitServer, gitProviderToken) if err != nil { return fmt.Errorf("failed to create git provider: %w", err) } @@ -416,8 +405,8 @@ func (h *PutProject) tryToCloneRepo( config.Token = *repositoryPassword } - g := h.createGitProviderWithConfig(config) - if err := g.Clone(ctx, repoUrl, repoContext.WorkDir, 0); err != nil { + gitProvider := h.gitProviderFactory(config) + if err := gitProvider.Clone(ctx, repoUrl, repoContext.WorkDir); err != nil { return fmt.Errorf("failed to clone repository: %w", err) } @@ -440,13 +429,13 @@ func (h *PutProject) squashCommits(ctx context.Context, workDir string, strategy return fmt.Errorf("failed to remove .git folder: %w", err) } - g := h.createGitProviderWithConfig(gitproviderv2.Config{}) + gitProvider := h.gitProviderFactory(gitproviderv2.Config{}) - if err := g.Init(ctx, workDir); err != nil { + if err := gitProvider.Init(ctx, workDir); err != nil { return fmt.Errorf("failed to create git repository: %w", err) } - if err := g.Commit(ctx, workDir, "Initial commit"); err != nil { + if err := gitProvider.Commit(ctx, workDir, "Initial commit"); err != nil { return fmt.Errorf("failed to commit all default content: %w", err) } @@ -468,15 +457,15 @@ func (h *PutProject) emptyProjectProvisioning(ctx context.Context, repoContext * log.Info("Initialing empty git repository") - g := h.createGitProviderWithConfig(gitproviderv2.Config{}) + gitProvider := h.gitProviderFactory(gitproviderv2.Config{}) - if err := g.Init(ctx, repoContext.WorkDir); err != nil { + if err := gitProvider.Init(ctx, repoContext.WorkDir); err != nil { return fmt.Errorf("failed to create empty git repository: %w", err) } log.Info("Making initial commit") - if err := g.Commit(ctx, repoContext.WorkDir, "Initial commit", gitproviderv2.CommitAllowEmpty()); err != nil { + if err := gitProvider.Commit(ctx, repoContext.WorkDir, "Initial commit", gitproviderv2.CommitAllowEmpty()); err != nil { return fmt.Errorf("failed to create Initial commit: %w", err) } @@ -488,7 +477,7 @@ func (h *PutProject) notEmptyProjectProvisioning(ctx context.Context, codebase * log.Info("Start initial provisioning for non-empty project") - repoUrl, err := util.GetRepoUrl(codebase) + repoUrl, err := util.GetRepoUrlForClone(codebase) if err != nil { return fmt.Errorf("failed to build repo url: %w", err) } @@ -505,7 +494,7 @@ func (h *PutProject) notEmptyProjectProvisioning(ctx context.Context, codebase * Username: *repu, Token: *repp, } - tempProvider := h.createGitProviderWithConfig(tempConfig) + tempProvider := h.gitProviderFactory(tempConfig) 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) diff --git a/controllers/codebase/service/chain/put_project_test.go b/controllers/codebase/service/chain/put_project_test.go index 4450c5ea..97b3767a 100644 --- a/controllers/codebase/service/chain/put_project_test.go +++ b/controllers/codebase/service/chain/put_project_test.go @@ -152,7 +152,7 @@ func TestPutProject_ServeRequest(t *testing.T) { Maybe(). Return(nil) - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { return mock } }, @@ -260,7 +260,7 @@ func TestPutProject_ServeRequest(t *testing.T) { Maybe(). Return(nil) - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { return mock } }, @@ -360,7 +360,7 @@ func TestPutProject_ServeRequest(t *testing.T) { Maybe(). Return(nil) - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { return mock } }, @@ -462,7 +462,7 @@ func TestPutProject_ServeRequest(t *testing.T) { Maybe(). Return(nil) - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { return mock } }, @@ -567,7 +567,7 @@ func TestPutProject_ServeRequest(t *testing.T) { Maybe(). Return(errors.New("failed to push changes")) - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { return mock } }, @@ -642,7 +642,7 @@ func TestPutProject_ServeRequest(t *testing.T) { }, objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { mock := v2mocks.NewMockGit(t) mock.On("CheckPermissions", testify.Anything, testify.Anything). @@ -725,7 +725,7 @@ func TestPutProject_ServeRequest(t *testing.T) { }, objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { mock := v2mocks.NewMockGit(t) mock.On("CheckPermissions", testify.Anything, testify.Anything). @@ -810,7 +810,7 @@ func TestPutProject_ServeRequest(t *testing.T) { }, objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { mock := v2mocks.NewMockGit(t) mock.On("CheckPermissions", testify.Anything, testify.Anything). @@ -893,7 +893,7 @@ func TestPutProject_ServeRequest(t *testing.T) { }, objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { mock := v2mocks.NewMockGit(t) mock.On("CheckPermissions", testify.Anything, testify.Anything). @@ -966,7 +966,7 @@ func TestPutProject_ServeRequest(t *testing.T) { }, objects: []client.Object{gerritGitServer}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { return v2mocks.NewMockGit(t) } }, @@ -1014,7 +1014,7 @@ func TestPutProject_ServeRequest(t *testing.T) { }, objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { mock := v2mocks.NewMockGit(t) mock.On("CheckPermissions", testify.Anything, testify.Anything). @@ -1072,7 +1072,7 @@ func TestPutProject_ServeRequest(t *testing.T) { }, objects: []client.Object{githubGitServer, githubGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { mock := v2mocks.NewMockGit(t) mock.On("CheckPermissions", testify.Anything, testify.Anything). @@ -1150,7 +1150,7 @@ func TestPutProject_ServeRequest(t *testing.T) { }, objects: []client.Object{githubGitServer, githubGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { mock := v2mocks.NewMockGit(t) mock.On("CheckPermissions", testify.Anything, testify.Anything). @@ -1219,7 +1219,7 @@ func TestPutProject_ServeRequest(t *testing.T) { }, objects: []client.Object{gitlabGitServer, gitlabGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { mock := v2mocks.NewMockGit(t) mock.On("CheckPermissions", testify.Anything, testify.Anything). @@ -1297,7 +1297,7 @@ func TestPutProject_ServeRequest(t *testing.T) { }, objects: []client.Object{gitlabGitServer, gitlabGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { mock := v2mocks.NewMockGit(t) mock.On("CheckPermissions", testify.Anything, testify.Anything). @@ -1367,7 +1367,7 @@ func TestPutProject_ServeRequest(t *testing.T) { }, objects: []client.Object{gitlabGitServer, gitlabGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { + return func(cfg gitproviderv2.Config) gitproviderv2.Git { mock := v2mocks.NewMockGit(t) mock.On("CheckPermissions", testify.Anything, testify.Anything). @@ -1532,7 +1532,6 @@ func TestPutProject_ServeRequest(t *testing.T) { tt.gerritClient(t), tt.gitProvider(t), tt.gitProviderFactory(t), - tt.createGitProviderWithConfig(t), ) err := h.ServeRequest(ctrl.LoggerInto(context.Background(), logr.Discard()), tt.codebase) diff --git a/controllers/codebase/service/template/template.go b/controllers/codebase/service/template/template.go index 313feb70..4f411c56 100644 --- a/controllers/codebase/service/template/template.go +++ b/controllers/codebase/service/template/template.go @@ -81,7 +81,7 @@ func buildTemplateConfig(ctx context.Context, c client.Client, cb *codebaseApi.C func getProjectUrl(c client.Client, s *codebaseApi.CodebaseSpec, n string) (string, error) { switch s.Strategy { case "create": - p := util.BuildRepoUrl(s) + p := util.BuildTemplateRepoUrl(s) return p, nil case "clone": diff --git a/controllers/codebasebranch/chain/check_reference.go b/controllers/codebasebranch/chain/check_reference.go index 3456ae18..b5ca71a0 100644 --- a/controllers/codebasebranch/chain/check_reference.go +++ b/controllers/codebasebranch/chain/check_reference.go @@ -73,13 +73,13 @@ func (c CheckReferenceExists) ServeRequest(ctx context.Context, codebaseBranch * } // Create git provider using factory - g := c.GitProviderFactory(gitServer, secret) + g := c.GitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(gitServer, secret)) workDir := GetCodebaseBranchWorkingDirectory(codebaseBranch) if !DirectoryExistsNotEmpty(workDir) { repoGitUrl := util.GetProjectGitUrl(gitServer, secret, codebase.Spec.GetProjectID()) - if err := g.Clone(ctx, repoGitUrl, workDir, 0); err != nil { + if err := g.Clone(ctx, repoGitUrl, workDir); err != nil { return c.processErr(codebaseBranch, fmt.Errorf("failed to clone repository: %w", err)) } } diff --git a/controllers/codebasebranch/chain/check_reference_test.go b/controllers/codebasebranch/chain/check_reference_test.go index 9b361c2c..77515dce 100644 --- a/controllers/codebasebranch/chain/check_reference_test.go +++ b/controllers/codebasebranch/chain/check_reference_test.go @@ -251,10 +251,12 @@ func TestCheckReferenceExists_ServeRequest(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + gitProvider := tt.gitClient() + c := CheckReferenceExists{ Client: fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(tt.objects...).Build(), - GitProviderFactory: func(gitServer *codebaseApi.GitServer, secret *coreV1.Secret) gitproviderv2.Git { - return tt.gitClient() + GitProviderFactory: func(cfg gitproviderv2.Config) gitproviderv2.Git { + return gitProvider }, } diff --git a/controllers/codebasebranch/chain/factory/factory.go b/controllers/codebasebranch/chain/factory/factory.go index 4eec25e1..bed794c3 100644 --- a/controllers/codebasebranch/chain/factory/factory.go +++ b/controllers/codebasebranch/chain/factory/factory.go @@ -1,10 +1,8 @@ package factory import ( - corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" - codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1" "github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/chain" "github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/chain/clean_tmp_directory" "github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/chain/empty" @@ -13,33 +11,19 @@ import ( "github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/chain/put_codebase_image_stream" "github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/service" gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" - "github.com/epam/edp-codebase-operator/v2/pkg/util" ) func GetDeletionChain() handler.CodebaseBranchHandler { return empty.MakeChain("no deletion chain for tekton", false) } -// DefaultGitProviderFactory creates a v2.GitProvider with credentials from GitServer and Secret. -func DefaultGitProviderFactory(gitServer *codebaseApi.GitServer, secret *corev1.Secret) gitproviderv2.Git { - config := gitproviderv2.Config{ - SSHKey: string(secret.Data[util.PrivateSShKeyName]), - SSHUser: gitServer.Spec.GitUser, - SSHPort: gitServer.Spec.SshPort, - GitProvider: gitServer.Spec.GitProvider, - Token: string(secret.Data[util.GitServerSecretTokenField]), - } - - return gitproviderv2.NewGitProvider(config) -} - func GetChain(c client.Client) handler.CodebaseBranchHandler { return chain.CheckReferenceExists{ Client: c, - GitProviderFactory: DefaultGitProviderFactory, + GitProviderFactory: gitproviderv2.NewGitProviderFactory, Next: put_branch_in_git.PutBranchInGit{ Client: c, - GitProviderFactory: DefaultGitProviderFactory, + GitProviderFactory: gitproviderv2.NewGitProviderFactory, Next: chain.ProcessNewVersion{ Client: c, Next: put_codebase_image_stream.PutCodebaseImageStream{ diff --git a/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git.go b/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git.go index 0a3d3a40..a215cef6 100644 --- a/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git.go +++ b/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git.go @@ -89,31 +89,31 @@ func (h PutBranchInGit) ServeRequest(ctx context.Context, branch *codebaseApi.Co } // Create git provider using factory - g := h.GitProviderFactory(gitServer, secret) + gitProvider := h.GitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(gitServer, secret)) wd := chain.GetCodebaseBranchWorkingDirectory(branch) if !checkDirectory(wd) { repoGitUrl := util.GetProjectGitUrl(gitServer, secret, codebase.Spec.GetProjectID()) - if err := g.Clone(ctx, repoGitUrl, wd, 0); err != nil { + if err := gitProvider.Clone(ctx, repoGitUrl, wd); err != nil { putGitBranchSetFailedFields(branch, err.Error()) return fmt.Errorf("failed to clone repository: %w", err) } } - currentBranchName, err := g.GetCurrentBranchName(ctx, wd) + currentBranchName, err := gitProvider.GetCurrentBranchName(ctx, wd) if err != nil { return fmt.Errorf("failed to get current branch name: %w", err) } if currentBranchName != codebase.Spec.DefaultBranch { - if err = g.CheckoutRemoteBranch(ctx, wd, codebase.Spec.DefaultBranch); err != nil { + if err = gitProvider.CheckoutRemoteBranch(ctx, wd, codebase.Spec.DefaultBranch); err != nil { return fmt.Errorf("failed to checkout to default branch %s: %w", codebase.Spec.DefaultBranch, err) } } - err = g.CreateRemoteBranch(ctx, wd, branch.Spec.BranchName, branch.Spec.FromCommit) + err = gitProvider.CreateRemoteBranch(ctx, wd, branch.Spec.BranchName, branch.Spec.FromCommit) if err != nil { putGitBranchSetFailedFields(branch, err.Error()) diff --git a/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git_test.go b/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git_test.go index d8492e19..f6b568f3 100644 --- a/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git_test.go +++ b/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git_test.go @@ -121,7 +121,7 @@ func TestPutBranchInGit_ShouldBeExecutedSuccessfullyWithDefaultVersioning(t *tes err := PutBranchInGit{ Client: fakeCl, - GitProviderFactory: func(gitServer *codebaseApi.GitServer, secret *coreV1.Secret) gitproviderv2.Git { + GitProviderFactory: func(cfg gitproviderv2.Config) gitproviderv2.Git { return mGit }, }.ServeRequest(ctrl.LoggerInto(context.Background(), logr.Discard()), cb) @@ -208,7 +208,7 @@ func TestPutBranchInGit_ShouldFailgetCurrentbranch(t *testing.T) { err := PutBranchInGit{ Client: fakeCl, - GitProviderFactory: func(gitServer *codebaseApi.GitServer, secret *coreV1.Secret) gitproviderv2.Git { + GitProviderFactory: func(cfg gitproviderv2.Config) gitproviderv2.Git { return mGit }, }.ServeRequest(ctrl.LoggerInto(context.Background(), logr.Discard()), cb) @@ -306,7 +306,7 @@ func TestPutBranchInGit_ShouldFailCreateRemoteBranch(t *testing.T) { err := PutBranchInGit{ Client: fakeCl, - GitProviderFactory: func(gitServer *codebaseApi.GitServer, secret *coreV1.Secret) gitproviderv2.Git { + GitProviderFactory: func(cfg gitproviderv2.Config) gitproviderv2.Git { return mGit }, }.ServeRequest(ctrl.LoggerInto(context.Background(), logr.Discard()), cb) @@ -455,7 +455,7 @@ func TestPutBranchInGit_ShouldBeExecutedSuccessfullyWithEdpVersioning(t *testing wd := chain.GetCodebaseBranchWorkingDirectory(cb) - mGit.On("Clone", testifymock.Anything, testifymock.Anything, wd, testifymock.Anything). + mGit.On("Clone", testifymock.Anything, testifymock.Anything, wd). Return(nil) mGit.On( "GetCurrentBranchName", @@ -466,7 +466,7 @@ func TestPutBranchInGit_ShouldBeExecutedSuccessfullyWithEdpVersioning(t *testing err := PutBranchInGit{ Client: fakeCl, - GitProviderFactory: func(gitServer *codebaseApi.GitServer, secret *coreV1.Secret) gitproviderv2.Git { + GitProviderFactory: func(cfg gitproviderv2.Config) gitproviderv2.Git { return mGit }, Service: &service.CodebaseBranchServiceProvider{ @@ -609,7 +609,7 @@ func TestPutBranchInGit_SkipAlreadyCreated(t *testing.T) { mGit := gitServerMocks.NewMockGit(t) err := PutBranchInGit{ Client: fake.NewClientBuilder().Build(), - GitProviderFactory: func(gitServer *codebaseApi.GitServer, secret *coreV1.Secret) gitproviderv2.Git { + GitProviderFactory: func(cfg gitproviderv2.Config) gitproviderv2.Git { return mGit }, }.ServeRequest(ctrl.LoggerInto(context.Background(), logr.Discard()), codeBaseBranch) diff --git a/pkg/git/git_test.go b/pkg/git/git_test.go index b4689435..b3ec8af2 100644 --- a/pkg/git/git_test.go +++ b/pkg/git/git_test.go @@ -218,66 +218,6 @@ func TestGitProvider_RemoveBranch(t *testing.T) { } } -func TestGitProvider_RenameBranch(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - initRepo func(t *testing.T) string - oldName string - newName string - wantErr require.ErrorAssertionFunc - }{ - { - name: "should rename branch successfully", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - r, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - // Create initial commit - w, err := r.Worktree() - require.NoError(t, err) - - f, err := os.Create(path.Join(dir, "test.txt")) - require.NoError(t, err) - _, err = f.WriteString("test content") - require.NoError(t, err) - require.NoError(t, f.Close()) - - _, err = w.Add("test.txt") - require.NoError(t, err) - - _, err = w.Commit("initial commit", &gogit.CommitOptions{ - Author: &object.Signature{ - Name: "test", - Email: "test@example.com", - When: time.Now(), - }, - }) - require.NoError(t, err) - - return dir - }, - oldName: "master", - newName: "main", - wantErr: require.NoError, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - gp := gitproviderv2.NewGitProvider(gitproviderv2.Config{}) - dir := tt.initRepo(t) - - err := gp.RenameBranch(context.Background(), dir, tt.oldName, tt.newName) - tt.wantErr(t, err) - }) - } -} - func Test_initAuth(t *testing.T) { t.Parallel() diff --git a/pkg/git/v2/factory.go b/pkg/git/v2/factory.go index 9e1faf15..55a5c01d 100644 --- a/pkg/git/v2/factory.go +++ b/pkg/git/v2/factory.go @@ -9,17 +9,24 @@ import ( // GitProviderFactory creates a Git provider from GitServer and Secret. // This is a factory pattern to enable dependency injection and mocking in tests. -type GitProviderFactory func(gitServer *codebaseApi.GitServer, secret *corev1.Secret) Git +type GitProviderFactory func(cfg Config) Git // DefaultGitProviderFactory is the default factory implementation for creating GitProvider instances. func DefaultGitProviderFactory(gitServer *codebaseApi.GitServer, secret *corev1.Secret) Git { - config := Config{ + return NewGitProvider(NewConfigFromGitServerAndSecret(gitServer, secret)) +} + +func NewGitProviderFactory(cfg Config) Git { + return NewGitProvider(cfg) +} + +func NewConfigFromGitServerAndSecret(gitServer *codebaseApi.GitServer, secret *corev1.Secret) Config { + return Config{ SSHKey: string(secret.Data[util.PrivateSShKeyName]), SSHUser: gitServer.Spec.GitUser, SSHPort: gitServer.Spec.SshPort, GitProvider: gitServer.Spec.GitProvider, Token: string(secret.Data[util.GitServerSecretTokenField]), + Username: string(secret.Data[util.GitServerSecretUserNameField]), } - - return NewGitProvider(config) } diff --git a/pkg/git/v2/git.go b/pkg/git/v2/git.go index 53af7632..29be0276 100644 --- a/pkg/git/v2/git.go +++ b/pkg/git/v2/git.go @@ -5,9 +5,8 @@ import "context" // Git interface provides methods for working with git using v2 GitProvider. // This interface uses context-aware methods and handles authentication via Config. type Git interface { - // Clone clones a repository to the specified destination. - // depth: 0 means full clone, >0 means shallow clone with specified depth. - Clone(ctx context.Context, repoURL, destination string, depth int) error + // Clone clones a repository to the specified destination with full history. + Clone(ctx context.Context, repoURL, destination string) error // Commit commits changes in the working directory. Commit(ctx context.Context, directory, message string, ops ...CommitOps) error @@ -36,9 +35,6 @@ type Git interface { // RemoveBranch removes a local branch. RemoveBranch(ctx context.Context, directory, branchName string) error - // RenameBranch renames a branch. - RenameBranch(ctx context.Context, directory, oldName, newName string) error - // CreateChildBranch creates a new branch from an existing branch. CreateChildBranch(ctx context.Context, directory, parentBranch, newBranch string) error diff --git a/pkg/git/v2/mocks/git_mock.go b/pkg/git/v2/mocks/git_mock.go index 684e15d6..fd838577 100644 --- a/pkg/git/v2/mocks/git_mock.go +++ b/pkg/git/v2/mocks/git_mock.go @@ -262,17 +262,17 @@ func (_c *MockGit_CheckoutRemoteBranch_Call) RunAndReturn(run func(context.Conte return _c } -// Clone provides a mock function with given fields: ctx, repoURL, destination, depth -func (_m *MockGit) Clone(ctx context.Context, repoURL string, destination string, depth int) error { - ret := _m.Called(ctx, repoURL, destination, depth) +// Clone provides a mock function with given fields: ctx, repoURL, destination +func (_m *MockGit) Clone(ctx context.Context, repoURL string, destination string) error { + ret := _m.Called(ctx, repoURL, destination) if len(ret) == 0 { panic("no return value specified for Clone") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, int) error); ok { - r0 = rf(ctx, repoURL, destination, depth) + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, repoURL, destination) } else { r0 = ret.Error(0) } @@ -289,14 +289,13 @@ type MockGit_Clone_Call struct { // - ctx context.Context // - repoURL string // - destination string -// - depth int -func (_e *MockGit_Expecter) Clone(ctx interface{}, repoURL interface{}, destination interface{}, depth interface{}) *MockGit_Clone_Call { - return &MockGit_Clone_Call{Call: _e.mock.On("Clone", ctx, repoURL, destination, depth)} +func (_e *MockGit_Expecter) Clone(ctx interface{}, repoURL interface{}, destination interface{}) *MockGit_Clone_Call { + return &MockGit_Clone_Call{Call: _e.mock.On("Clone", ctx, repoURL, destination)} } -func (_c *MockGit_Clone_Call) Run(run func(ctx context.Context, repoURL string, destination string, depth int)) *MockGit_Clone_Call { +func (_c *MockGit_Clone_Call) Run(run func(ctx context.Context, repoURL string, destination string)) *MockGit_Clone_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(int)) + run(args[0].(context.Context), args[1].(string), args[2].(string)) }) return _c } @@ -306,7 +305,7 @@ func (_c *MockGit_Clone_Call) Return(_a0 error) *MockGit_Clone_Call { return _c } -func (_c *MockGit_Clone_Call) RunAndReturn(run func(context.Context, string, string, int) error) *MockGit_Clone_Call { +func (_c *MockGit_Clone_Call) RunAndReturn(run func(context.Context, string, string) error) *MockGit_Clone_Call { _c.Call.Return(run) return _c } @@ -841,55 +840,6 @@ func (_c *MockGit_RemoveBranch_Call) RunAndReturn(run func(context.Context, stri return _c } -// RenameBranch provides a mock function with given fields: ctx, directory, oldName, newName -func (_m *MockGit) RenameBranch(ctx context.Context, directory string, oldName string, newName string) error { - ret := _m.Called(ctx, directory, oldName, newName) - - if len(ret) == 0 { - panic("no return value specified for RenameBranch") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { - r0 = rf(ctx, directory, oldName, newName) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_RenameBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RenameBranch' -type MockGit_RenameBranch_Call struct { - *mock.Call -} - -// RenameBranch is a helper method to define mock.On call -// - ctx context.Context -// - directory string -// - oldName string -// - newName string -func (_e *MockGit_Expecter) RenameBranch(ctx interface{}, directory interface{}, oldName interface{}, newName interface{}) *MockGit_RenameBranch_Call { - return &MockGit_RenameBranch_Call{Call: _e.mock.On("RenameBranch", ctx, directory, oldName, newName)} -} - -func (_c *MockGit_RenameBranch_Call) Run(run func(ctx context.Context, directory string, oldName string, newName string)) *MockGit_RenameBranch_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string)) - }) - return _c -} - -func (_c *MockGit_RenameBranch_Call) Return(_a0 error) *MockGit_RenameBranch_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_RenameBranch_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockGit_RenameBranch_Call { - _c.Call.Return(run) - return _c -} - // NewMockGit creates a new instance of MockGit. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMockGit(t interface { diff --git a/pkg/git/v2/provider.go b/pkg/git/v2/provider.go index 39c5424e..40f5b7bb 100644 --- a/pkg/git/v2/provider.go +++ b/pkg/git/v2/provider.go @@ -11,6 +11,7 @@ import ( "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/plumbing/storer" "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/plumbing/transport/ssh" @@ -150,7 +151,7 @@ func (p *GitProvider) getTokenAuth() transport.AuthMethod { } // Clone clones a repository to the specified destination. -func (p *GitProvider) Clone(ctx context.Context, repoURL, destination string, depth int) error { +func (p *GitProvider) Clone(ctx context.Context, repoURL, destination string) error { log := ctrl.LoggerFrom(ctx).WithValues("repository", repoURL, "destination", destination) log.Info("Cloning repository") @@ -159,22 +160,37 @@ func (p *GitProvider) Clone(ctx context.Context, repoURL, destination string, de return fmt.Errorf("failed to get authentication: %w", err) } + // Clone the repository (gets default branch) cloneOptions := &git.CloneOptions{ URL: repoURL, Progress: os.Stdout, Auth: auth, } - if depth > 0 { - cloneOptions.Depth = depth - } - - _, err = git.PlainCloneContext(ctx, destination, false, cloneOptions) + repo, err := git.PlainCloneContext(ctx, destination, false, cloneOptions) if err != nil { return fmt.Errorf("failed to clone repository: %w", err) } - log.Info("Repository cloned successfully") + log.Info("Repository cloned successfully, now fetching all branches and tags") + + // Fetch all refs (branches, tags, etc.) to get everything similar to --mirror + fetchOptions := &git.FetchOptions{ + RemoteName: "origin", + Auth: auth, + Progress: os.Stdout, + RefSpecs: []config.RefSpec{ + config.RefSpec("+refs/heads/*:refs/heads/*"), + config.RefSpec("+refs/tags/*:refs/tags/*"), + }, + } + + err = repo.FetchContext(ctx, fetchOptions) + if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { + return fmt.Errorf("failed to fetch all branches and tags: %w", err) + } + + log.Info("All branches and tags fetched successfully") return nil } @@ -271,7 +287,7 @@ func (p *GitProvider) Push(ctx context.Context, directory string, refspecs ...st } err = repo.PushContext(ctx, pushOptions) - if err != nil && err != git.NoErrAlreadyUpToDate { + if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { return fmt.Errorf("failed to push: %w", err) } @@ -313,7 +329,7 @@ func (p *GitProvider) Checkout(ctx context.Context, directory, branchName string } err = repo.FetchContext(ctx, fetchOptions) - if err != nil && err != git.NoErrAlreadyUpToDate { + if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { return fmt.Errorf("failed to fetch: %w", err) } @@ -353,72 +369,40 @@ func (p *GitProvider) CreateRemoteBranch(ctx context.Context, directory, branchN return fmt.Errorf("failed to open repository at %q: %w", directory, err) } - // Check if branch already exists branches, err := repo.Branches() if err != nil { - return fmt.Errorf("failed to get branches: %w", err) + return fmt.Errorf("failed to get branches iterator: %w", err) } - exists := false - - err = branches.ForEach(func(ref *plumbing.Reference) error { - if ref.Name().Short() == branchName { - exists = true - } - - return nil - }) + exists, err := branchExists(branchName, branches) if err != nil { - return fmt.Errorf("failed to iterate branches: %w", err) + return err } if exists { - log.Info("Branch already exists, skipping creation") + log.Info("Branch already exists. Skip creating") return nil } - // Resolve the target commit hash - var targetHash plumbing.Hash - - if fromRef == "" { - // Use HEAD if no reference specified - head, err := repo.Head() - if err != nil { - return fmt.Errorf("failed to get HEAD: %w", err) - } - - targetHash = head.Hash() - } else { - // Try to resolve as branch first - branchRef, err := repo.Reference(plumbing.NewBranchReferenceName(fromRef), false) - if err == nil { - targetHash = branchRef.Hash() - } else { - // Try as commit hash - targetHash = plumbing.NewHash(fromRef) - if targetHash.IsZero() { - return fmt.Errorf("invalid reference or commit hash: %s", fromRef) - } - // Verify commit exists - _, err = repo.CommitObject(targetHash) - if err != nil { - return fmt.Errorf("failed to resolve reference %q: %w", fromRef, err) - } - } + targetHash, err := resolveReference(repo, fromRef) + if err != nil { + return err } - // Create the branch reference - newRef := plumbing.NewHashReference(plumbing.NewBranchReferenceName(branchName), targetHash) + newRef := plumbing.NewHashReference( + plumbing.NewBranchReferenceName(branchName), + targetHash, + ) err = repo.Storer.SetReference(newRef) if err != nil { - return fmt.Errorf("failed to create branch reference: %w", err) + return fmt.Errorf("failed to set reference: %w", err) } // Push all branches err = p.Push(ctx, directory, RefSpecPushAllBranches) if err != nil { - return fmt.Errorf("failed to push branch: %w", err) + return err } log.Info("Remote branch created successfully") @@ -501,29 +485,14 @@ func (p *GitProvider) CheckReference(ctx context.Context, directory, refName str return nil } - repo, err := git.PlainOpen(directory) + r, err := git.PlainOpen(directory) if err != nil { - return fmt.Errorf("failed to open repository at %q: %w", directory, err) + return fmt.Errorf("failed to open git repository: %w", err) } - // Try to resolve as branch first - _, err = repo.Reference(plumbing.NewBranchReferenceName(refName), false) - if err == nil { - log.Info("Reference exists as branch") - return nil - } - - // Try to resolve as commit - hash := plumbing.NewHash(refName) - if !hash.IsZero() { - _, err = repo.CommitObject(hash) - if err == nil { - log.Info("Reference exists as commit") - return nil - } - } + _, err = resolveReference(r, refName) - return fmt.Errorf("reference %q not found", refName) + return err } // RemoveBranch removes a local branch. @@ -546,55 +515,6 @@ func (p *GitProvider) RemoveBranch(ctx context.Context, directory, branchName st return nil } -// RenameBranch renames a branch. -func (p *GitProvider) RenameBranch(ctx context.Context, directory, oldName, newName string) error { - log := ctrl.LoggerFrom(ctx).WithValues("directory", directory, "oldName", oldName, "newName", newName) - log.Info("Renaming branch") - - repo, err := git.PlainOpen(directory) - if err != nil { - return fmt.Errorf("failed to open repository at %q: %w", directory, err) - } - - // Get the old branch reference - oldRef, err := repo.Reference(plumbing.NewBranchReferenceName(oldName), false) - if err != nil { - return fmt.Errorf("failed to get branch reference: %w", err) - } - - // Create new reference with the same hash - newRef := plumbing.NewHashReference(plumbing.NewBranchReferenceName(newName), oldRef.Hash()) - - err = repo.Storer.SetReference(newRef) - if err != nil { - return fmt.Errorf("failed to create new branch reference: %w", err) - } - - // Checkout the new branch - worktree, err := repo.Worktree() - if err != nil { - return fmt.Errorf("failed to get worktree: %w", err) - } - - err = worktree.Checkout(&git.CheckoutOptions{ - Branch: plumbing.NewBranchReferenceName(newName), - Force: true, - }) - if err != nil { - return fmt.Errorf("failed to checkout new branch: %w", err) - } - - // Remove the old reference - err = repo.Storer.RemoveReference(plumbing.NewBranchReferenceName(oldName)) - if err != nil { - return fmt.Errorf("failed to remove old branch reference: %w", err) - } - - log.Info("Branch renamed successfully") - - return nil -} - // CreateChildBranch creates a new branch from an existing branch. func (p *GitProvider) CreateChildBranch(ctx context.Context, directory, parentBranch, newBranch string) error { log := ctrl.LoggerFrom(ctx).WithValues("directory", directory, "parent", parentBranch, "newBranch", newBranch) @@ -675,7 +595,7 @@ func (p *GitProvider) Fetch(ctx context.Context, directory, branchName string) e } err = repo.FetchContext(ctx, fetchOptions) - if err != nil && err != git.NoErrAlreadyUpToDate { + if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { return fmt.Errorf("failed to fetch: %w", err) } @@ -766,7 +686,7 @@ func (p *GitProvider) CheckoutRemoteBranch(ctx context.Context, directory, branc } err = repo.FetchContext(ctx, fetchOptions) - if err != nil && err != git.NoErrAlreadyUpToDate { + if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { return fmt.Errorf("failed to fetch: %w", err) } @@ -849,3 +769,52 @@ func (p *GitProvider) CreateRemoteTag(ctx context.Context, directory, branchName return nil } + +func branchExists(branchName string, branches storer.ReferenceIter) (bool, error) { + exist := false + + if err := branches.ForEach(func(ref *plumbing.Reference) error { + if ref.Name().Short() == branchName { + exist = true + return storer.ErrStop + } + + return nil + }); err != nil { + return false, fmt.Errorf("failed to iterate branches: %w", err) + } + + return exist, nil +} + +// resolveReference resolves a reference (branch or commit) to a hash. +func resolveReference(r *git.Repository, ref string) (plumbing.Hash, error) { + if ref == "" { + // If no reference specified, use HEAD + ref, err := r.Head() + if err != nil { + return plumbing.ZeroHash, fmt.Errorf("failed to get git HEAD reference: %w", err) + } + + return ref.Hash(), nil + } + + // Try to resolve as a branch first + branchRef, err := r.Reference(plumbing.NewBranchReferenceName(ref), false) + if err == nil { + return branchRef.Hash(), nil + } + + // If not a branch, try to resolve as a commit + commitHash := plumbing.NewHash(ref) + if commitHash.IsZero() { + return plumbing.ZeroHash, fmt.Errorf("invalid reference or commit hash: %s", ref) + } + + _, err = r.CommitObject(commitHash) + if err != nil { + return plumbing.ZeroHash, fmt.Errorf("failed to get commit %s: %w", ref, err) + } + + return commitHash, nil +} diff --git a/pkg/git/v2/provider_test.go b/pkg/git/v2/provider_test.go new file mode 100644 index 00000000..5225614d --- /dev/null +++ b/pkg/git/v2/provider_test.go @@ -0,0 +1,520 @@ +package v2 + +import ( + "context" + "encoding/base64" + "net/http" + "net/http/httptest" + "os" + "path" + "testing" + "time" + + gogit "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/epam/edp-codebase-operator/v2/pkg/platform" +) + +func TestGitProvider_CheckPermissions(t *testing.T) { + user := "user" + pass := "pass" + + config := Config{ + Username: user, + Token: pass, + } + gp := NewGitProvider(config) + + bts, err := base64.StdEncoding.DecodeString(`MDAxZSMgc2VydmljZT1naXQtdXBsb2FkLXBhY2sKMDAwMDAxNTY2ZWNmMGVmMmMyZGZmYjc5NjAzM2U1YTAyMjE5YWY4NmVjNjU4NGU1IEhFQUQAbXVsdGlfYWNrIHRoaW4tcGFjayBzaWRlLWJhbmQgc2lkZS1iYW5kLTY0ayBvZnMtZGVsdGEgc2hhbGxvdyBkZWVwZW4tc2luY2UgZGVlcGVuLW5vdCBkZWVwZW4tcmVsYXRpdmUgbm8tcHJvZ3Jlc3MgaW5jbHVkZS10YWcgbXVsdGlfYWNrX2RldGFpbGVkIGFsbG93LXRpcC1zaGExLWluLXdhbnQgYWxsb3ctcmVhY2hhYmxlLXNoYTEtaW4td2FudCBuby1kb25lIHN5bXJlZj1IRUFEOnJlZnMvaGVhZHMvbWFzdGVyIGZpbHRlciBvYmplY3QtZm9ybWF0PXNoYTEgYWdlbnQ9Z2l0L2dpdGh1Yi1nNzhiNDUyNDEzZThiCjAwM2ZlOGQzZmZhYjU1Mjg5NWMxOWI5ZmNmN2FhMjY0ZDI3N2NkZTMzODgxIHJlZnMvaGVhZHMvYnJhbmNoCjAwM2Y2ZWNmMGVmMmMyZGZmYjc5NjAzM2U1YTAyMjE5YWY4NmVjNjU4NGU1IHJlZnMvaGVhZHMvbWFzdGVyCjAwM2ViOGU0NzFmNThiY2JjYTYzYjA3YmRhMjBlNDI4MTkwNDA5YzJkYjQ3IHJlZnMvcHVsbC8xL2hlYWQKMDAzZTk2MzJmMDI4MzNiMmY5NjEzYWZiNWU3NTY4MjEzMmIwYjIyZTRhMzEgcmVmcy9wdWxsLzIvaGVhZAowMDNmYzM3ZjU4YTEzMGNhNTU1ZTQyZmY5NmEwNzFjYjljY2IzZjQzNzUwNCByZWZzL3B1bGwvMi9tZXJnZQowMDAw`) + require.NoError(t, err) + + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err = w.Write(bts) + assert.NoError(t, err, "failed to write response") + })) + defer s.Close() + + err = gp.CheckPermissions(context.Background(), s.URL) + require.NoError(t, err, "repo must be accessible") +} + +func TestGitProvider_CheckPermissions_NoRefs(t *testing.T) { + user := "user" + pass := "pass" + + config := Config{ + Username: user, + Token: pass, + } + gp := NewGitProvider(config) + + bts, err := base64.StdEncoding.DecodeString(`MDAxZSMgc2VydmljZT1naXQtdXBsb2FkLXBhY2sKMDAwMDAwZGUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIGNhcGFiaWxpdGllc157fQAgaW5jbHVkZS10YWcgbXVsdGlfYWNrX2RldGFpbGVkIG11bHRpX2FjayBvZnMtZGVsdGEgc2lkZS1iYW5kIHNpZGUtYmFuZC02NGsgdGhpbi1wYWNrIG5vLXByb2dyZXNzIHNoYWxsb3cgbm8tZG9uZSBhZ2VudD1KR2l0L3Y1LjkuMC4yMDIwMDkwODA1MDEtci00MS1nNWQ5MjVlY2JiCjAwMDA=`) + require.NoError(t, err) + + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err = w.Write(bts) + assert.NoError(t, err, "failed to write response") + })) + defer s.Close() + + mockLogger := platform.NewLoggerMock() + + // v2 implementation returns nil for empty repos (they are technically accessible, just empty) + // This is different from v1 which logged an error + err = gp.CheckPermissions(ctrl.LoggerInto(context.Background(), mockLogger), s.URL) + require.NoError(t, err, "v2 considers empty repos accessible") +} + +func TestGitProvider_CreateChildBranch(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + initRepo func(t *testing.T) string + parent string + child string + wantErr require.ErrorAssertionFunc + }{ + { + name: "should create child branch successfully", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + // Create initial commit on master branch + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test content") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial commit", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + // Create a parent branch and check it out so it exists as a proper reference + err = w.Checkout(&gogit.CheckoutOptions{ + Branch: plumbing.NewBranchReferenceName("parent-branch"), + Create: true, + }) + require.NoError(t, err) + + return dir + }, + parent: "parent-branch", + child: "child-branch", + wantErr: require.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.CreateChildBranch(context.Background(), dir, tt.parent, tt.child) + tt.wantErr(t, err) + }) + } +} + +func TestGitProvider_RemoveBranch(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + initRepo func(t *testing.T) string + branch string + wantErr require.ErrorAssertionFunc + }{ + { + name: "should remove branch successfully", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + // Create initial commit + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test content") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial commit", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + // Create a new branch + err = w.Checkout(&gogit.CheckoutOptions{ + Branch: plumbing.NewBranchReferenceName("test-branch"), + Create: true, + }) + require.NoError(t, err) + + // Checkout back to master so we can delete test-branch + err = w.Checkout(&gogit.CheckoutOptions{ + Branch: plumbing.NewBranchReferenceName("master"), + }) + require.NoError(t, err) + + return dir + }, + branch: "test-branch", + wantErr: require.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.RemoveBranch(context.Background(), dir, tt.branch) + tt.wantErr(t, err) + }) + } +} + +func TestGitProvider_CommitChanges(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + ops []CommitOps + initRepo func(t *testing.T) string + wantErr require.ErrorAssertionFunc + checkRepo func(t *testing.T, dir string) + }{ + { + name: "should commit changes successfully", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + _, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + _, err = os.Create(path.Join(dir, "config.yaml")) + require.NoError(t, err) + + return dir + }, + wantErr: require.NoError, + checkRepo: func(t *testing.T, dir string) { + r, err := gogit.PlainOpen(dir) + require.NoError(t, err) + + commits, err := r.CommitObjects() + require.NoError(t, err) + + count := 0 + _ = commits.ForEach(func(*object.Commit) error { + count++ + + return nil + }) + + require.Equalf(t, 1, count, "expected 1 commits, got %d", count) + }, + }, + { + name: "skip commit if no changes", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + _, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + return dir + }, + wantErr: require.NoError, + checkRepo: func(t *testing.T, dir string) { + r, err := gogit.PlainOpen(dir) + require.NoError(t, err) + + commits, err := r.CommitObjects() + require.NoError(t, err) + + count := 0 + _ = commits.ForEach(func(*object.Commit) error { + count++ + + return nil + }) + + require.Equalf(t, 0, count, "expected 0 commits, got %d", count) + }, + }, + { + name: "should create empty commit", + ops: []CommitOps{ + CommitAllowEmpty(), + }, + initRepo: func(t *testing.T) string { + dir := t.TempDir() + _, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + return dir + }, + wantErr: require.NoError, + checkRepo: func(t *testing.T, dir string) { + r, err := gogit.PlainOpen(dir) + require.NoError(t, err) + + commits, err := r.CommitObjects() + require.NoError(t, err) + + count := 0 + _ = commits.ForEach(func(*object.Commit) error { + count++ + + return nil + }) + + require.Equalf(t, 1, count, "expected 1 commits, got %d", count) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.Commit(context.Background(), dir, "test commit message", tt.ops...) + tt.wantErr(t, err) + tt.checkRepo(t, dir) + }) + } +} + +func TestGitProvider_AddRemoteLink(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + remoteUrl string + initRepo func(t *testing.T) string + wantErr require.ErrorAssertionFunc + checkRepo func(t *testing.T, dir string) + }{ + { + name: "should add remote link successfully", + remoteUrl: "git@host:32/app.git", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + _, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + return dir + }, + wantErr: require.NoError, + checkRepo: func(t *testing.T, dir string) { + r, err := gogit.PlainOpen(dir) + require.NoError(t, err) + + remote, err := r.Remote("origin") + require.NoError(t, err) + + require.Equal(t, "origin", remote.Config().Name) + require.Len(t, remote.Config().URLs, 1) + require.Equal(t, "git@host:32/app.git", remote.Config().URLs[0]) + }, + }, + { + name: "empty git dir", + remoteUrl: "git@host:32/app.git", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + + return dir + }, + wantErr: require.Error, + checkRepo: func(t *testing.T, dir string) {}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.AddRemoteLink(context.Background(), dir, tt.remoteUrl) + tt.wantErr(t, err) + tt.checkRepo(t, dir) + }) + } +} + +func TestGitProvider_CheckReference(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + initRepo func(t *testing.T) string + from string + wantErr require.ErrorAssertionFunc + }{ + { + name: "should return nil for empty reference", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + _, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + return dir + }, + from: "", + wantErr: require.NoError, + }, + { + name: "should find existing branch reference", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + // Create initial commit + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test content") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial commit", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + // Create and checkout a new branch + err = w.Checkout(&gogit.CheckoutOptions{ + Branch: plumbing.NewBranchReferenceName("test-branch"), + Create: true, + }) + require.NoError(t, err) + + return dir + }, + from: "test-branch", + wantErr: require.NoError, + }, + { + name: "should find existing commit reference", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + // Create initial commit + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test content") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + commit, err := w.Commit("initial commit", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + // Store the commit hash for the test + t.Logf("Created commit with hash: %s", commit.String()) + + return dir + }, + from: "", // Will be set dynamically + wantErr: require.NoError, + }, + { + name: "should return error for non-existent reference", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + _, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + return dir + }, + from: "non-existent", + wantErr: require.Error, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + // For the commit reference test, we need to get the actual commit hash + if tt.name == "should find existing commit reference" { + r, err := gogit.PlainOpen(dir) + require.NoError(t, err) + + ref, err := r.Head() + require.NoError(t, err) + + tt.from = ref.Hash().String() + t.Logf("Using commit hash: %s", tt.from) + } + + err := gp.CheckReference(context.Background(), dir, tt.from) + tt.wantErr(t, err) + }) + } +} diff --git a/pkg/util/url.go b/pkg/util/url.go index 3c85db36..f34caafa 100644 --- a/pkg/util/url.go +++ b/pkg/util/url.go @@ -36,7 +36,7 @@ func AddGitToURL(url string) string { return url } -func GetRepoUrl(c *codebaseApi.Codebase) (string, error) { +func GetRepoUrlForClone(c *codebaseApi.Codebase) (string, error) { log.Info("Setup repo url", "codebase_name", c.Name) if c.Spec.Strategy == codebaseApi.Clone { @@ -46,7 +46,7 @@ func GetRepoUrl(c *codebaseApi.Codebase) (string, error) { log.Info("TriggerType is not clone. Start build url...", logCodebaseNameKey, c.Name) - u := BuildRepoUrl(&c.Spec) + u := BuildTemplateRepoUrl(&c.Spec) log.Info("Repository url has been generated", "url", u, logCodebaseNameKey, c.Name) @@ -61,7 +61,7 @@ func tryGetRepoUrl(spec *codebaseApi.CodebaseSpec) (string, error) { return spec.Repository.Url, nil } -func BuildRepoUrl(spec *codebaseApi.CodebaseSpec) string { +func BuildTemplateRepoUrl(spec *codebaseApi.CodebaseSpec) string { log.Info("Start building repo url", "base url", GithubDomain, "spec", spec) return strings.ToLower( diff --git a/pkg/util/url_test.go b/pkg/util/url_test.go index b79ddf3b..d185d92d 100644 --- a/pkg/util/url_test.go +++ b/pkg/util/url_test.go @@ -48,7 +48,7 @@ func TestBuildRepoUrl(t *testing.T) { Framework: tt.fields.framework, } - got := BuildRepoUrl(spec) + got := BuildTemplateRepoUrl(spec) assert.Equal(t, tt.want, got) }) @@ -158,7 +158,7 @@ func TestGetRepoUrl(t *testing.T) { }, } - got, err := GetRepoUrl(codebase) + got, err := GetRepoUrlForClone(codebase) tt.wantErr(t, err) assert.Equal(t, tt.want, got)