Skip to content

Commit d6b0bd2

Browse files
committed
refactor: Project onboarding workflow (#231)
- Refactored `put_project.go` to improve code maintainability - Extracted error handling into reusable `handleError` helper method - Consolidated git provider initialization throughout the code - Simplified push operation to use single call with multiple refspecs - Improved error messages with more context - Rewrote tests in `put_project_test.go` with better coverage - Split tests into separate files for third-party Git providers and Gerrit - Added comprehensive test cases for success and error scenarios - Reduced test complexity and improved maintainability - Enhanced GitServer API documentation - Added detailed description for `nameSshKeySecret` field - Documented required and optional secret keys for each Git provider
1 parent a368e92 commit d6b0bd2

File tree

7 files changed

+1007
-1267
lines changed

7 files changed

+1007
-1267
lines changed

api/v1/git_server_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ type GitServerSpec struct {
2424

2525
SshPort int32 `json:"sshPort"`
2626

27+
// NameSshKeySecret is the name of the Kubernetes secret containing Git repository credentials.
28+
// Required keys:
29+
// - token: Git provider access token (required)
30+
// Optional keys:
31+
// - id_rsa: SSH private key for Git operations over SSH
32+
// - secretString: Webhook secret for validating webhook requests
33+
// - username: Git username to override the default GitUser
34+
// For Gerrit provider, only id_rsa key is required and used.
35+
// +kubebuilder:example:=my-git-credentials
36+
// +required
2737
NameSshKeySecret string `json:"nameSshKeySecret"`
2838

2939
// GitProvider is a git provider type. It can be gerrit, github or gitlab. Default value is gerrit.

config/crd/bases/v2.edp.epam.com_gitservers.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ spec:
7474
format: int32
7575
type: integer
7676
nameSshKeySecret:
77+
description: |-
78+
NameSshKeySecret is the name of the Kubernetes secret containing Git repository credentials.
79+
Required keys:
80+
- token: Git provider access token (required)
81+
Optional keys:
82+
- id_rsa: SSH private key for Git operations over SSH
83+
- secretString: Webhook secret for validating webhook requests
84+
- username: Git username to override the default GitUser
85+
For Gerrit provider, only id_rsa key is required and used.
86+
example: my-git-credentials
7787
type: string
7888
skipWebhookSSLVerification:
7989
description: SkipWebhookSSLVerification is a flag to skip webhook

controllers/codebase/service/chain/put_project.go

Lines changed: 45 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"os"
8+
"path/filepath"
89
"slices"
910
"strconv"
1011

@@ -25,6 +26,7 @@ type PutProject struct {
2526
gerritClient gerrit.Client
2627
gitApiProjectProvider func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error)
2728
gitProviderFactory gitproviderv2.GitProviderFactory
29+
gitProviderNoAuth gitproviderv2.Git
2830
}
2931

3032
var (
@@ -35,14 +37,15 @@ var (
3537
func NewPutProject(
3638
c client.Client,
3739
gerritProvider gerrit.Client,
38-
gitProjectProvider func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error),
40+
gitApiProjectProvider func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error),
3941
gitProviderFactory gitproviderv2.GitProviderFactory,
4042
) *PutProject {
4143
return &PutProject{
4244
k8sClient: c,
4345
gerritClient: gerritProvider,
44-
gitApiProjectProvider: gitProjectProvider,
46+
gitApiProjectProvider: gitApiProjectProvider,
4547
gitProviderFactory: gitProviderFactory,
48+
gitProviderNoAuth: gitProviderFactory(gitproviderv2.Config{}),
4649
}
4750
}
4851

@@ -59,37 +62,26 @@ func (h *PutProject) ServeRequest(ctx context.Context, codebase *codebaseApi.Cod
5962

6063
err := setIntermediateSuccessFields(ctx, h.k8sClient, codebase, codebaseApi.RepositoryProvisioning)
6164
if err != nil {
62-
return fmt.Errorf("failed to update Codebase %v status: %w", codebase.Name, err)
65+
return fmt.Errorf("failed to update codebase %s status: %w", codebase.Name, err)
6366
}
6467

6568
repoContext, err := GetGitRepositoryContext(ctx, h.k8sClient, codebase)
6669
if err != nil {
67-
setFailedFields(codebase, codebaseApi.RepositoryProvisioning, err.Error())
68-
69-
return fmt.Errorf("failed to get git repository context: %w", err)
70-
}
71-
72-
if err = util.CreateDirectory(repoContext.WorkDir); err != nil {
73-
setFailedFields(codebase, codebaseApi.RepositoryProvisioning, err.Error())
74-
75-
return fmt.Errorf("failed to create dir %q: %w", repoContext.WorkDir, err)
70+
return h.handleError(codebase, err, "failed to get git repository context")
7671
}
7772

7873
err = h.initialProjectProvisioning(ctx, codebase, repoContext)
7974
if err != nil {
80-
setFailedFields(codebase, codebaseApi.RepositoryProvisioning, err.Error())
81-
return fmt.Errorf("failed to perform initial provisioning of codebase %v: %w", codebase.Name, err)
75+
return h.handleError(codebase, err, fmt.Sprintf("failed to perform initial provisioning of codebase %s", codebase.Name))
8276
}
8377

8478
if err = h.checkoutBranch(ctrl.LoggerInto(ctx, log), codebase, repoContext); err != nil {
85-
setFailedFields(codebase, codebaseApi.RepositoryProvisioning, err.Error())
86-
return err
79+
return h.handleError(codebase, err, "failed to checkout branch")
8780
}
8881

8982
err = h.createProject(ctrl.LoggerInto(ctx, log), codebase, repoContext)
9083
if err != nil {
91-
setFailedFields(codebase, codebaseApi.RepositoryProvisioning, err.Error())
92-
return fmt.Errorf("failed to create project: %w", err)
84+
return h.handleError(codebase, err, "failed to create project")
9385
}
9486

9587
if err = updateGitStatusWithPatch(ctx, h.k8sClient, codebase, codebaseApi.RepositoryProvisioning, util.ProjectPushedStatus); err != nil {
@@ -117,25 +109,29 @@ func (*PutProject) skip(ctx context.Context, codebase *codebaseApi.Codebase) boo
117109
return false
118110
}
119111

112+
// handleError sets failed fields on codebase and returns formatted error.
113+
func (*PutProject) handleError(codebase *codebaseApi.Codebase, err error, message string) error {
114+
setFailedFields(codebase, codebaseApi.RepositoryProvisioning, err.Error())
115+
return fmt.Errorf("%s: %w", message, err)
116+
}
117+
120118
func (h *PutProject) createProject(
121119
ctx context.Context,
122120
codebase *codebaseApi.Codebase,
123121
repoContext *GitRepositoryContext,
124122
) error {
125-
gitProvider := h.gitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(repoContext.GitServer, repoContext.GitServerSecret))
126-
127123
if repoContext.GitServer.Spec.GitProvider == codebaseApi.GitProviderGerrit {
128124
err := h.createGerritProject(ctx, repoContext.GitServer, repoContext.PrivateSSHKey, codebase.Spec.GetProjectID())
129125
if err != nil {
130-
return fmt.Errorf("failed to create project in Gerrit for codebase %v: %w", codebase.Name, err)
126+
return fmt.Errorf("failed to create project in Gerrit for codebase %s: %w", codebase.Name, err)
131127
}
132128
} else {
133129
if err := h.createGitThirdPartyProject(ctx, repoContext.GitServer, repoContext.Token, codebase); err != nil {
134130
return err
135131
}
136132
}
137133

138-
err := h.pushProject(ctx, gitProvider, codebase.Spec.GetProjectID(), repoContext)
134+
err := h.pushProject(ctx, codebase.Spec.GetProjectID(), repoContext)
139135
if err != nil {
140136
return err
141137
}
@@ -156,7 +152,7 @@ func (h *PutProject) replaceDefaultBranch(ctx context.Context, g gitproviderv2.G
156152
log.Info("Removing default branch")
157153

158154
if err := g.RemoveBranch(ctx, directory, defaultBranchName); err != nil {
159-
return fmt.Errorf("failed to remove master branch: %w", err)
155+
return fmt.Errorf("failed to remove default branch %s: %w", defaultBranchName, err)
160156
}
161157

162158
log.Info("Creating new branch")
@@ -170,30 +166,26 @@ func (h *PutProject) replaceDefaultBranch(ctx context.Context, g gitproviderv2.G
170166
return nil
171167
}
172168

173-
func (h *PutProject) pushProject(ctx context.Context, g gitproviderv2.Git, projectName string, repoContext *GitRepositoryContext) error {
169+
func (h *PutProject) pushProject(ctx context.Context, projectName string, repoContext *GitRepositoryContext) error {
174170
log := ctrl.LoggerFrom(ctx).WithValues("gitProvider", repoContext.GitServer.Spec.GitProvider)
175171

176172
log.Info("Start pushing project")
177173
log.Info("Start adding remote link")
178174

179-
if err := g.AddRemoteLink(
175+
gitProvider := h.gitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(repoContext.GitServer, repoContext.GitServerSecret))
176+
177+
if err := gitProvider.AddRemoteLink(
180178
ctx,
181179
repoContext.WorkDir,
182180
util.GetProjectGitUrl(repoContext.GitServer, repoContext.GitServerSecret, projectName),
183181
); err != nil {
184182
return fmt.Errorf("failed to add remote link: %w", err)
185183
}
186184

187-
log.Info("Start pushing changes into git")
188-
189-
if err := g.Push(ctx, repoContext.WorkDir, gitproviderv2.RefSpecPushAllBranches); err != nil {
190-
return fmt.Errorf("failed to push changes: %w", err)
191-
}
192-
193-
log.Info("Start pushing tags into git")
185+
log.Info("Start pushing changes and tags into git")
194186

195-
if err := g.Push(ctx, repoContext.WorkDir, gitproviderv2.RefSpecPushAllTags); err != nil {
196-
return fmt.Errorf("failed to push changes into git: %w", err)
187+
if err := gitProvider.Push(ctx, repoContext.WorkDir, gitproviderv2.RefSpecPushAllBranches, gitproviderv2.RefSpecPushAllTags); err != nil {
188+
return fmt.Errorf("failed to push changes and tags into git: %w", err)
197189
}
198190

199191
log.Info("Project has been pushed successfully")
@@ -212,7 +204,7 @@ func (h *PutProject) createGerritProject(ctx context.Context, gitServer *codebas
212204
}
213205

214206
if projectExist {
215-
log.Info("Skip creating project in Gerrit, project already exist")
207+
log.Info("Skip creating project in Gerrit, project already exists")
216208
return nil
217209
}
218210

@@ -236,18 +228,17 @@ func (h *PutProject) checkoutBranch(ctx context.Context, codebase *codebaseApi.C
236228

237229
gitprovider := h.gitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(repoContext.GitServer, repoContext.GitServerSecret))
238230

239-
// TODO: branchToCopyInDefaultBranch is never used. Check if we can remove it.
240231
if codebase.Spec.BranchToCopyInDefaultBranch != "" && codebase.Spec.DefaultBranch != codebase.Spec.BranchToCopyInDefaultBranch {
241232
log.Info("Start checkout branch to copy")
242233

243234
if err := CheckoutBranch(ctx, codebase.Spec.BranchToCopyInDefaultBranch, repoContext, codebase, h.k8sClient, h.gitProviderFactory); err != nil {
244-
return fmt.Errorf("failed to checkout default branch %s: %w", codebase.Spec.DefaultBranch, err)
235+
return fmt.Errorf("failed to checkout branch to copy %s: %w", codebase.Spec.BranchToCopyInDefaultBranch, err)
245236
}
246237

247238
log.Info("Start replace default branch")
248239

249240
if err := h.replaceDefaultBranch(ctx, gitprovider, repoContext.WorkDir, codebase.Spec.DefaultBranch, codebase.Spec.BranchToCopyInDefaultBranch); err != nil {
250-
return fmt.Errorf("failed to replace master: %w", err)
241+
return fmt.Errorf("failed to replace default branch %s with %s: %w", codebase.Spec.DefaultBranch, codebase.Spec.BranchToCopyInDefaultBranch, err)
251242
}
252243

253244
return nil
@@ -270,7 +261,6 @@ func (h *PutProject) createGitThirdPartyProject(
270261
gitProviderToken string,
271262
codebase *codebaseApi.Codebase,
272263
) error {
273-
projectName := codebase.Spec.GetProjectID()
274264
log := ctrl.LoggerFrom(ctx).WithValues("gitProvider", gitServer.Spec.GitProvider)
275265

276266
log.Info("Start creating project in git provider")
@@ -280,6 +270,8 @@ func (h *PutProject) createGitThirdPartyProject(
280270
return fmt.Errorf("failed to create git provider: %w", err)
281271
}
282272

273+
projectName := codebase.Spec.GetProjectID()
274+
283275
projectExists, err := gitProvider.ProjectExists(
284276
ctx,
285277
gitprovider.GetGitProviderAPIURL(gitServer),
@@ -291,7 +283,7 @@ func (h *PutProject) createGitThirdPartyProject(
291283
}
292284

293285
if projectExists {
294-
log.Info("Skip creating project in git provider, project already exist")
286+
log.Info("Skip creating project in git provider, project already exists")
295287

296288
return nil
297289
}
@@ -320,8 +312,7 @@ func (h *PutProject) setDefaultBranch(
320312
gitProviderToken,
321313
privateSSHKey string,
322314
) error {
323-
log := ctrl.LoggerFrom(ctx).
324-
WithValues("gitProvider", gitServer.Spec.GitProvider)
315+
log := ctrl.LoggerFrom(ctx).WithValues("gitProvider", gitServer.Spec.GitProvider)
325316

326317
log.Info("Start setting default branch", "defaultBranch", codebase.Spec.DefaultBranch)
327318

@@ -391,7 +382,7 @@ func (h *PutProject) tryToCloneRepo(
391382

392383
log.Info("Start cloning repository")
393384

394-
if util.DoesDirectoryExist(repoContext.WorkDir + "/.git") {
385+
if util.DoesDirectoryExist(filepath.Join(repoContext.WorkDir, ".git")) {
395386
log.Info("Repository already exists")
396387

397388
return nil
@@ -419,18 +410,16 @@ func (h *PutProject) squashCommits(ctx context.Context, workDir string, strategy
419410

420411
log.Info("Start squashing commits")
421412

422-
err := os.RemoveAll(workDir + "/.git")
413+
err := os.RemoveAll(filepath.Join(workDir, ".git"))
423414
if err != nil {
424415
return fmt.Errorf("failed to remove .git folder: %w", err)
425416
}
426417

427-
gitProvider := h.gitProviderFactory(gitproviderv2.Config{})
428-
429-
if err := gitProvider.Init(ctx, workDir); err != nil {
418+
if err := h.gitProviderNoAuth.Init(ctx, workDir); err != nil {
430419
return fmt.Errorf("failed to create git repository: %w", err)
431420
}
432421

433-
if err := gitProvider.Commit(ctx, workDir, "Initial commit"); err != nil {
422+
if err := h.gitProviderNoAuth.Commit(ctx, workDir, "Initial commit"); err != nil {
434423
return fmt.Errorf("failed to commit all default content: %w", err)
435424
}
436425

@@ -440,6 +429,10 @@ func (h *PutProject) squashCommits(ctx context.Context, workDir string, strategy
440429
}
441430

442431
func (h *PutProject) initialProjectProvisioning(ctx context.Context, codebase *codebaseApi.Codebase, repoContext *GitRepositoryContext) error {
432+
if err := util.CreateDirectory(repoContext.WorkDir); err != nil {
433+
return h.handleError(codebase, err, fmt.Sprintf("failed to create dir %q", repoContext.WorkDir))
434+
}
435+
443436
if codebase.Spec.EmptyProject {
444437
return h.emptyProjectProvisioning(ctx, repoContext)
445438
}
@@ -452,15 +445,13 @@ func (h *PutProject) emptyProjectProvisioning(ctx context.Context, repoContext *
452445

453446
log.Info("Initialing empty git repository")
454447

455-
gitProvider := h.gitProviderFactory(gitproviderv2.Config{})
456-
457-
if err := gitProvider.Init(ctx, repoContext.WorkDir); err != nil {
448+
if err := h.gitProviderNoAuth.Init(ctx, repoContext.WorkDir); err != nil {
458449
return fmt.Errorf("failed to create empty git repository: %w", err)
459450
}
460451

461452
log.Info("Making initial commit")
462453

463-
if err := gitProvider.Commit(ctx, repoContext.WorkDir, "Initial commit", gitproviderv2.CommitAllowEmpty()); err != nil {
454+
if err := h.gitProviderNoAuth.Commit(ctx, repoContext.WorkDir, "Initial commit", gitproviderv2.CommitAllowEmpty()); err != nil {
464455
return fmt.Errorf("failed to create Initial commit: %w", err)
465456
}
466457

@@ -477,23 +468,12 @@ func (h *PutProject) notEmptyProjectProvisioning(ctx context.Context, codebase *
477468
return fmt.Errorf("failed to build repo url: %w", err)
478469
}
479470

480-
repu, repp, exists, err := codebaseutil.GetRepositoryCredentialsIfExists(ctx, codebase, h.k8sClient)
471+
repoUsername, repoPassword, _, err := codebaseutil.GetRepositoryCredentialsIfExists(ctx, codebase, h.k8sClient)
481472
if err != nil {
482473
return fmt.Errorf("failed to get repository credentials for project provisioning: %w", err)
483474
}
484475

485-
if exists {
486-
cloneGitProvider := h.gitProviderFactory(gitproviderv2.Config{
487-
Username: repu,
488-
Token: repp,
489-
})
490-
491-
if err := cloneGitProvider.CheckPermissions(ctx, repoUrl); err != nil {
492-
return fmt.Errorf("failed to get access to the repository %s for user %s: %w", repoUrl, repu, err)
493-
}
494-
}
495-
496-
if err = h.tryToCloneRepo(ctx, repoUrl, repu, repp, repoContext); err != nil {
476+
if err = h.tryToCloneRepo(ctx, repoUrl, repoUsername, repoPassword, repoContext); err != nil {
497477
return fmt.Errorf("failed to clone template project: %w", err)
498478
}
499479

0 commit comments

Comments
 (0)