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}, }, }