From c4ad86edcf19b912c4e5c7519e3d1f21f05d311d Mon Sep 17 00:00:00 2001 From: Riya <108456994+rg3122@users.noreply.github.com> Date: Thu, 29 Oct 2026 10:52:17 +0530 Subject: [PATCH 1/7] Add cleanup for artifacts of objects leaving a shard --- internal/controller/artifact_cleanup.go | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 internal/controller/artifact_cleanup.go diff --git a/internal/controller/artifact_cleanup.go b/internal/controller/artifact_cleanup.go new file mode 100644 index 000000000..df654d706 --- /dev/null +++ b/internal/controller/artifact_cleanup.go @@ -0,0 +1,44 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "fmt" + "os" + + securejoin "github.com/cyphar/filepath-securejoin" + "github.com/fluxcd/pkg/artifact/storage" +) + +// removeArtifactsForMissingObject removes artifacts stored by this controller +// for an object that is no longer visible to its cache. This also handles +// objects that moved outside the controller's watch-label selector: they still +// exist in the API, but this shard must stop serving their stale artifacts. +func removeArtifactsForMissingObject(s *storage.Storage, kind, namespace, name string) error { + if s == nil { + return nil + } + + artifactDir, err := securejoin.SecureJoin(s.BasePath, storage.ArtifactDir(kind, namespace, name)) + if err != nil { + return fmt.Errorf("failed to resolve artifact directory for %s %s/%s: %w", kind, namespace, name, err) + } + if err := os.RemoveAll(artifactDir); err != nil { + return fmt.Errorf("failed to remove artifacts for %s %s/%s: %w", kind, namespace, name, err) + } + return nil +} From 2a54c981e8c03eb0ce768e18d9b8f8fc0dd861df Mon Sep 17 00:00:00 2001 From: Riya <108456994+rg3122@users.noreply.github.com> Date: Thu, 29 Oct 2026 10:52:20 +0530 Subject: [PATCH 2/7] Test cleanup of artifacts for missing objects --- internal/controller/artifact_cleanup_test.go | 49 ++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 internal/controller/artifact_cleanup_test.go diff --git a/internal/controller/artifact_cleanup_test.go b/internal/controller/artifact_cleanup_test.go new file mode 100644 index 000000000..ee97e1dd7 --- /dev/null +++ b/internal/controller/artifact_cleanup_test.go @@ -0,0 +1,49 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "os" + "path/filepath" + "testing" + + "github.com/fluxcd/pkg/artifact/storage" +) + +func TestRemoveArtifactsForMissingObject(t *testing.T) { + basePath := t.TempDir() + targetDir := filepath.Join(basePath, storage.ArtifactDir("HelmRepository", "default", "podinfo")) + otherDir := filepath.Join(basePath, storage.ArtifactDir("HelmRepository", "default", "other")) + for _, dir := range []string{targetDir, otherDir} { + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "artifact.tgz"), []byte("artifact"), 0o600); err != nil { + t.Fatal(err) + } + } + + if err := removeArtifactsForMissingObject(&storage.Storage{BasePath: basePath}, "HelmRepository", "default", "podinfo"); err != nil { + t.Fatalf("removeArtifactsForMissingObject() error = %v", err) + } + if _, err := os.Stat(targetDir); !os.IsNotExist(err) { + t.Fatalf("expected target artifact directory to be removed, stat error = %v", err) + } + if _, err := os.Stat(filepath.Join(otherDir, "artifact.tgz")); err != nil { + t.Fatalf("expected unrelated artifact to remain, stat error = %v", err) + } +} From 9b30e9e93c4886ef4ec8ca111a0c8ff84c8e3e18 Mon Sep 17 00:00:00 2001 From: Riya <108456994+rg3122@users.noreply.github.com> Date: Thu, 29 Oct 2026 10:52:28 +0530 Subject: [PATCH 3/7] Clean up Bucket artifacts when object leaves shard --- internal/controller/bucket_controller.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/controller/bucket_controller.go b/internal/controller/bucket_controller.go index 8406a1c08..4e31be0ae 100644 --- a/internal/controller/bucket_controller.go +++ b/internal/controller/bucket_controller.go @@ -199,7 +199,13 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res // Fetch the Bucket obj := &sourcev1.Bucket{} if err := r.Get(ctx, req.NamespacedName, obj); err != nil { - return ctrl.Result{}, client.IgnoreNotFound(err) + if client.IgnoreNotFound(err) == nil { + if cleanupErr := removeArtifactsForMissingObject(r.Storage, "Bucket", req.Namespace, req.Name); cleanupErr != nil { + return ctrl.Result{}, cleanupErr + } + return ctrl.Result{}, nil + } + return ctrl.Result{}, err } // Initialize the patch helper with the current version of the object. From af56d5609bb5bc6133947e1b1879b74f69eea401 Mon Sep 17 00:00:00 2001 From: Riya <108456994+rg3122@users.noreply.github.com> Date: Thu, 29 Oct 2026 10:52:32 +0530 Subject: [PATCH 4/7] Clean up GitRepository artifacts when object leaves shard --- internal/controller/gitrepository_controller.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/controller/gitrepository_controller.go b/internal/controller/gitrepository_controller.go index bfc44d199..3d67171c6 100644 --- a/internal/controller/gitrepository_controller.go +++ b/internal/controller/gitrepository_controller.go @@ -213,7 +213,13 @@ func (r *GitRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reques // Fetch the GitRepository obj := &sourcev1.GitRepository{} if err := r.Get(ctx, req.NamespacedName, obj); err != nil { - return ctrl.Result{}, client.IgnoreNotFound(err) + if client.IgnoreNotFound(err) == nil { + if cleanupErr := removeArtifactsForMissingObject(r.Storage, "GitRepository", req.Namespace, req.Name); cleanupErr != nil { + return ctrl.Result{}, cleanupErr + } + return ctrl.Result{}, nil + } + return ctrl.Result{}, err } // Initialize the patch helper with the current version of the object. From 484f9d1d5112a5cc4fd52710566dc1fd1eb52c11 Mon Sep 17 00:00:00 2001 From: Riya <108456994+rg3122@users.noreply.github.com> Date: Thu, 29 Oct 2026 10:52:38 +0530 Subject: [PATCH 5/7] Clean up HelmChart artifacts when object leaves shard --- internal/controller/helmchart_controller.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/controller/helmchart_controller.go b/internal/controller/helmchart_controller.go index ca80c4f25..7d3a0e9fc 100644 --- a/internal/controller/helmchart_controller.go +++ b/internal/controller/helmchart_controller.go @@ -213,7 +213,13 @@ func (r *HelmChartReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // Fetch the HelmChart obj := &sourcev1.HelmChart{} if err := r.Get(ctx, req.NamespacedName, obj); err != nil { - return ctrl.Result{}, client.IgnoreNotFound(err) + if client.IgnoreNotFound(err) == nil { + if cleanupErr := removeArtifactsForMissingObject(r.Storage, "HelmChart", req.Namespace, req.Name); cleanupErr != nil { + return ctrl.Result{}, cleanupErr + } + return ctrl.Result{}, nil + } + return ctrl.Result{}, err } // Initialize the patch helper with the current version of the object. From f7dc4a0f0f4b4dee75bdf5567d6f9bf8b6af68be Mon Sep 17 00:00:00 2001 From: Riya <108456994+rg3122@users.noreply.github.com> Date: Thu, 29 Oct 2026 10:52:42 +0530 Subject: [PATCH 6/7] Clean up HelmRepository artifacts when object leaves shard --- internal/controller/helmrepository_controller.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/controller/helmrepository_controller.go b/internal/controller/helmrepository_controller.go index d3a966fe3..95058ff14 100644 --- a/internal/controller/helmrepository_controller.go +++ b/internal/controller/helmrepository_controller.go @@ -152,7 +152,13 @@ func (r *HelmRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reque // Fetch the HelmRepository obj := &sourcev1.HelmRepository{} if err := r.Get(ctx, req.NamespacedName, obj); err != nil { - return ctrl.Result{}, client.IgnoreNotFound(err) + if client.IgnoreNotFound(err) == nil { + if cleanupErr := removeArtifactsForMissingObject(r.Storage, "HelmRepository", req.Namespace, req.Name); cleanupErr != nil { + return ctrl.Result{}, cleanupErr + } + return ctrl.Result{}, nil + } + return ctrl.Result{}, err } // Initialize the patch helper with the current version of the object. From b8555a70ffcfdc97f856e9b994346473b24e3c67 Mon Sep 17 00:00:00 2001 From: Riya <108456994+rg3122@users.noreply.github.com> Date: Thu, 29 Oct 2026 10:52:49 +0530 Subject: [PATCH 7/7] Clean up OCIRepository artifacts when object leaves shard --- internal/controller/ocirepository_controller.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/controller/ocirepository_controller.go b/internal/controller/ocirepository_controller.go index 5329432e3..5822f101a 100644 --- a/internal/controller/ocirepository_controller.go +++ b/internal/controller/ocirepository_controller.go @@ -187,7 +187,13 @@ func (r *OCIRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reques // Fetch the OCIRepository obj := &sourcev1.OCIRepository{} if err := r.Get(ctx, req.NamespacedName, obj); err != nil { - return ctrl.Result{}, client.IgnoreNotFound(err) + if client.IgnoreNotFound(err) == nil { + if cleanupErr := removeArtifactsForMissingObject(r.Storage, "OCIRepository", req.Namespace, req.Name); cleanupErr != nil { + return ctrl.Result{}, cleanupErr + } + return ctrl.Result{}, nil + } + return ctrl.Result{}, err } // Initialize the patch helper with the current version of the object.