diff --git a/go.mod b/go.mod index ea2b4da..965d201 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,9 @@ module github.com/cert-manager/klone -go 1.21.1 +go 1.23 require ( + github.com/cenkalti/backoff/v5 v5.0.1 github.com/rogpeppe/go-internal v1.11.0 github.com/spf13/cobra v1.7.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index ccc1b40..83bdfef 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/cenkalti/backoff/v5 v5.0.1 h1:kGZdCHH1+eW+Yd0wftimjMuhg9zidDvNF5aGdnkkb+U= +github.com/cenkalti/backoff/v5 v5.0.1/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= diff --git a/pkg/cache/clone.go b/pkg/cache/clone.go index d617d96..31cc8ef 100644 --- a/pkg/cache/clone.go +++ b/pkg/cache/clone.go @@ -18,6 +18,7 @@ func calculateCacheKey(src mod.KloneSource) string { } func getCacheDir() (string, error) { + // TODO: add centralized config management defining env vars + maybe a global config file for klone if cacheDir := os.Getenv("KLONE_CACHE_DIR"); cacheDir != "" { return filepath.Abs(filepath.Clean(cacheDir)) } diff --git a/pkg/download/git/clone.go b/pkg/download/git/clone.go index 819c86a..4e0d046 100644 --- a/pkg/download/git/clone.go +++ b/pkg/download/git/clone.go @@ -8,6 +8,10 @@ import ( "os" "os/exec" "path/filepath" + "strconv" + "time" + + "github.com/cenkalti/backoff/v5" "github.com/cert-manager/klone/pkg/mod" ) @@ -24,26 +28,55 @@ func Get(ctx context.Context, targetPath string, src mod.KloneSource) (string, e return filepath.Join(targetPath, src.RepoPath), nil } +const gitRetryDelay = 5 * time.Second + func runGitCmd(ctx context.Context, root string, stdout io.Writer, stderr io.Writer, args ...string) error { - cmd := exec.CommandContext(ctx, "git", args...) + do := func() (struct{}, error) { + // dummy return value to match the interface of backoff.Operation + ret := struct{}{} - cmd.Dir = root - cmd.Env = append(os.Environ(), cmd.Env...) - // Disable Git terminal prompts in case we're running with a tty - cmd.Env = append(cmd.Env, "GIT_TERMINAL_PROMPT=false") + cmd := exec.CommandContext(ctx, "git", args...) - cmd.Stdout = stdout - cmd.Stderr = stderr + cmd.Dir = root + cmd.Env = append(os.Environ(), cmd.Env...) + // Disable Git terminal prompts in case we're running with a tty + cmd.Env = append(cmd.Env, "GIT_TERMINAL_PROMPT=false") - if err := cmd.Start(); err != nil { - return err + cmd.Stdout = stdout + cmd.Stderr = stderr + + if err := cmd.Start(); err != nil { + return ret, err + } + + if err := cmd.Wait(); err != nil { + return ret, fmt.Errorf("git command failed: %v", err) + } + + return ret, nil } - if err := cmd.Wait(); err != nil { - return fmt.Errorf("git command failed: %v", err) + _, err := backoff.Retry(ctx, do, backoff.WithMaxTries(getRetryCount()), backoff.WithBackOff(backoff.NewConstantBackOff(gitRetryDelay))) + return err +} + +func getRetryCount() uint { + // TODO: add centralized config management defining env vars + maybe a global config file for klone + retryCountRaw := os.Getenv("KLONE_GIT_RETRY_ATTEMPTS") + if retryCountRaw == "" { + return 1 } - return nil + retryCount, err := strconv.Atoi(retryCountRaw) + if err != nil { + return 1 + } + + if retryCount <= 0 { + return 1 + } + + return uint(retryCount) } func sparseCheckout(ctx context.Context, root string, repoURL string, branch string, patterns []string) error {