From 51d62ec248adef9bce41330cabd4c914058c5aa3 Mon Sep 17 00:00:00 2001 From: Aravindhan Ayyanathan Date: Fri, 24 Jul 2026 15:42:33 +0100 Subject: [PATCH 1/2] Add --delete-propagation-policy and --delete-timeout flags to kpt live destroy Signed-off-by: Aravindhan Ayyanathan --- commands/live/destroy/cmddestroy.go | 31 +++++-- commands/live/destroy/cmddestroy_test.go | 85 ++++++++++++------- .../en/reference/cli/live/destroy/_index.md | 10 +++ internal/docs/generated/livedocs/docs.go | 10 +++ 4 files changed, 100 insertions(+), 36 deletions(-) diff --git a/commands/live/destroy/cmddestroy.go b/commands/live/destroy/cmddestroy.go index b9c2ecb20e..9fdd7b922d 100644 --- a/commands/live/destroy/cmddestroy.go +++ b/commands/live/destroy/cmddestroy.go @@ -1,4 +1,4 @@ -// Copyright 2021 The kpt Authors +// Copyright 2021,2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import ( "context" "fmt" "os" + "time" "github.com/kptdev/kpt/internal/docs/generated/livedocs" argsutil "github.com/kptdev/kpt/pkg/lib/util/args" @@ -25,6 +26,7 @@ import ( "github.com/kptdev/kpt/pkg/live" "github.com/kptdev/kpt/pkg/status" "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/kubectl/pkg/cmd/util" "sigs.k8s.io/cli-utils/cmd/flagutils" @@ -67,6 +69,12 @@ func NewRunner( c.Flags().StringVar(&r.statusPolicyString, "status-policy", "all", "It determines which status information should be saved in the inventory (if compatible). Available options "+ fmt.Sprintf("%q and %q.", "all", "none")) + c.Flags().StringVar(&r.deletePropagationPolicyString, "delete-propagation-policy", + "Background", "The propagation policy that should be used when deleting resources. "+ + "The available options are 'Background', 'Foreground', and 'Orphan'.") + c.Flags().DurationVar(&r.deleteTimeout, "delete-timeout", time.Duration(0), + "Timeout threshold for waiting for all resources to be deleted. "+ + "If not set, kpt will wait until interrupted.") return r } @@ -91,8 +99,12 @@ type Runner struct { printStatusEvents bool statusPolicyString string - inventoryPolicy inventory.Policy - statusPolicy inventory.StatusPolicy + deletePropagationPolicyString string + deleteTimeout time.Duration + + inventoryPolicy inventory.Policy + statusPolicy inventory.StatusPolicy + deletePropPolicy metav1.DeletionPropagation // TODO(mortent): This is needed for now since we don't have a good way to // stub out the Destroyer with an interface for testing purposes. @@ -111,6 +123,11 @@ func (r *Runner) preRunE(_ *cobra.Command, _ []string) error { return err } + r.deletePropPolicy, err = flagutils.ConvertPropagationPolicy(r.deletePropagationPolicyString) + if err != nil { + return err + } + if found := printers.ValidatePrinterType(r.output); !found { return fmt.Errorf("unknown output type %q", r.output) } @@ -188,9 +205,11 @@ func runDestroy(r *Runner, inv inventory.Info, dryRunStrategy common.DryRunStrat } options := apply.DestroyerOptions{ - InventoryPolicy: r.inventoryPolicy, - DryRunStrategy: dryRunStrategy, - EmitStatusEvents: true, + InventoryPolicy: r.inventoryPolicy, + DryRunStrategy: dryRunStrategy, + DeletePropagationPolicy: r.deletePropPolicy, + DeleteTimeout: r.deleteTimeout, + EmitStatusEvents: true, } ch := destroyer.Run(r.ctx, inv, options) diff --git a/commands/live/destroy/cmddestroy_test.go b/commands/live/destroy/cmddestroy_test.go index 1056d50c4c..2f1d58c6fa 100644 --- a/commands/live/destroy/cmddestroy_test.go +++ b/commands/live/destroy/cmddestroy_test.go @@ -17,12 +17,14 @@ package destroy import ( "path/filepath" "testing" + "time" kptfilev1 "github.com/kptdev/kpt/api/kptfile/v1" "github.com/kptdev/kpt/internal/testutil" "github.com/kptdev/kpt/pkg/kptfile/kptfileutil" "github.com/kptdev/kpt/pkg/printer/fake" "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/genericclioptions" cmdtesting "k8s.io/kubectl/pkg/cmd/testing" "sigs.k8s.io/cli-utils/pkg/common" @@ -30,55 +32,77 @@ import ( ) func TestCmd(t *testing.T) { + validInventory := &kptfilev1.Inventory{ + Namespace: "my-ns", + Name: "my-name", + InventoryID: "my-inv-id", + } + + // failOnDestroy is used for error cases where preRunE should reject the + // input before the destroy runner is reached. + failOnDestroy := func(t *testing.T, _ *Runner, _ inventory.Info) { + t.Fatal("destroy runner should not be called for error cases") + } + testCases := map[string]struct { args []string namespace string inventory *kptfilev1.Inventory - destroyCallbackFunc func(*testing.T, inventory.Info) + destroyCallbackFunc func(*testing.T, *Runner, inventory.Info) expectedErrorMsg string }{ - "invalid inventory policy": { - args: []string{ - "--inventory-policy", "noSuchPolicy", - }, - namespace: "testns", - destroyCallbackFunc: func(t *testing.T, _ inventory.Info) { - t.FailNow() - }, - expectedErrorMsg: "inventory policy must be one of strict, adopt", + "destroy rejects invalid inventory policy": { + args: []string{"--inventory-policy", "noSuchPolicy"}, + namespace: "testns", + destroyCallbackFunc: failOnDestroy, + expectedErrorMsg: "inventory policy must be one of strict, adopt", + }, + "destroy rejects invalid status policy": { + args: []string{"--status-policy", "noSuchPolicy"}, + namespace: "testns", + destroyCallbackFunc: failOnDestroy, + expectedErrorMsg: "status policy must be one of none, all", }, - "invalid status policy": { + "destroy rejects invalid output format": { + args: []string{"--output", "foo"}, + namespace: "testns", + destroyCallbackFunc: failOnDestroy, + expectedErrorMsg: "unknown output type \"foo\"", + }, + "destroy rejects invalid delete-propagation-policy": { + args: []string{"--delete-propagation-policy", "noSuchPolicy"}, + namespace: "testns", + destroyCallbackFunc: failOnDestroy, + expectedErrorMsg: "prune propagation policy must be one of Background, Foreground, Orphan", + }, + "destroy accepts Foreground delete-propagation-policy": { args: []string{ - "--status-policy", "noSuchPolicy", + "--delete-propagation-policy", "Foreground", }, + inventory: validInventory, namespace: "testns", - destroyCallbackFunc: func(t *testing.T, _ inventory.Info) { - t.FailNow() + destroyCallbackFunc: func(t *testing.T, r *Runner, _ inventory.Info) { + assert.Equal(t, metav1.DeletePropagationForeground, r.deletePropPolicy) }, - expectedErrorMsg: "status policy must be one of none, all", }, - "invalid output format": { + "destroy parses delete-timeout duration": { args: []string{ - "--output", "foo", + "--delete-timeout", "2m", }, + inventory: validInventory, namespace: "testns", - destroyCallbackFunc: func(t *testing.T, _ inventory.Info) { - t.FailNow() + destroyCallbackFunc: func(t *testing.T, r *Runner, _ inventory.Info) { + assert.Equal(t, 2*time.Minute, r.deleteTimeout) }, - expectedErrorMsg: "unknown output type \"foo\"", }, - "fetches the correct inventory information from the Kptfile": { + "destroy reads inventory from Kptfile": { args: []string{ "--inventory-policy", "adopt", "--output", "events", }, - inventory: &kptfilev1.Inventory{ - Namespace: "my-ns", - Name: "my-name", - InventoryID: "my-inv-id", - }, + inventory: validInventory, namespace: "testns", - destroyCallbackFunc: func(t *testing.T, inv inventory.Info) { + destroyCallbackFunc: func(t *testing.T, _ *Runner, inv inventory.Info) { assert.Equal(t, "my-ns", inv.Namespace()) assert.Equal(t, "my-name", inv.Name()) assert.Equal(t, "my-inv-id", inv.ID()) @@ -103,13 +127,14 @@ func TestCmd(t *testing.T) { runner := NewRunner(fake.CtxWithDefaultPrinter(), tf, ioStreams) runner.Command.SetArgs(tc.args) - runner.destroyRunner = func(_ *Runner, inv inventory.Info, _ common.DryRunStrategy) error { - tc.destroyCallbackFunc(t, inv) + // Stub out the destroy execution to isolate flag parsing and + // validation from actual cluster interaction. + runner.destroyRunner = func(r *Runner, inv inventory.Info, _ common.DryRunStrategy) error { + tc.destroyCallbackFunc(t, r, inv) return nil } err := runner.Command.Execute() - // Check if there should be an error if tc.expectedErrorMsg != "" { if !assert.Error(t, err) { t.FailNow() diff --git a/documentation/content/en/reference/cli/live/destroy/_index.md b/documentation/content/en/reference/cli/live/destroy/_index.md index a611d54e31..6470374811 100644 --- a/documentation/content/en/reference/cli/live/destroy/_index.md +++ b/documentation/content/en/reference/cli/live/destroy/_index.md @@ -74,6 +74,16 @@ PKG_PATH | -: * none: Do not save any status information in the inventory. The default value is `all`. + +--delete-propagation-policy: + The propagation policy that should be used when deleting resources. The + default value here is 'Background'. The other options are 'Foreground' and 'Orphan'. + +--delete-timeout: + The threshold for how long to wait for all resources to be deleted before + giving up. If this flag is not set, kpt live destroy will wait until + interrupted. In most cases, it would also make sense to set the + --delete-propagation-policy to Foreground when this flag is set. ``` diff --git a/internal/docs/generated/livedocs/docs.go b/internal/docs/generated/livedocs/docs.go index bbf8ae637f..7166076a61 100644 --- a/internal/docs/generated/livedocs/docs.go +++ b/internal/docs/generated/livedocs/docs.go @@ -166,6 +166,16 @@ Flags: * none: Do not save any status information in the inventory. The default value is ` + "`" + `all` + "`" + `. + + --delete-propagation-policy: + The propagation policy that should be used when deleting resources. The + default value here is 'Background'. The other options are 'Foreground' and 'Orphan'. + + --delete-timeout: + The threshold for how long to wait for all resources to be deleted before + giving up. If this flag is not set, kpt live destroy will wait until + interrupted. In most cases, it would also make sense to set the + --delete-propagation-policy to Foreground when this flag is set. ` var DestroyExamples = ` # remove all resources in the current package from the cluster. From 72bb6fdd912df3784613d3bfea621a5e237d530d Mon Sep 17 00:00:00 2001 From: Aravindhan Ayyanathan Date: Thu, 30 Jul 2026 09:35:26 +0100 Subject: [PATCH 2/2] Address review comments Signed-off-by: Aravindhan Ayyanathan --- commands/live/destroy/cmddestroy.go | 2 +- commands/live/destroy/cmddestroy_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/commands/live/destroy/cmddestroy.go b/commands/live/destroy/cmddestroy.go index 9fdd7b922d..f33897cee6 100644 --- a/commands/live/destroy/cmddestroy.go +++ b/commands/live/destroy/cmddestroy.go @@ -125,7 +125,7 @@ func (r *Runner) preRunE(_ *cobra.Command, _ []string) error { r.deletePropPolicy, err = flagutils.ConvertPropagationPolicy(r.deletePropagationPolicyString) if err != nil { - return err + return fmt.Errorf("delete propagation policy must be one of Background, Foreground, Orphan") } if found := printers.ValidatePrinterType(r.output); !found { diff --git a/commands/live/destroy/cmddestroy_test.go b/commands/live/destroy/cmddestroy_test.go index 2f1d58c6fa..68d4affd7f 100644 --- a/commands/live/destroy/cmddestroy_test.go +++ b/commands/live/destroy/cmddestroy_test.go @@ -73,7 +73,7 @@ func TestCmd(t *testing.T) { args: []string{"--delete-propagation-policy", "noSuchPolicy"}, namespace: "testns", destroyCallbackFunc: failOnDestroy, - expectedErrorMsg: "prune propagation policy must be one of Background, Foreground, Orphan", + expectedErrorMsg: "delete propagation policy must be one of Background, Foreground, Orphan", }, "destroy accepts Foreground delete-propagation-policy": { args: []string{