kubectl delete: Introduce new interactive flag for interactive deletion (#114530)
This commit is contained in:
		@@ -121,6 +121,7 @@ type DeleteOptions struct {
 | 
			
		||||
	Quiet               bool
 | 
			
		||||
	WarnClusterScope    bool
 | 
			
		||||
	Raw                 string
 | 
			
		||||
	Interactive         bool
 | 
			
		||||
 | 
			
		||||
	GracePeriod int
 | 
			
		||||
	Timeout     time.Duration
 | 
			
		||||
@@ -132,6 +133,8 @@ type DeleteOptions struct {
 | 
			
		||||
	DynamicClient      dynamic.Interface
 | 
			
		||||
	Mapper             meta.RESTMapper
 | 
			
		||||
	Result             *resource.Result
 | 
			
		||||
	PreviewResult      *resource.Result
 | 
			
		||||
	previewResourceMap map[cmdwait.ResourceLocation]struct{}
 | 
			
		||||
 | 
			
		||||
	genericiooptions.IOStreams
 | 
			
		||||
	WarningPrinter *printers.WarningPrinter
 | 
			
		||||
@@ -197,7 +200,15 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Co
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(o.Raw) == 0 {
 | 
			
		||||
	// Set default WarningPrinter if not already set.
 | 
			
		||||
	if o.WarningPrinter == nil {
 | 
			
		||||
		o.WarningPrinter = printers.NewWarningPrinter(o.ErrOut, printers.WarningPrinterOptions{Color: term.AllowsColorOutput(o.ErrOut)})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(o.Raw) != 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := f.NewBuilder().
 | 
			
		||||
		Unstructured().
 | 
			
		||||
		ContinueOnError().
 | 
			
		||||
@@ -216,6 +227,30 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Co
 | 
			
		||||
	}
 | 
			
		||||
	o.Result = r
 | 
			
		||||
 | 
			
		||||
	if o.Interactive {
 | 
			
		||||
		// preview result will be used to list resources for confirmation prior to actual delete.
 | 
			
		||||
		// We can not use r as result object because it can only be used once. But we need to traverse
 | 
			
		||||
		// twice. Parameters in preview result must be equal to genuine result.
 | 
			
		||||
		previewr := f.NewBuilder().
 | 
			
		||||
			Unstructured().
 | 
			
		||||
			ContinueOnError().
 | 
			
		||||
			NamespaceParam(cmdNamespace).DefaultNamespace().
 | 
			
		||||
			FilenameParam(enforceNamespace, &o.FilenameOptions).
 | 
			
		||||
			LabelSelectorParam(o.LabelSelector).
 | 
			
		||||
			FieldSelectorParam(o.FieldSelector).
 | 
			
		||||
			SelectAllParam(o.DeleteAll).
 | 
			
		||||
			AllNamespaces(o.DeleteAllNamespaces).
 | 
			
		||||
			ResourceTypeOrNameArgs(false, args...).RequireObject(false).
 | 
			
		||||
			Flatten().
 | 
			
		||||
			Do()
 | 
			
		||||
		err = previewr.Err()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		o.PreviewResult = previewr
 | 
			
		||||
		o.previewResourceMap = make(map[cmdwait.ResourceLocation]struct{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.Mapper, err = f.ToRESTMapper()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -225,12 +260,6 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Co
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Set default WarningPrinter if not already set.
 | 
			
		||||
	if o.WarningPrinter == nil {
 | 
			
		||||
		o.WarningPrinter = printers.NewWarningPrinter(o.ErrOut, printers.WarningPrinterOptions{Color: term.AllowsColorOutput(o.ErrOut)})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -257,7 +286,13 @@ func (o *DeleteOptions) Validate() error {
 | 
			
		||||
		return fmt.Errorf("--force and --grace-period greater than 0 cannot be specified together")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(o.Raw) > 0 {
 | 
			
		||||
	if len(o.Raw) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.Interactive {
 | 
			
		||||
		return fmt.Errorf("--interactive can not be used with --raw")
 | 
			
		||||
	}
 | 
			
		||||
	if len(o.FilenameOptions.Filenames) > 1 {
 | 
			
		||||
		return fmt.Errorf("--raw can only use a single local file or stdin")
 | 
			
		||||
	} else if len(o.FilenameOptions.Filenames) == 1 {
 | 
			
		||||
@@ -275,7 +310,6 @@ func (o *DeleteOptions) Validate() error {
 | 
			
		||||
	if _, err := url.ParseRequestURI(o.Raw); err != nil {
 | 
			
		||||
		return fmt.Errorf("--raw must be a valid URL path: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -291,6 +325,39 @@ func (o *DeleteOptions) RunDelete(f cmdutil.Factory) error {
 | 
			
		||||
		}
 | 
			
		||||
		return rawhttp.RawDelete(restClient, o.IOStreams, o.Raw, o.Filenames[0])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.Interactive {
 | 
			
		||||
		previewInfos := []*resource.Info{}
 | 
			
		||||
		if o.IgnoreNotFound {
 | 
			
		||||
			o.PreviewResult = o.PreviewResult.IgnoreErrors(errors.IsNotFound)
 | 
			
		||||
		}
 | 
			
		||||
		err := o.PreviewResult.Visit(func(info *resource.Info, err error) error {
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			previewInfos = append(previewInfos, info)
 | 
			
		||||
			o.previewResourceMap[cmdwait.ResourceLocation{
 | 
			
		||||
				GroupResource: info.Mapping.Resource.GroupResource(),
 | 
			
		||||
				Namespace:     info.Namespace,
 | 
			
		||||
				Name:          info.Name,
 | 
			
		||||
			}] = struct{}{}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if len(previewInfos) == 0 {
 | 
			
		||||
			fmt.Fprintf(o.Out, "No resources found\n")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !o.confirmation(previewInfos) {
 | 
			
		||||
			fmt.Fprintf(o.Out, "deletion is cancelled\n")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return o.DeleteResult(o.Result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -306,6 +373,18 @@ func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if o.Interactive {
 | 
			
		||||
			if _, ok := o.previewResourceMap[cmdwait.ResourceLocation{
 | 
			
		||||
				GroupResource: info.Mapping.Resource.GroupResource(),
 | 
			
		||||
				Namespace:     info.Namespace,
 | 
			
		||||
				Name:          info.Name,
 | 
			
		||||
			}]; !ok {
 | 
			
		||||
				// resource not in the list of previewed resources based on resourceLocation
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		deletedInfos = append(deletedInfos, info)
 | 
			
		||||
		found++
 | 
			
		||||
 | 
			
		||||
@@ -440,3 +519,24 @@ func (o *DeleteOptions) PrintObj(info *resource.Info) {
 | 
			
		||||
	// understandable output by default
 | 
			
		||||
	fmt.Fprintf(o.Out, "%s \"%s\" %s\n", kindString, info.Name, operation)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *DeleteOptions) confirmation(infos []*resource.Info) bool {
 | 
			
		||||
	fmt.Fprintf(o.Out, i18n.T("You are about to delete the following %d resource(s):\n"), len(infos))
 | 
			
		||||
	for _, info := range infos {
 | 
			
		||||
		groupKind := info.Mapping.GroupVersionKind
 | 
			
		||||
		kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group)
 | 
			
		||||
		if len(groupKind.Group) == 0 {
 | 
			
		||||
			kindString = strings.ToLower(groupKind.Kind)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Fprintf(o.Out, i18n.T("Do you want to continue?")+" (y/n): ")
 | 
			
		||||
	var input string
 | 
			
		||||
	_, err := fmt.Fscan(o.In, &input)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return strings.EqualFold(input, "y")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -48,6 +48,7 @@ type DeleteFlags struct {
 | 
			
		||||
	Wait              *bool
 | 
			
		||||
	Output            *string
 | 
			
		||||
	Raw               *string
 | 
			
		||||
	Interactive       *bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *DeleteFlags) ToOptions(dynamicClient dynamic.Interface, streams genericiooptions.IOStreams) (*DeleteOptions, error) {
 | 
			
		||||
@@ -106,6 +107,9 @@ func (f *DeleteFlags) ToOptions(dynamicClient dynamic.Interface, streams generic
 | 
			
		||||
	if f.Raw != nil {
 | 
			
		||||
		options.Raw = *f.Raw
 | 
			
		||||
	}
 | 
			
		||||
	if f.Interactive != nil {
 | 
			
		||||
		options.Interactive = *f.Interactive
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return options, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -156,6 +160,11 @@ func (f *DeleteFlags) AddFlags(cmd *cobra.Command) {
 | 
			
		||||
	if f.Raw != nil {
 | 
			
		||||
		cmd.Flags().StringVar(f.Raw, "raw", *f.Raw, "Raw URI to DELETE to the server.  Uses the transport specified by the kubeconfig file.")
 | 
			
		||||
	}
 | 
			
		||||
	if cmdutil.InteractiveDelete.IsEnabled() {
 | 
			
		||||
		if f.Interactive != nil {
 | 
			
		||||
			cmd.Flags().BoolVarP(f.Interactive, "interactive", "i", *f.Interactive, "If true, delete resource only when user confirms. This flag is in Alpha.")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewDeleteCommandFlags provides default flags and values for use with the "delete" command
 | 
			
		||||
@@ -175,6 +184,7 @@ func NewDeleteCommandFlags(usage string) *DeleteFlags {
 | 
			
		||||
	timeout := time.Duration(0)
 | 
			
		||||
	wait := true
 | 
			
		||||
	raw := ""
 | 
			
		||||
	interactive := false
 | 
			
		||||
 | 
			
		||||
	filenames := []string{}
 | 
			
		||||
	recursive := false
 | 
			
		||||
@@ -198,6 +208,7 @@ func NewDeleteCommandFlags(usage string) *DeleteFlags {
 | 
			
		||||
		Wait:           &wait,
 | 
			
		||||
		Output:         &output,
 | 
			
		||||
		Raw:            &raw,
 | 
			
		||||
		Interactive:    &interactive,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ package delete
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
@@ -35,6 +36,7 @@ import (
 | 
			
		||||
	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
 | 
			
		||||
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
 | 
			
		||||
	"k8s.io/kubectl/pkg/scheme"
 | 
			
		||||
	"k8s.io/utils/pointer"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func fakecmd() *cobra.Command {
 | 
			
		||||
@@ -47,6 +49,49 @@ func fakecmd() *cobra.Command {
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteFlagValidation(t *testing.T) {
 | 
			
		||||
	f := cmdtesting.NewTestFactory()
 | 
			
		||||
	defer f.Cleanup()
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		flags        DeleteFlags
 | 
			
		||||
		enableAlphas []cmdutil.FeatureGate
 | 
			
		||||
		args         [][]string
 | 
			
		||||
		expectedErr  string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			flags: DeleteFlags{
 | 
			
		||||
				Raw:         pointer.String("test"),
 | 
			
		||||
				Interactive: pointer.Bool(true),
 | 
			
		||||
			},
 | 
			
		||||
			enableAlphas: []cmdutil.FeatureGate{cmdutil.InteractiveDelete},
 | 
			
		||||
			expectedErr:  "--interactive can not be used with --raw",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		cmd := fakecmd()
 | 
			
		||||
		cmdtesting.WithAlphaEnvs(test.enableAlphas, t, func(t *testing.T) {
 | 
			
		||||
			deleteOptions, err := test.flags.ToOptions(nil, genericiooptions.NewTestIOStreamsDiscard())
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatalf("unexpected error creating delete options: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			deleteOptions.Filenames = []string{"../../../testdata/redis-master-controller.yaml"}
 | 
			
		||||
			err = deleteOptions.Complete(f, nil, cmd)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatalf("unexpected error creating delete options: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			err = deleteOptions.Validate()
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				t.Fatalf("missing expected error")
 | 
			
		||||
			}
 | 
			
		||||
			if test.expectedErr != err.Error() {
 | 
			
		||||
				t.Errorf("expected error %s, got %s", test.expectedErr, err)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteObjectByTuple(t *testing.T) {
 | 
			
		||||
	cmdtesting.InitTestErrorHandler(t)
 | 
			
		||||
	_, _, rc := cmdtesting.TestData()
 | 
			
		||||
@@ -263,6 +308,90 @@ func TestDeleteObject(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPreviewResultEqualToResult(t *testing.T) {
 | 
			
		||||
	deleteFlags := NewDeleteCommandFlags("")
 | 
			
		||||
	deleteFlags.Interactive = pointer.Bool(true)
 | 
			
		||||
 | 
			
		||||
	tf := cmdtesting.NewTestFactory().WithNamespace("test")
 | 
			
		||||
	defer tf.Cleanup()
 | 
			
		||||
 | 
			
		||||
	streams, _, _, _ := genericiooptions.NewTestIOStreams()
 | 
			
		||||
 | 
			
		||||
	deleteOptions, err := deleteFlags.ToOptions(nil, streams)
 | 
			
		||||
	deleteOptions.Filenames = []string{"../../../testdata/redis-master-controller.yaml"}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("unexpected error %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	err = deleteOptions.Complete(tf, nil, fakecmd())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("unexpected error %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	infos, err := deleteOptions.Result.Infos()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("unexpected error %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	previewInfos, err := deleteOptions.PreviewResult.Infos()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("unexpected error %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(infos) != len(previewInfos) {
 | 
			
		||||
		t.Errorf("result and previewResult must match")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteObjectWithInteractive(t *testing.T) {
 | 
			
		||||
	cmdtesting.InitTestErrorHandler(t)
 | 
			
		||||
	_, _, rc := cmdtesting.TestData()
 | 
			
		||||
 | 
			
		||||
	tf := cmdtesting.NewTestFactory().WithNamespace("test")
 | 
			
		||||
	defer tf.Cleanup()
 | 
			
		||||
 | 
			
		||||
	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
 | 
			
		||||
 | 
			
		||||
	tf.UnstructuredClient = &fake.RESTClient{
 | 
			
		||||
		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
 | 
			
		||||
		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
 | 
			
		||||
			switch p, m := req.URL.Path, req.Method; {
 | 
			
		||||
			case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
 | 
			
		||||
				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
 | 
			
		||||
			default:
 | 
			
		||||
				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
 | 
			
		||||
				return nil, nil
 | 
			
		||||
			}
 | 
			
		||||
		}),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.InteractiveDelete}, t, func(t *testing.T) {
 | 
			
		||||
		streams, in, buf, _ := genericiooptions.NewTestIOStreams()
 | 
			
		||||
		fmt.Fprint(in, "y")
 | 
			
		||||
		cmd := NewCmdDelete(tf, streams)
 | 
			
		||||
		cmd.Flags().Set("filename", "../../../testdata/redis-master-controller.yaml")
 | 
			
		||||
		cmd.Flags().Set("output", "name")
 | 
			
		||||
		cmd.Flags().Set("interactive", "true")
 | 
			
		||||
		cmd.Run(cmd, []string{})
 | 
			
		||||
 | 
			
		||||
		if buf.String() != "You are about to delete the following 1 resource(s):\nreplicationcontroller/redis-master\nDo you want to continue? (y/n): replicationcontroller/redis-master\n" {
 | 
			
		||||
			t.Errorf("unexpected output: %s", buf.String())
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		streams, in, buf, _ = genericiooptions.NewTestIOStreams()
 | 
			
		||||
		fmt.Fprint(in, "n")
 | 
			
		||||
		cmd = NewCmdDelete(tf, streams)
 | 
			
		||||
		cmd.Flags().Set("filename", "../../../testdata/redis-master-controller.yaml")
 | 
			
		||||
		cmd.Flags().Set("output", "name")
 | 
			
		||||
		cmd.Flags().Set("interactive", "true")
 | 
			
		||||
		cmd.Run(cmd, []string{})
 | 
			
		||||
 | 
			
		||||
		if buf.String() != "You are about to delete the following 1 resource(s):\nreplicationcontroller/redis-master\nDo you want to continue? (y/n): deletion is cancelled\n" {
 | 
			
		||||
			t.Errorf("unexpected output: %s", buf.String())
 | 
			
		||||
		}
 | 
			
		||||
		if buf.String() == ": replicationcontroller/redis-master\n" {
 | 
			
		||||
			t.Errorf("unexpected output: %s", buf.String())
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGracePeriodScenarios(t *testing.T) {
 | 
			
		||||
	pods, _, _ := cmdtesting.TestData()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -428,6 +428,7 @@ const (
 | 
			
		||||
	ApplySet              FeatureGate = "KUBECTL_APPLYSET"
 | 
			
		||||
	ExplainOpenapiV3      FeatureGate = "KUBECTL_EXPLAIN_OPENAPIV3"
 | 
			
		||||
	CmdPluginAsSubcommand FeatureGate = "KUBECTL_ENABLE_CMD_SHADOW"
 | 
			
		||||
	InteractiveDelete     FeatureGate = "KUBECTL_INTERACTIVE_DELETE"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (f FeatureGate) IsEnabled() bool {
 | 
			
		||||
 
 | 
			
		||||
@@ -52,3 +52,75 @@ run_kubectl_delete_allnamespaces_tests() {
 | 
			
		||||
  set +o nounset
 | 
			
		||||
  set +o errexit
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Runs tests related to kubectl delete --confirm
 | 
			
		||||
run_kubectl_delete_interactive_tests() {
 | 
			
		||||
  set -o nounset
 | 
			
		||||
  set -o errexit
 | 
			
		||||
 | 
			
		||||
  # enable interactivity flag feature environment variable
 | 
			
		||||
  export KUBECTL_INTERACTIVE_DELETE=true
 | 
			
		||||
 | 
			
		||||
  ns_one="namespace-$(date +%s)-${RANDOM}"
 | 
			
		||||
  ns_two="namespace-$(date +%s)-${RANDOM}"
 | 
			
		||||
  kubectl create namespace "${ns_one}"
 | 
			
		||||
  kubectl create namespace "${ns_two}"
 | 
			
		||||
 | 
			
		||||
  # create configmaps
 | 
			
		||||
  kubectl create configmap "one" --namespace="${ns_one}"
 | 
			
		||||
  kubectl create configmap "two" --namespace="${ns_two}"
 | 
			
		||||
  kubectl label configmap "one" --namespace="${ns_one}" deletetest=true
 | 
			
		||||
  kubectl label configmap "two" --namespace="${ns_two}" deletetest=true
 | 
			
		||||
 | 
			
		||||
  # not confirm dry-run=server deletions
 | 
			
		||||
  output_message=$(kubectl delete configmap --dry-run=server --interactive -l deletetest=true --all-namespaces <<< $'n\n')
 | 
			
		||||
  kube::test::if_has_string "${output_message}" 'configmap/two' 'configmap/one'
 | 
			
		||||
  # confirm dry-run=server deletions
 | 
			
		||||
  kubectl delete configmap --dry-run=server --interactive -l deletetest=true --all-namespaces <<< $'y\n'
 | 
			
		||||
  # not confirm resource deletions
 | 
			
		||||
  output_message=$(kubectl delete configmap --interactive -l deletetest=true --all-namespaces <<< $'n\n')
 | 
			
		||||
  kube::test::if_has_string "${output_message}" 'configmap/two' 'configmap/one'
 | 
			
		||||
  kubectl config set-context "${CONTEXT}" --namespace="${ns_one}"
 | 
			
		||||
  kube::test::get_object_assert 'configmap -l deletetest' "{{range.items}}{{${id_field:?}}}:{{end}}" 'one:'
 | 
			
		||||
  kubectl config set-context "${CONTEXT}" --namespace="${ns_two}"
 | 
			
		||||
  kube::test::get_object_assert 'configmap -l deletetest' "{{range.items}}{{${id_field:?}}}:{{end}}" 'two:'
 | 
			
		||||
 | 
			
		||||
  # clean configmaps with label deletetest=true
 | 
			
		||||
  kubectl delete configmap -l deletetest=true --all-namespaces
 | 
			
		||||
 | 
			
		||||
  # create new configmaps
 | 
			
		||||
  kubectl create configmap "third" --namespace="${ns_one}"
 | 
			
		||||
  kubectl create configmap "fourth" --namespace="${ns_two}"
 | 
			
		||||
  kubectl label configmap "third" --namespace="${ns_one}" deletetest2=true
 | 
			
		||||
  kubectl label configmap "fourth" --namespace="${ns_two}" deletetest2=true
 | 
			
		||||
 | 
			
		||||
  # confirm all resource deletions with waiting
 | 
			
		||||
  kubectl delete configmaps --interactive -l deletetest2=true --all-namespaces --wait <<< $'y\n'
 | 
			
		||||
 | 
			
		||||
  # no configmaps should be in either of those namespaces with label deletetest2
 | 
			
		||||
  kubectl config set-context "${CONTEXT}" --namespace="${ns_one}"
 | 
			
		||||
  kube::test::get_object_assert 'configmap -l deletetest2' "{{range.items}}{{${id_field:?}}}:{{end}}" ''
 | 
			
		||||
  kubectl config set-context "${CONTEXT}" --namespace="${ns_two}"
 | 
			
		||||
  kube::test::get_object_assert 'configmap -l deletetest2' "{{range.items}}{{${id_field:?}}}:{{end}}" ''
 | 
			
		||||
 | 
			
		||||
  # clean configmaps with label deletetest2=true
 | 
			
		||||
  kubectl delete configmap -l deletetest2=true --all-namespaces
 | 
			
		||||
 | 
			
		||||
  # create new configmaps in one namespace
 | 
			
		||||
  kubectl create configmap "fifth" --namespace="${ns_one}"
 | 
			
		||||
  kubectl create configmap "sixth" --namespace="${ns_one}"
 | 
			
		||||
  kubectl label configmap "fifth" --namespace="${ns_one}" deletetest3=true
 | 
			
		||||
  kubectl label configmap "sixth" --namespace="${ns_one}" deletetest3=true
 | 
			
		||||
 | 
			
		||||
  # confirm all resource deletions with forcing and waiting
 | 
			
		||||
  kubectl delete configmaps -l deletetest3=true --force --interactive --namespace="${ns_one}" --wait <<< $'y\n'
 | 
			
		||||
 | 
			
		||||
  # no configmaps should be in either of those namespaces with label deletetest3
 | 
			
		||||
  kubectl config set-context "${CONTEXT}" --namespace="${ns_one}"
 | 
			
		||||
  kube::test::get_object_assert 'configmap -l deletetest3' "{{range.items}}{{${id_field:?}}}:{{end}}" ''
 | 
			
		||||
 | 
			
		||||
  unset KUBECTL_INTERACTIVE_DELETE
 | 
			
		||||
 | 
			
		||||
  set +o nounset
 | 
			
		||||
  set +o errexit
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -618,6 +618,13 @@ runTests() {
 | 
			
		||||
    record_command run_kubectl_delete_allnamespaces_tests
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  ######################
 | 
			
		||||
  # Delete --interactive   #
 | 
			
		||||
  ######################
 | 
			
		||||
  if kube::test::if_supports_resource "${configmaps}" ; then
 | 
			
		||||
    record_command run_kubectl_delete_interactive_tests
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  ##################
 | 
			
		||||
  # Global timeout #
 | 
			
		||||
  ##################
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user