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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions controllers/codebasebranch/chain/check_reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,27 +75,17 @@ func (c CheckReferenceExists) ServeRequest(ctx context.Context, codebaseBranch *
return c.processErr(codebaseBranch, fmt.Errorf("failed to get secret %s: %w", gitServer.Spec.NameSshKeySecret, err))
}

// Create git provider using factory
g := c.GitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(gitServer, secret))

workDir := GetCodebaseBranchWorkingDirectory(codebaseBranch)
if !DirectoryExistsNotEmpty(workDir) {
repoGitUrl := util.GetProjectGitUrl(gitServer, secret, codebase.Spec.GetProjectID())
repoGitUrl := util.GetProjectGitUrl(gitServer, secret, codebase.Spec.GetProjectID())

if err := g.Clone(ctx, repoGitUrl, workDir); err != nil {
return c.processErr(codebaseBranch, fmt.Errorf("failed to clone repository: %w", err))
}
}

err := g.CheckReference(ctx, workDir, codebaseBranch.Spec.FromCommit)
if err != nil {
if _, err := g.ResolveRemoteReference(ctx, repoGitUrl, codebaseBranch.Spec.FromCommit); err != nil {
return c.processErr(codebaseBranch, fmt.Errorf("reference %s doesn't exist: %w", codebaseBranch.Spec.FromCommit, err))
}

return c.next(ctx, codebaseBranch)
}

// next is a method for serving next chain element.
func (c CheckReferenceExists) next(ctx context.Context, codebaseBranch *codebaseApi.CodebaseBranch) error {
err := handler.NextServeOrNil(ctx, c.Next, codebaseBranch)
if err != nil {
Expand All @@ -105,7 +95,6 @@ func (c CheckReferenceExists) next(ctx context.Context, codebaseBranch *codebase
return nil
}

// processErr is a method for processing error in chain.
func (c CheckReferenceExists) processErr(codebaseBranch *codebaseApi.CodebaseBranch, err error) error {
if err == nil {
return nil
Expand Down
33 changes: 6 additions & 27 deletions controllers/codebasebranch/chain/check_reference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,18 +77,11 @@ func TestCheckReferenceExists_ServeRequest(t *testing.T) {
gitClient: func() gitproviderv2.Git {
mGit := gitServerMocks.NewMockGit(t)
mGit.On(
"Clone",
"ResolveRemoteReference",
testifymock.Anything,
testifymock.Anything,
testifymock.Anything,
testifymock.Anything,
).Return(nil)
mGit.On(
"CheckReference",
testifymock.Anything,
testifymock.Anything,
testifymock.Anything,
).Return(nil)
).Return("bfba920bd3bdebc9ae1c4475d70391152645b2a4", nil)

return mGit
},
Expand Down Expand Up @@ -136,18 +129,11 @@ func TestCheckReferenceExists_ServeRequest(t *testing.T) {
gitClient: func() gitproviderv2.Git {
mGit := gitServerMocks.NewMockGit(t)
mGit.On(
"Clone",
testifymock.Anything,
"ResolveRemoteReference",
testifymock.Anything,
testifymock.Anything,
testifymock.Anything,
).Return(nil)
mGit.On(
"CheckReference",
testifymock.Anything,
testifymock.Anything,
testifymock.Anything,
).Return(nil)
).Return("bfba920bd3bdebc9ae1c4475d70391152645b2a4", nil)

return mGit
},
Expand Down Expand Up @@ -195,18 +181,11 @@ func TestCheckReferenceExists_ServeRequest(t *testing.T) {
gitClient: func() gitproviderv2.Git {
mGit := gitServerMocks.NewMockGit(t)
mGit.On(
"Clone",
testifymock.Anything,
testifymock.Anything,
testifymock.Anything,
testifymock.Anything,
).Return(nil)
mGit.On(
"CheckReference",
"ResolveRemoteReference",
testifymock.Anything,
testifymock.Anything,
testifymock.Anything,
).Return(errors.New("reference not found"))
).Return("", errors.New("reference not found"))

return mGit
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"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/handler"
"github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/service"
gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git"
Expand Down Expand Up @@ -88,41 +87,20 @@ func (h PutBranchInGit) ServeRequest(ctx context.Context, branch *codebaseApi.Co
return err
}

// Create git provider using factory
gitProvider := h.GitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(gitServer, secret))

wd := chain.GetCodebaseBranchWorkingDirectory(branch)
if !checkDirectory(wd) {
repoGitUrl := util.GetProjectGitUrl(gitServer, secret, codebase.Spec.GetProjectID())
repoGitUrl := util.GetProjectGitUrl(gitServer, secret, codebase.Spec.GetProjectID())

if err := gitProvider.Clone(ctx, repoGitUrl, wd); err != nil {
putGitBranchSetFailedFields(branch, err.Error())

return fmt.Errorf("failed to clone repository: %w", err)
}
}

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 = gitProvider.CheckoutRemoteBranch(ctx, wd, codebase.Spec.DefaultBranch); err != nil {
return fmt.Errorf("failed to checkout to default branch %s: %w", codebase.Spec.DefaultBranch, err)
}
// An empty FromCommit means the branch starts from the tip of the default branch.
fromRef := branch.Spec.FromCommit
if fromRef == "" {
fromRef = codebase.Spec.DefaultBranch
}

err = gitProvider.CreateRemoteBranch(ctx, wd, branch.Spec.BranchName, branch.Spec.FromCommit)
err := gitProvider.CreateRemoteBranchViaRefUpdate(ctx, repoGitUrl, branch.Spec.BranchName, fromRef)
if err != nil {
putGitBranchSetFailedFields(branch, err.Error())

// We need to remove work directory if branch creation failed(push error).
// Otherwise, the next time the branch creation will be skipped because local branch already exists.
if err = util.RemoveDirectory(wd); err != nil {
log.Error(err, "failed to remove directory", "path", wd)
}

return fmt.Errorf("failed to create remote branch: %w", err)
}

Expand Down Expand Up @@ -189,7 +167,3 @@ func putGitBranchSetFailedFields(cb *codebaseApi.CodebaseBranch, message string)
Conditions: cb.Status.Conditions,
}
}

func checkDirectory(path string) bool {
return util.DoesDirectoryExist(path) && !util.IsDirectoryEmpty(path)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/fake"

codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1"
"github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/chain"
"github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/service"
gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git"
gitServerMocks "github.com/epam/edp-codebase-operator/v2/pkg/git/mocks"
Expand Down Expand Up @@ -94,25 +93,7 @@ func TestPutBranchInGit_ShouldBeExecutedSuccessfullyWithDefaultVersioning(t *tes
mGit := gitServerMocks.NewMockGit(t)

mGit.On(
"Clone",
testifymock.Anything,
testifymock.Anything,
testifymock.Anything,
testifymock.Anything,
).Return(nil)
mGit.On(
"GetCurrentBranchName",
testifymock.Anything,
testifymock.Anything,
).Return("default-branch", nil)
mGit.On(
"CheckoutRemoteBranch",
testifymock.Anything,
testifymock.Anything,
c.Spec.DefaultBranch,
).Return(nil)
mGit.On(
"CreateRemoteBranch",
"CreateRemoteBranchViaRefUpdate",
testifymock.Anything,
testifymock.Anything,
cb.Spec.BranchName,
Expand All @@ -129,94 +110,6 @@ func TestPutBranchInGit_ShouldBeExecutedSuccessfullyWithDefaultVersioning(t *tes
assert.NoError(t, err)
}

func TestPutBranchInGit_ShouldFailgetCurrentbranch(t *testing.T) {
c := &codebaseApi.Codebase{
ObjectMeta: metav1.ObjectMeta{
Name: "test-app",
Namespace: "default",
},
Spec: codebaseApi.CodebaseSpec{
GitServer: "gitserver",
GitUrlPath: "/test-app",
},
Status: codebaseApi.CodebaseStatus{
Available: true,
},
}

gitUser := "git-user"
gs := &codebaseApi.GitServer{
ObjectMeta: metav1.ObjectMeta{
Name: "gitserver",
Namespace: "default",
},
Spec: codebaseApi.GitServerSpec{
NameSshKeySecret: "secret",
GitHost: "git-host",
SshPort: 22,
GitUser: gitUser,
},
}

sshKey := "fake"
s := &coreV1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "secret",
Namespace: "default",
},
Data: map[string][]byte{
util.PrivateSShKeyName: []byte(sshKey),
},
}

cb := &codebaseApi.CodebaseBranch{
ObjectMeta: metav1.ObjectMeta{
Name: "feature-branch",
Namespace: "default",
},
Spec: codebaseApi.CodebaseBranchSpec{
CodebaseName: "test-app",
BranchName: "feature-branch",
FromCommit: "commitsha",
},
}

scheme := runtime.NewScheme()
require.NoError(t, codebaseApi.AddToScheme(scheme))
require.NoError(t, coreV1.AddToScheme(scheme))

fakeCl := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(c, gs, cb, s).
WithStatusSubresource(cb).
Build()

mGit := gitServerMocks.NewMockGit(t)

mGit.On(
"Clone",
testifymock.Anything,
testifymock.Anything,
testifymock.Anything,
testifymock.Anything,
).Return(nil)
mGit.On(
"GetCurrentBranchName",
testifymock.Anything,
testifymock.Anything,
).Return("", errors.New("failed to get current branch"))

err := PutBranchInGit{
Client: fakeCl,
GitProviderFactory: func(cfg gitproviderv2.Config) gitproviderv2.Git {
return mGit
},
}.ServeRequest(ctrl.LoggerInto(context.Background(), logr.Discard()), cb)

require.Error(t, err)
assert.Contains(t, err.Error(), "failed to get current branch")
}

func TestPutBranchInGit_ShouldFailCreateRemoteBranch(t *testing.T) {
t.Setenv(util.WorkDirEnv, t.TempDir())

Expand Down Expand Up @@ -283,21 +176,7 @@ func TestPutBranchInGit_ShouldFailCreateRemoteBranch(t *testing.T) {
mGit := gitServerMocks.NewMockGit(t)

mGit.On(
"Clone",
testifymock.Anything,
testifymock.Anything,
testifymock.Anything,
testifymock.Anything,
).Return(nil)

mGit.On(
"GetCurrentBranchName",
testifymock.Anything,
testifymock.Anything,
).Return("main", nil)

mGit.On(
"CreateRemoteBranch",
"CreateRemoteBranchViaRefUpdate",
testifymock.Anything,
testifymock.Anything,
fakeName,
Expand Down Expand Up @@ -453,16 +332,8 @@ func TestPutBranchInGit_ShouldBeExecutedSuccessfullyWithEdpVersioning(t *testing

mGit := gitServerMocks.NewMockGit(t)

wd := chain.GetCodebaseBranchWorkingDirectory(cb)

mGit.On("Clone", testifymock.Anything, testifymock.Anything, wd).
Return(nil)
mGit.On(
"GetCurrentBranchName",
testifymock.Anything,
wd,
).Return("main", nil)
mGit.On("CreateRemoteBranch", testifymock.Anything, wd, fakeName, "").Return(nil)
// Empty FromCommit must fall back to the codebase default branch.
mGit.On("CreateRemoteBranchViaRefUpdate", testifymock.Anything, testifymock.Anything, fakeName, "main").Return(nil)

err := PutBranchInGit{
Client: fakeCl,
Expand Down
5 changes: 0 additions & 5 deletions controllers/codebasebranch/chain/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ func HasNewVersion(codebaseBranch *codebaseApi.CodebaseBranch) (bool, error) {
return !slices.Contains(codebaseBranch.Status.VersionHistory, *codebaseBranch.Spec.Version), nil
}

// DirectoryExistsNotEmpty checks if directory exists and not empty.
func DirectoryExistsNotEmpty(dirPath string) bool {
return util.DoesDirectoryExist(dirPath) && !util.IsDirectoryEmpty(dirPath)
}

func GetCodebaseBranchWorkingDirectory(codebaseBranch *codebaseApi.CodebaseBranch) string {
return path.Join(
util.GetWorkDir(codebaseBranch.Spec.CodebaseName, codebaseBranch.Namespace),
Expand Down
15 changes: 9 additions & 6 deletions pkg/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ type Git interface {
// If remote is true, fetches from remote first and only creates local branch if it doesn't exist remotely.
Checkout(ctx context.Context, directory, branchName string, remote bool) error

// CreateRemoteBranch creates a new branch from a reference and pushes it to remote.
// fromRef: branch name or commit hash to create from (empty string means HEAD).
CreateRemoteBranch(ctx context.Context, directory, branchName, fromRef string) error

// GetCurrentBranchName returns the name of the current branch.
GetCurrentBranchName(ctx context.Context, directory string) (string, error)

Expand All @@ -33,8 +29,15 @@ type Git interface {
// without cloning it (equivalent to git ls-remote --heads).
ListRemoteBranches(ctx context.Context, repoURL string) ([]string, error)

// CheckReference checks if a reference (branch or commit) exists in the repository.
CheckReference(ctx context.Context, directory, refName string) error
// ResolveRemoteReference resolves a reference (branch, tag, commit hash, or empty for HEAD)
// against the remote repository using only the reference advertisement, without cloning.
// Returns the resolved commit hash or ErrReferenceNotFound.
ResolveRemoteReference(ctx context.Context, repoURL, ref string) (string, error)

// CreateRemoteBranchViaRefUpdate creates a branch on the remote pointing at fromRef
// (branch, tag, or commit hash; must not be empty) without cloning, by sending a
// reference update with an empty packfile. Skips creation if the branch already exists.
CreateRemoteBranchViaRefUpdate(ctx context.Context, repoURL, branchName, fromRef string) error

// RemoveBranch removes a local branch.
RemoveBranch(ctx context.Context, directory, branchName string) error
Expand Down
Loading
Loading