diff --git a/internal/controller/imageupdateautomation_controller.go b/internal/controller/imageupdateautomation_controller.go index c44c810..d3bbc0a 100644 --- a/internal/controller/imageupdateautomation_controller.go +++ b/internal/controller/imageupdateautomation_controller.go @@ -396,6 +396,10 @@ func (r *ImageUpdateAutomationReconciler) reconcile(ctx context.Context, sp *pat if r.features[features.GitShallowClone] { checkoutOpts = append(checkoutOpts, source.WithCheckoutOptionShallowClone()) } + if r.features[features.GitSparseCheckout] && obj.Spec.Update.Path != "" { + checkoutOpts = append(checkoutOpts, source.WithCheckoutOptionSparseCheckoutDirectories(obj.Spec.Update.Path)) + } + // If full sync is still not needed, configure last observed commit to // perform optimized clone and obtain a non-concrete commit if the remote // has not changed. diff --git a/internal/features/features.go b/internal/features/features.go index 0446a03..7a30cc1 100644 --- a/internal/features/features.go +++ b/internal/features/features.go @@ -34,6 +34,9 @@ const ( // GitAllBranchReferences enables the download of all branch head references // when push branches are configured. When enabled fixes fluxcd/flux2#3384. GitAllBranchReferences = "GitAllBranchReferences" + // GitSparseCheckout enables the use of sparse checkout when pulling source from + // Git repositories. + GitSparseCheckout = "GitSparseCheckout" // CacheSecretsAndConfigMaps controls whether Secrets and ConfigMaps should // be cached. // @@ -55,6 +58,10 @@ var features = map[string]bool{ // opt-out from v0.28 GitAllBranchReferences: true, + // GitSparseCheckout + // opt-in from v0.42 + GitSparseCheckout: false, + // CacheSecretsAndConfigMaps // opt-in from v0.29 CacheSecretsAndConfigMaps: false, diff --git a/internal/source/source.go b/internal/source/source.go index 5edea83..51403c8 100644 --- a/internal/source/source.go +++ b/internal/source/source.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "strings" "text/template" "time" @@ -196,6 +197,19 @@ func WithCheckoutOptionShallowClone() CheckoutOption { } } +// WithCheckoutOptionSparseCheckoutDirectories is a CheckoutOption option to configure +// SparseCheckoutDirectories. +func WithCheckoutOptionSparseCheckoutDirectories(updatePath string) CheckoutOption { + return func(cc *repository.CloneConfig) { + cleanedPath := filepath.Clean(updatePath) + if cleanedPath == "." { + // Do not set SparseCheckoutDirectories if repository root is specified + return + } + cc.SparseCheckoutDirectories = []string{cleanedPath} + } +} + // CheckoutSource clones and checks out the source. If a push branch is // configured that doesn't match with the checkout branch, a checkout to the // push branch is also performed. This ensures any change and push operation diff --git a/internal/source/source_test.go b/internal/source/source_test.go index a62a9b2..0a66e43 100644 --- a/internal/source/source_test.go +++ b/internal/source/source_test.go @@ -222,13 +222,14 @@ func TestSourceManager_CheckoutSource(t *testing.T) { func test_sourceManager_CheckoutSource(t *testing.T, proto string) { tests := []struct { - name string - autoGitSpec *imagev1.GitSpec - gitRepoRef *sourcev1.GitRepositoryRef - shallowClone bool - lastObserved bool - wantErr bool - wantRef string + name string + autoGitSpec *imagev1.GitSpec + gitRepoRef *sourcev1.GitRepositoryRef + shallowClone bool + sparseCheckoutDirectory string + lastObserved bool + wantErr bool + wantRef string }{ { name: "checkout for single branch", @@ -275,6 +276,42 @@ func test_sourceManager_CheckoutSource(t *testing.T, proto string) { wantErr: false, wantRef: "main", }, + { + name: "with sparse checkout", + autoGitSpec: &imagev1.GitSpec{ + Push: &imagev1.PushSpec{Branch: "main"}, + Checkout: &imagev1.GitCheckoutSpec{ + Reference: sourcev1.GitRepositoryRef{Branch: "main"}, + }, + }, + sparseCheckoutDirectory: "testdata/appconfig/deploy.yaml", + wantErr: false, + wantRef: "main", + }, + { + name: "with sparse checkout for current directory", + autoGitSpec: &imagev1.GitSpec{ + Push: &imagev1.PushSpec{Branch: "main"}, + Checkout: &imagev1.GitCheckoutSpec{ + Reference: sourcev1.GitRepositoryRef{Branch: "main"}, + }, + }, + sparseCheckoutDirectory: "./", + wantErr: false, + wantRef: "main", + }, + { + name: "with sparse checkout for different push branch", + autoGitSpec: &imagev1.GitSpec{ + Push: &imagev1.PushSpec{Branch: "foo"}, + Checkout: &imagev1.GitCheckoutSpec{ + Reference: sourcev1.GitRepositoryRef{Branch: "main"}, + }, + }, + sparseCheckoutDirectory: "testdata/appconfig/deploy.yaml", + wantErr: false, + wantRef: "foo", + }, { name: "with last observed commit", autoGitSpec: &imagev1.GitSpec{ @@ -386,6 +423,9 @@ func test_sourceManager_CheckoutSource(t *testing.T, proto string) { if tt.shallowClone { opts = append(opts, WithCheckoutOptionShallowClone()) } + if tt.sparseCheckoutDirectory != "" { + opts = append(opts, WithCheckoutOptionSparseCheckoutDirectories(tt.sparseCheckoutDirectory)) + } if tt.lastObserved { opts = append(opts, WithCheckoutOptionLastObserved(headRev)) }