Skip to content
Open
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
44 changes: 44 additions & 0 deletions internal/controller/artifact_cleanup.go
Original file line number Diff line number Diff line change
@@ -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
}
49 changes: 49 additions & 0 deletions internal/controller/artifact_cleanup_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
8 changes: 7 additions & 1 deletion internal/controller/bucket_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 7 additions & 1 deletion internal/controller/gitrepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 7 additions & 1 deletion internal/controller/helmchart_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 7 additions & 1 deletion internal/controller/helmrepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 7 additions & 1 deletion internal/controller/ocirepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down