diff --git a/pkg/apis/operator/v1alpha1/types.go b/pkg/apis/operator/v1alpha1/types.go index c591181..a376f65 100644 --- a/pkg/apis/operator/v1alpha1/types.go +++ b/pkg/apis/operator/v1alpha1/types.go @@ -48,9 +48,14 @@ type Git struct { // cloned and the specified tag is checked out. Source string `yaml:"source"` - // Tag of the KUDO operator version. - Tag string `yaml:"tag"` - // Directory where the KUDO operator is defined in the Git repository. Directory string `yaml:"directory"` + + // Tag (or branch) of the KUDO operator version. + Tag string `yaml:"tag,omitempty"` + + // Optional SHA of the KUDO operator version if a branch is used instead of + // a tag. If this isn't set, the latest commit of the referenced branch will + // be used. + SHA string `yaml:"sha,omitempty"` } diff --git a/pkg/internal/apis/operator/encode/convert.go b/pkg/internal/apis/operator/encode/convert.go index 4978c1a..acebba2 100644 --- a/pkg/internal/apis/operator/encode/convert.go +++ b/pkg/internal/apis/operator/encode/convert.go @@ -51,8 +51,9 @@ func convertV1Alpha1Version(in v1alpha1.Version) operator.Version { func convertV1Alpha1Git(in v1alpha1.Git) operator.Git { out := operator.Git{ Source: in.Source, - Tag: in.Tag, Directory: in.Directory, + Tag: in.Tag, + SHA: in.SHA, } return out diff --git a/pkg/internal/apis/operator/types.go b/pkg/internal/apis/operator/types.go index 1e0bb78..21f2743 100644 --- a/pkg/internal/apis/operator/types.go +++ b/pkg/internal/apis/operator/types.go @@ -39,9 +39,14 @@ type Git struct { // cloned and the specified tag is checked out. Source string - // Tag of the KUDO operator version. - Tag string - // Directory where the KUDO operator is defined in the Git repository. Directory string + + // Tag (or branch) of the KUDO operator version. + Tag string + + // Optional SHA of the KUDO operator version if a branch is used instead of + // a tag. If this isn't set, the latest commit of the referenced branch will + // be used. + SHA string } diff --git a/pkg/internal/resolvers/git/git.go b/pkg/internal/resolvers/git/git.go index e91fcbe..bab524b 100644 --- a/pkg/internal/resolvers/git/git.go +++ b/pkg/internal/resolvers/git/git.go @@ -16,17 +16,19 @@ import ( type Resolver struct { URL string Branch string + SHA string OperatorDirectory string // Extracted function to simplify testing. - gitClone func(ctx context.Context, url, branch, tempDir string) error + gitClone func(ctx context.Context, tempDir, url, branch, sha string) error } // NewResolver creates a new Resolver for a Git repository at the specified URL. -func NewResolver(url, branch, operatorDirectory string) Resolver { +func NewResolver(url, branch, sha string, operatorDirectory string) Resolver { return Resolver{ URL: url, Branch: branch, + SHA: sha, OperatorDirectory: operatorDirectory, gitClone: gitClone, @@ -50,9 +52,10 @@ func (r Resolver) Resolve(ctx context.Context) (afero.Fs, resolvers.Remover, err log.WithField("url", r.URL). WithField("branch", r.Branch). + WithField("sha", r.SHA). Info("Cloning Git repository") - if err := r.gitClone(ctx, r.URL, r.Branch, tempDir); err != nil { + if err := r.gitClone(ctx, tempDir, r.URL, r.Branch, r.SHA); err != nil { return nil, nil, err } @@ -65,9 +68,27 @@ func (r Resolver) Resolve(ctx context.Context) (afero.Fs, resolvers.Remover, err return afero.NewBasePathFs(fs, path.Join(tempDir, r.OperatorDirectory)), remover, nil } -func gitClone(ctx context.Context, url, branch, tempDir string) error { +func gitClone(ctx context.Context, tempDir, url, branch, sha string) error { + logger := log.WithField("url", url). + WithField("branch", branch). + WithField("sha", sha) + + if err := runAndLog(ctx, logger, "git", "clone", "--branch", branch, "--single-branch", url, tempDir); err != nil { + return err + } + + if sha != "" { + if err := runAndLog(ctx, logger, "git", "-C", tempDir, "reset", "--hard", sha); err != nil { + return err + } + } + + return nil +} + +func runAndLog(ctx context.Context, logger *log.Entry, name string, args ...string) error { //nolint:gosec - cmd := exec.CommandContext(ctx, "git", "clone", "--branch", branch, "--single-branch", url, tempDir) + cmd := exec.CommandContext(ctx, name, args...) stderr, err := cmd.StderrPipe() if err != nil { @@ -83,9 +104,7 @@ func gitClone(ctx context.Context, url, branch, tempDir string) error { for scanner.Scan() { t := scanner.Text() - log.WithField("url", url). - WithField("branch", branch). - Debug(t) + logger.Debug(t) } if err := cmd.Wait(); err != nil { diff --git a/pkg/internal/resolvers/git/git_test.go b/pkg/internal/resolvers/git/git_test.go index b1c0ef3..3c75cdd 100644 --- a/pkg/internal/resolvers/git/git_test.go +++ b/pkg/internal/resolvers/git/git_test.go @@ -9,8 +9,8 @@ import ( ) func TestResolve(t *testing.T) { - testClone := func(ctx context.Context, url, branch, tempDir string) error { - if url == "example.org" && branch == "test" { + testClone := func(ctx context.Context, tempDir, url, branch, sha string) error { + if url == "example.org" && branch == "test" && sha == "" { return nil } diff --git a/pkg/update/update.go b/pkg/update/update.go index ef303c5..b8129de 100644 --- a/pkg/update/update.go +++ b/pkg/update/update.go @@ -124,7 +124,7 @@ func getResolver(o operator.Operator, version operator.Version) (resolvers.Resol } // TODO: cache git sources to ensure that repositories are only cloned once per source - resolver := git.NewResolver(source.URL, version.Git.Tag, version.Git.Directory) + resolver := git.NewResolver(source.URL, version.Git.Tag, version.Git.SHA, version.Git.Directory) return resolver, nil }