diff --git a/pkg/sync/common/types.go b/pkg/sync/common/types.go index 7399cc78a..a1f41b606 100644 --- a/pkg/sync/common/types.go +++ b/pkg/sync/common/types.go @@ -34,7 +34,10 @@ const ( // Sync option that disables resource deletion SyncOptionDisableDeletion = "Delete=false" // Sync option that sync only out of sync resources - SyncOptionApplyOutOfSyncOnly = "ApplyOutOfSyncOnly=true" + SyncOptionApplyOutOfSyncOnly = "ApplyOutOfSyncOnly=true" + SyncOptionPrunePropagationPolicyForeground = "PrunePropagationPolicy=foreground" + SyncOptionPrunePropagationPolicyBackground = "PrunePropagationPolicy=background" + SyncOptionPrunePropagationPolicyOrphan = "PrunePropagationPolicy=orphan" ) type PermissionValidator func(un *unstructured.Unstructured, res *metav1.APIResource) error diff --git a/pkg/sync/sync_context.go b/pkg/sync/sync_context.go index d56de6a1b..e3b8c8422 100644 --- a/pkg/sync/sync_context.go +++ b/pkg/sync/sync_context.go @@ -955,6 +955,19 @@ func (sc *syncContext) applyObject(t *syncTask, dryRun, validate bool) (common.R var message string shouldReplace := sc.replace || resourceutil.HasAnnotationOption(t.targetObj, common.AnnotationSyncOptions, common.SyncOptionReplace) force := sc.force || resourceutil.HasAnnotationOption(t.targetObj, common.AnnotationSyncOptions, common.SyncOptionForce) + // Default to foreground deletion, use sync context policy, then use object-level config + prunePropagationPolicy := metav1.DeletePropagationForeground + if sc.prunePropagationPolicy != nil { + prunePropagationPolicy = *sc.prunePropagationPolicy + } + switch { + case resourceutil.HasAnnotationOption(t.targetObj, common.AnnotationSyncOptions, common.SyncOptionPrunePropagationPolicyBackground): + prunePropagationPolicy = metav1.DeletePropagationBackground + case resourceutil.HasAnnotationOption(t.targetObj, common.AnnotationSyncOptions, common.SyncOptionPrunePropagationPolicyForeground): + prunePropagationPolicy = metav1.DeletePropagationForeground + case resourceutil.HasAnnotationOption(t.targetObj, common.AnnotationSyncOptions, common.SyncOptionPrunePropagationPolicyOrphan): + prunePropagationPolicy = metav1.DeletePropagationOrphan + } // if it is a dry run, disable server side apply, as the goal is to validate only the // yaml correctness of the rendered manifests. // running dry-run in server mode breaks the auto create namespace feature @@ -975,13 +988,13 @@ func (sc *syncContext) applyObject(t *syncTask, dryRun, validate bool) (common.R message = fmt.Sprintf("error when updating: %v", err.Error()) } } else { - message, err = sc.resourceOps.ReplaceResource(context.TODO(), t.targetObj, dryRunStrategy, force) + message, err = sc.resourceOps.ReplaceResource(context.TODO(), t.targetObj, dryRunStrategy, force, prunePropagationPolicy) } } else { message, err = sc.resourceOps.CreateResource(context.TODO(), t.targetObj, dryRunStrategy, validate) } } else { - message, err = sc.resourceOps.ApplyResource(context.TODO(), t.targetObj, dryRunStrategy, force, validate, serverSideApply, sc.serverSideApplyManager, false) + message, err = sc.resourceOps.ApplyResource(context.TODO(), t.targetObj, dryRunStrategy, force, validate, serverSideApply, sc.serverSideApplyManager, false, prunePropagationPolicy) } if err != nil { return common.ResultCodeSyncFailed, err.Error() @@ -1008,7 +1021,21 @@ func (sc *syncContext) pruneObject(liveObj *unstructured.Unstructured, prune, dr // Skip deletion if object is already marked for deletion, so we don't cause a resource update hotloop deletionTimestamp := liveObj.GetDeletionTimestamp() if deletionTimestamp == nil || deletionTimestamp.IsZero() { - err := sc.kubectl.DeleteResource(context.TODO(), sc.config, liveObj.GroupVersionKind(), liveObj.GetName(), liveObj.GetNamespace(), sc.getDeleteOptions()) + propagationPolicy := metav1.DeletePropagationForeground + deleteOptions := sc.getDeleteOptions() + if deleteOptions.PropagationPolicy != nil { + propagationPolicy = *deleteOptions.PropagationPolicy + } + switch { + case resourceutil.HasAnnotationOption(liveObj, common.AnnotationSyncOptions, common.SyncOptionPrunePropagationPolicyBackground): + propagationPolicy = metav1.DeletePropagationBackground + case resourceutil.HasAnnotationOption(liveObj, common.AnnotationSyncOptions, common.SyncOptionPrunePropagationPolicyForeground): + propagationPolicy = metav1.DeletePropagationForeground + case resourceutil.HasAnnotationOption(liveObj, common.AnnotationSyncOptions, common.SyncOptionPrunePropagationPolicyOrphan): + propagationPolicy = metav1.DeletePropagationOrphan + } + deleteOptions.PropagationPolicy = &propagationPolicy + err := sc.kubectl.DeleteResource(context.TODO(), sc.config, liveObj.GroupVersionKind(), liveObj.GetName(), liveObj.GetNamespace(), deleteOptions) if err != nil { return common.ResultCodeSyncFailed, err.Error() } diff --git a/pkg/utils/kube/kubetest/mock_resource_operations.go b/pkg/utils/kube/kubetest/mock_resource_operations.go index c6e30f4e4..e1d1222c5 100644 --- a/pkg/utils/kube/kubetest/mock_resource_operations.go +++ b/pkg/utils/kube/kubetest/mock_resource_operations.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/argoproj/gitops-engine/pkg/utils/kube" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/watch" @@ -105,7 +106,7 @@ func (r *MockResourceOps) GetLastResourceCommand(key kube.ResourceKey) string { return r.lastCommandPerResource[key] } -func (r *MockResourceOps) ApplyResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force, validate, serverSideApply bool, manager string, serverSideDiff bool) (string, error) { +func (r *MockResourceOps) ApplyResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force, validate, serverSideApply bool, manager string, serverSideDiff bool, cascadingStrategy metav1.DeletionPropagation) (string, error) { r.SetLastValidate(validate) r.SetLastServerSideApply(serverSideApply) r.SetLastServerSideApplyManager(manager) @@ -119,7 +120,7 @@ func (r *MockResourceOps) ApplyResource(ctx context.Context, obj *unstructured.U return command.Output, command.Err } -func (r *MockResourceOps) ReplaceResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force bool) (string, error) { +func (r *MockResourceOps) ReplaceResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force bool, cascadingStrategy metav1.DeletionPropagation) (string, error) { r.SetLastForce(force) command, ok := r.Commands[obj.GetName()] r.SetLastResourceCommand(kube.GetResourceKey(obj), "replace") diff --git a/pkg/utils/kube/resource_ops.go b/pkg/utils/kube/resource_ops.go index 983f5f886..eec1fe5fb 100644 --- a/pkg/utils/kube/resource_ops.go +++ b/pkg/utils/kube/resource_ops.go @@ -39,8 +39,8 @@ import ( // ResourceOperations provides methods to manage k8s resources type ResourceOperations interface { - ApplyResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force, validate, serverSideApply bool, manager string, serverSideDiff bool) (string, error) - ReplaceResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force bool) (string, error) + ApplyResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force, validate, serverSideApply bool, manager string, serverSideDiff bool, cascadingStrategy metav1.DeletionPropagation) (string, error) + ReplaceResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force bool, cascadingStrategy metav1.DeletionPropagation) (string, error) CreateResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, validate bool) (string, error) UpdateResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy) (*unstructured.Unstructured, error) } @@ -159,7 +159,7 @@ func kubeCmdFactory(kubeconfig, ns string, config *rest.Config) cmdutil.Factory return cmdutil.NewFactory(matchVersionKubeConfigFlags) } -func (k *kubectlResourceOperations) ReplaceResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force bool) (string, error) { +func (k *kubectlResourceOperations) ReplaceResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force bool, cascadingStrategy metav1.DeletionPropagation) (string, error) { span := k.tracer.StartSpan("ReplaceResource") span.SetBaggageItem("kind", obj.GetKind()) span.SetBaggageItem("name", obj.GetName()) @@ -172,7 +172,7 @@ func (k *kubectlResourceOperations) ReplaceResource(ctx context.Context, obj *un } defer cleanup() - replaceOptions, err := k.newReplaceOptions(k.config, f, ioStreams, fileName, obj.GetNamespace(), force, dryRunStrategy) + replaceOptions, err := k.newReplaceOptions(k.config, f, ioStreams, fileName, obj.GetNamespace(), force, dryRunStrategy, cascadingStrategy) if err != nil { return err } @@ -240,7 +240,7 @@ func (k *kubectlResourceOperations) UpdateResource(ctx context.Context, obj *uns } // ApplyResource performs an apply of a unstructured resource -func (k *kubectlResourceOperations) ApplyResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force, validate, serverSideApply bool, manager string, serverSideDiff bool) (string, error) { +func (k *kubectlResourceOperations) ApplyResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force, validate, serverSideApply bool, manager string, serverSideDiff bool, cascadingStrategy metav1.DeletionPropagation) (string, error) { span := k.tracer.StartSpan("ApplyResource") span.SetBaggageItem("kind", obj.GetKind()) span.SetBaggageItem("name", obj.GetName()) @@ -258,7 +258,7 @@ func (k *kubectlResourceOperations) ApplyResource(ctx context.Context, obj *unst } defer cleanup() - applyOpts, err := k.newApplyOptions(ioStreams, obj, fileName, validate, force, serverSideApply, dryRunStrategy, manager, serverSideDiff) + applyOpts, err := k.newApplyOptions(ioStreams, obj, fileName, validate, force, serverSideApply, dryRunStrategy, manager, serverSideDiff, cascadingStrategy) if err != nil { return err } @@ -266,7 +266,7 @@ func (k *kubectlResourceOperations) ApplyResource(ctx context.Context, obj *unst }) } -func (k *kubectlResourceOperations) newApplyOptions(ioStreams genericclioptions.IOStreams, obj *unstructured.Unstructured, fileName string, validate bool, force, serverSideApply bool, dryRunStrategy cmdutil.DryRunStrategy, manager string, serverSideDiff bool) (*apply.ApplyOptions, error) { +func (k *kubectlResourceOperations) newApplyOptions(ioStreams genericclioptions.IOStreams, obj *unstructured.Unstructured, fileName string, validate bool, force, serverSideApply bool, dryRunStrategy cmdutil.DryRunStrategy, manager string, serverSideDiff bool, cascadingStrategy metav1.DeletionPropagation) (*apply.ApplyOptions, error) { flags := apply.NewApplyFlags(ioStreams) o := &apply.ApplyOptions{ IOStreams: ioStreams, @@ -334,6 +334,7 @@ func (k *kubectlResourceOperations) newApplyOptions(ioStreams genericclioptions. o.DeleteOptions.FilenameOptions.Filenames = []string{fileName} o.Namespace = obj.GetNamespace() o.DeleteOptions.ForceDeletion = force + o.DeleteOptions.CascadingStrategy = cascadingStrategy o.DryRunStrategy = dryRunStrategy if manager != "" { o.FieldManager = manager @@ -378,7 +379,7 @@ func (k *kubectlResourceOperations) newCreateOptions(ioStreams genericclioptions return o, nil } -func (k *kubectlResourceOperations) newReplaceOptions(config *rest.Config, f cmdutil.Factory, ioStreams genericclioptions.IOStreams, fileName string, namespace string, force bool, dryRunStrategy cmdutil.DryRunStrategy) (*replace.ReplaceOptions, error) { +func (k *kubectlResourceOperations) newReplaceOptions(config *rest.Config, f cmdutil.Factory, ioStreams genericclioptions.IOStreams, fileName string, namespace string, force bool, dryRunStrategy cmdutil.DryRunStrategy, cascadingStrategy metav1.DeletionPropagation) (*replace.ReplaceOptions, error) { o := replace.NewReplaceOptions(ioStreams) recorder, err := o.RecordFlags.ToRecorder() @@ -426,6 +427,7 @@ func (k *kubectlResourceOperations) newReplaceOptions(config *rest.Config, f cmd o.DeleteOptions.FilenameOptions.Filenames = []string{fileName} o.Namespace = namespace o.DeleteOptions.ForceDeletion = force + o.DeleteOptions.CascadingStrategy = cascadingStrategy return o, nil }