diff --git a/api/v1/kustomization_types.go b/api/v1/kustomization_types.go
index b4b9bca1..4854aaa9 100644
--- a/api/v1/kustomization_types.go
+++ b/api/v1/kustomization_types.go
@@ -34,9 +34,10 @@ const (
IfNotPresentValue = "IfNotPresent"
IgnoreValue = "Ignore"
- DeletionPolicyMirrorPrune = "MirrorPrune"
- DeletionPolicyDelete = "Delete"
- DeletionPolicyOrphan = "Orphan"
+ DeletionPolicyMirrorPrune = "MirrorPrune"
+ DeletionPolicyDelete = "Delete"
+ DeletionPolicyWaitForTermination = "WaitForTermination"
+ DeletionPolicyOrphan = "Orphan"
)
// KustomizationSpec defines the configuration to calculate the desired state
@@ -101,9 +102,9 @@ type KustomizationSpec struct {
// DeletionPolicy can be used to control garbage collection when this
// Kustomization is deleted. Valid values are ('MirrorPrune', 'Delete',
- // 'Orphan'). 'MirrorPrune' mirrors the Prune field (orphan if false,
- // delete if true). Defaults to 'MirrorPrune'.
- // +kubebuilder:validation:Enum=MirrorPrune;Delete;Orphan
+ // 'WaitForTermination', 'Orphan'). 'MirrorPrune' mirrors the Prune field
+ // (orphan if false, delete if true). Defaults to 'MirrorPrune'.
+ // +kubebuilder:validation:Enum=MirrorPrune;Delete;WaitForTermination;Orphan
// +optional
DeletionPolicy string `json:"deletionPolicy,omitempty"`
diff --git a/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml b/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml
index 9526ee49..21e9a0de 100644
--- a/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml
+++ b/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml
@@ -113,11 +113,12 @@ spec:
description: |-
DeletionPolicy can be used to control garbage collection when this
Kustomization is deleted. Valid values are ('MirrorPrune', 'Delete',
- 'Orphan'). 'MirrorPrune' mirrors the Prune field (orphan if false,
- delete if true). Defaults to 'MirrorPrune'.
+ 'WaitForTermination', 'Orphan'). 'MirrorPrune' mirrors the Prune field
+ (orphan if false, delete if true). Defaults to 'MirrorPrune'.
enum:
- MirrorPrune
- Delete
+ - WaitForTermination
- Orphan
type: string
dependsOn:
diff --git a/docs/api/v1/kustomize.md b/docs/api/v1/kustomize.md
index 325ba381..a109c61e 100644
--- a/docs/api/v1/kustomize.md
+++ b/docs/api/v1/kustomize.md
@@ -217,8 +217,8 @@ string
(Optional)
DeletionPolicy can be used to control garbage collection when this
Kustomization is deleted. Valid values are (‘MirrorPrune’, ‘Delete’,
-‘Orphan’). ‘MirrorPrune’ mirrors the Prune field (orphan if false,
-delete if true). Defaults to ‘MirrorPrune’.
+‘WaitForTermination’, ‘Orphan’). ‘MirrorPrune’ mirrors the Prune field
+(orphan if false, delete if true). Defaults to ‘MirrorPrune’.
@@ -775,8 +775,8 @@ string
(Optional)
DeletionPolicy can be used to control garbage collection when this
Kustomization is deleted. Valid values are (‘MirrorPrune’, ‘Delete’,
-‘Orphan’). ‘MirrorPrune’ mirrors the Prune field (orphan if false,
-delete if true). Defaults to ‘MirrorPrune’.
+‘WaitForTermination’, ‘Orphan’). ‘MirrorPrune’ mirrors the Prune field
+(orphan if false, delete if true). Defaults to ‘MirrorPrune’.
diff --git a/docs/spec/v1/kustomizations.md b/docs/spec/v1/kustomizations.md
index 2c594e52..37d29583 100644
--- a/docs/spec/v1/kustomizations.md
+++ b/docs/spec/v1/kustomizations.md
@@ -181,8 +181,16 @@ Valid values:
`true` and orphaned if `false`.
- `Delete` - Ensure the managed resources are deleted before the Kustomization
is deleted.
+- `WaitForTermination` - Ensure the managed resources are deleted and wait for
+ termination before the Kustomization is deleted.
- `Orphan` - Leave the managed resources when the Kustomization is deleted.
+The `WaitForTermination` deletion policy blocks and waits for the managed
+resources to be removed from etcd by the Kubernetes garbage collector.
+The wait time is determined by the `.spec.timeout` field. If a timeout occurs,
+the controller will stop waiting for the deletion of the resources,
+log an error and will allow the Kustomization to be deleted.
+
For special cases when the managed resources are removed by other means (e.g.
the deletion of the namespace specified with
[`.spec.targetNamespace`](#target-namespace)), you can set the deletion policy
diff --git a/go.mod b/go.mod
index 284ece72..7cf883c8 100644
--- a/go.mod
+++ b/go.mod
@@ -29,7 +29,7 @@ require (
github.com/fluxcd/pkg/http/fetch v0.16.0
github.com/fluxcd/pkg/kustomize v1.17.0
github.com/fluxcd/pkg/runtime v0.59.0
- github.com/fluxcd/pkg/ssa v0.46.0
+ github.com/fluxcd/pkg/ssa v0.47.0
github.com/fluxcd/pkg/tar v0.12.0
github.com/fluxcd/pkg/testserver v0.11.0
github.com/fluxcd/source-controller/api v1.5.0
diff --git a/go.sum b/go.sum
index 49f56da4..4bf4748b 100644
--- a/go.sum
+++ b/go.sum
@@ -202,8 +202,8 @@ github.com/fluxcd/pkg/runtime v0.59.0 h1:3OrFkMJB39NcQ2vhhoxqls59sQVSn8U+thhyLbs
github.com/fluxcd/pkg/runtime v0.59.0/go.mod h1:MFbfyNyyoYRgPxpdwC9/dCOkzo7Yxhu/cQ9NKyhvqc0=
github.com/fluxcd/pkg/sourceignore v0.12.0 h1:jCIe6d50rQ3wdXPF0+PhhqN0XrTRIq3upMomPelI8Mw=
github.com/fluxcd/pkg/sourceignore v0.12.0/go.mod h1:dc0zvkuXM5OgL/b3IkrVuwvPjj1zJn4NBUMH45uJ4Y0=
-github.com/fluxcd/pkg/ssa v0.46.0 h1:TGomtbA6zTfZrHF0TDn3mIGKH+bbX45zdWSkdYrwS8g=
-github.com/fluxcd/pkg/ssa v0.46.0/go.mod h1:qCek0b8tKumh9iNZLmga1mjeXOlZPlZpc6xip/hLMJM=
+github.com/fluxcd/pkg/ssa v0.47.0 h1:J6lt6g21HjpfF89xbwcMZ9s1ZgguvNsHrNyc0onjx8g=
+github.com/fluxcd/pkg/ssa v0.47.0/go.mod h1:qCek0b8tKumh9iNZLmga1mjeXOlZPlZpc6xip/hLMJM=
github.com/fluxcd/pkg/tar v0.12.0 h1:og6F+ivnWNRbNJSq0ukCTVs7YrGIlzjxSVZU+E8NprM=
github.com/fluxcd/pkg/tar v0.12.0/go.mod h1:Ra5Cj++MD5iCy7bZGKJJX3GpOeMPv+ZDkPO9bBwpDeU=
github.com/fluxcd/pkg/testserver v0.11.0 h1:a/kxpFqv7XQxZjwVPP3voooRmSd/3ipLVolK0xUIxXQ=
diff --git a/internal/controller/kustomization_controller.go b/internal/controller/kustomization_controller.go
index 7f905d09..6804edd2 100644
--- a/internal/controller/kustomization_controller.go
+++ b/internal/controller/kustomization_controller.go
@@ -1019,20 +1019,40 @@ func (r *KustomizationReconciler) prune(ctx context.Context,
return false, nil
}
+// finalizerShouldDeleteResources determines if resources should be deleted
+// based on the object's inventory and deletion policy.
+// A suspended Kustomization or one without an inventory will not delete resources.
func finalizerShouldDeleteResources(obj *kustomizev1.Kustomization) bool {
- if obj.GetDeletionPolicy() == kustomizev1.DeletionPolicyMirrorPrune {
+ if obj.Spec.Suspend {
+ return false
+ }
+
+ if obj.Status.Inventory == nil || len(obj.Status.Inventory.Entries) == 0 {
+ return false
+ }
+
+ switch obj.GetDeletionPolicy() {
+ case kustomizev1.DeletionPolicyMirrorPrune:
return obj.Spec.Prune
+ case kustomizev1.DeletionPolicyDelete:
+ return true
+ case kustomizev1.DeletionPolicyWaitForTermination:
+ return true
+ default:
+ return false
}
- return obj.Spec.DeletionPolicy == kustomizev1.DeletionPolicyDelete
}
+// finalize handles the finalization logic for a Kustomization resource during its deletion process.
+// Managed resources are pruned based on the deletion policy and suspended state of the Kustomization.
+// When the policy is set to WaitForTermination, the function blocks and waits for the resources
+// to be terminated by the Kubernetes Garbage Collector for the specified timeout duration.
+// If the service account used for impersonation is no longer available or if a timeout occurs
+// while waiting for resources to be terminated, an error is logged and the finalizer is removed.
func (r *KustomizationReconciler) finalize(ctx context.Context,
obj *kustomizev1.Kustomization) (ctrl.Result, error) {
log := ctrl.LoggerFrom(ctx)
- if finalizerShouldDeleteResources(obj) &&
- !obj.Spec.Suspend &&
- obj.Status.Inventory != nil &&
- obj.Status.Inventory.Entries != nil {
+ if finalizerShouldDeleteResources(obj) {
objects, _ := inventory.List(obj.Status.Inventory)
var impersonatorOpts []runtimeClient.ImpersonatorOption
@@ -1085,7 +1105,21 @@ func (r *KustomizationReconciler) finalize(ctx context.Context,
}
if changeSet != nil && len(changeSet.Entries) > 0 {
+ // Emit event with the resources marked for deletion.
r.event(obj, obj.Status.LastAppliedRevision, obj.Status.LastAppliedOriginRevision, eventv1.EventSeverityInfo, changeSet.String(), nil)
+
+ // Wait for the resources marked for deletion to be terminated.
+ if obj.GetDeletionPolicy() == kustomizev1.DeletionPolicyWaitForTermination {
+ if err := resourceManager.WaitForSetTermination(changeSet, ssa.WaitOptions{
+ Interval: 2 * time.Second,
+ Timeout: obj.GetTimeout(),
+ }); err != nil {
+ // Emit an event and log the error if a timeout occurs.
+ msg := "failed to wait for resources termination"
+ log.Error(err, msg)
+ r.event(obj, obj.Status.LastAppliedRevision, obj.Status.LastAppliedOriginRevision, eventv1.EventSeverityError, msg, nil)
+ }
+ }
}
} else {
// when the account to impersonate is gone, log the stale objects and continue with the finalization
diff --git a/internal/controller/kustomization_deletion_policy_test.go b/internal/controller/kustomization_deletion_policy_test.go
index f5493602..e1293bc4 100644
--- a/internal/controller/kustomization_deletion_policy_test.go
+++ b/internal/controller/kustomization_deletion_policy_test.go
@@ -48,6 +48,12 @@ func TestKustomizationReconciler_DeletionPolicyDelete(t *testing.T) {
deletionPolicy: kustomizev1.DeletionPolicyDelete,
wantDelete: true,
},
+ {
+ name: "should delete and wait when deletionPolicy overrides pruning disabled",
+ prune: false,
+ deletionPolicy: kustomizev1.DeletionPolicyWaitForTermination,
+ wantDelete: true,
+ },
{
name: "should delete when deletionPolicy mirrors prune and pruning enabled",
prune: true,
@@ -131,6 +137,7 @@ data:
TargetNamespace: id,
Prune: tt.prune,
DeletionPolicy: tt.deletionPolicy,
+ Timeout: &metav1.Duration{Duration: 5 * time.Second},
},
}