mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-09 12:07:47 +00:00
kubectl delete: Introduce new interactive flag for interactive deletion (#114530)
This commit is contained in:
parent
86038ae590
commit
3267dd9d52
@ -121,6 +121,7 @@ type DeleteOptions struct {
|
||||
Quiet bool
|
||||
WarnClusterScope bool
|
||||
Raw string
|
||||
Interactive bool
|
||||
|
||||
GracePeriod int
|
||||
Timeout time.Duration
|
||||
@ -129,9 +130,11 @@ type DeleteOptions struct {
|
||||
|
||||
Output string
|
||||
|
||||
DynamicClient dynamic.Interface
|
||||
Mapper meta.RESTMapper
|
||||
Result *resource.Result
|
||||
DynamicClient dynamic.Interface
|
||||
Mapper meta.RESTMapper
|
||||
Result *resource.Result
|
||||
PreviewResult *resource.Result
|
||||
previewResourceMap map[cmdwait.ResourceLocation]struct{}
|
||||
|
||||
genericiooptions.IOStreams
|
||||
WarningPrinter *printers.WarningPrinter
|
||||
@ -197,8 +200,38 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Co
|
||||
return err
|
||||
}
|
||||
|
||||
if len(o.Raw) == 0 {
|
||||
r := f.NewBuilder().
|
||||
// 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().
|
||||
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 = r.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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().
|
||||
@ -210,26 +243,22 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Co
|
||||
ResourceTypeOrNameArgs(false, args...).RequireObject(false).
|
||||
Flatten().
|
||||
Do()
|
||||
err = r.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Result = r
|
||||
|
||||
o.Mapper, err = f.ToRESTMapper()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.DynamicClient, err = f.DynamicClient()
|
||||
err = previewr.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PreviewResult = previewr
|
||||
o.previewResourceMap = make(map[cmdwait.ResourceLocation]struct{})
|
||||
}
|
||||
|
||||
// Set default WarningPrinter if not already set.
|
||||
if o.WarningPrinter == nil {
|
||||
o.WarningPrinter = printers.NewWarningPrinter(o.ErrOut, printers.WarningPrinterOptions{Color: term.AllowsColorOutput(o.ErrOut)})
|
||||
o.Mapper, err = f.ToRESTMapper()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.DynamicClient, err = f.DynamicClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -257,26 +286,31 @@ 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.FilenameOptions.Filenames) > 1 {
|
||||
return fmt.Errorf("--raw can only use a single local file or stdin")
|
||||
} else if len(o.FilenameOptions.Filenames) == 1 {
|
||||
if strings.Index(o.FilenameOptions.Filenames[0], "http://") == 0 || strings.Index(o.FilenameOptions.Filenames[0], "https://") == 0 {
|
||||
return fmt.Errorf("--raw cannot read from a url")
|
||||
}
|
||||
}
|
||||
if len(o.Raw) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if o.FilenameOptions.Recursive {
|
||||
return fmt.Errorf("--raw and --recursive are mutually exclusive")
|
||||
}
|
||||
if len(o.Output) > 0 {
|
||||
return fmt.Errorf("--raw and --output are mutually exclusive")
|
||||
}
|
||||
if _, err := url.ParseRequestURI(o.Raw); err != nil {
|
||||
return fmt.Errorf("--raw must be a valid URL path: %v", err)
|
||||
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 {
|
||||
if strings.Index(o.FilenameOptions.Filenames[0], "http://") == 0 || strings.Index(o.FilenameOptions.Filenames[0], "https://") == 0 {
|
||||
return fmt.Errorf("--raw cannot read from a url")
|
||||
}
|
||||
}
|
||||
|
||||
if o.FilenameOptions.Recursive {
|
||||
return fmt.Errorf("--raw and --recursive are mutually exclusive")
|
||||
}
|
||||
if len(o.Output) > 0 {
|
||||
return fmt.Errorf("--raw and --output are mutually exclusive")
|
||||
}
|
||||
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 #
|
||||
##################
|
||||
|
Loading…
Reference in New Issue
Block a user