...

Source file src/k8s.io/kubectl/pkg/cmd/delete/delete.go

Documentation: k8s.io/kubectl/pkg/cmd/delete

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package delete
    18  
    19  import (
    20  	"fmt"
    21  	"net/url"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/spf13/cobra"
    26  	"k8s.io/klog/v2"
    27  
    28  	"k8s.io/apimachinery/pkg/api/errors"
    29  	"k8s.io/apimachinery/pkg/api/meta"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/cli-runtime/pkg/genericclioptions"
    33  	"k8s.io/cli-runtime/pkg/genericiooptions"
    34  	"k8s.io/cli-runtime/pkg/printers"
    35  	"k8s.io/cli-runtime/pkg/resource"
    36  	"k8s.io/client-go/dynamic"
    37  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    38  	cmdwait "k8s.io/kubectl/pkg/cmd/wait"
    39  	"k8s.io/kubectl/pkg/rawhttp"
    40  	"k8s.io/kubectl/pkg/util/completion"
    41  	"k8s.io/kubectl/pkg/util/i18n"
    42  	"k8s.io/kubectl/pkg/util/templates"
    43  	"k8s.io/kubectl/pkg/util/term"
    44  )
    45  
    46  var (
    47  	deleteLong = templates.LongDesc(i18n.T(`
    48  		Delete resources by file names, stdin, resources and names, or by resources and label selector.
    49  
    50  		JSON and YAML formats are accepted. Only one type of argument may be specified: file names,
    51  		resources and names, or resources and label selector.
    52  
    53  		Some resources, such as pods, support graceful deletion. These resources define a default period
    54  		before they are forcibly terminated (the grace period) but you may override that value with
    55  		the --grace-period flag, or pass --now to set a grace-period of 1. Because these resources often
    56  		represent entities in the cluster, deletion may not be acknowledged immediately. If the node
    57  		hosting a pod is down or cannot reach the API server, termination may take significantly longer
    58  		than the grace period. To force delete a resource, you must specify the --force flag.
    59  		Note: only a subset of resources support graceful deletion. In absence of the support,
    60  		the --grace-period flag is ignored.
    61  
    62  		IMPORTANT: Force deleting pods does not wait for confirmation that the pod's processes have been
    63  		terminated, which can leave those processes running until the node detects the deletion and
    64  		completes graceful deletion. If your processes use shared storage or talk to a remote API and
    65  		depend on the name of the pod to identify themselves, force deleting those pods may result in
    66  		multiple processes running on different machines using the same identification which may lead
    67  		to data corruption or inconsistency. Only force delete pods when you are sure the pod is
    68  		terminated, or if your application can tolerate multiple copies of the same pod running at once.
    69  		Also, if you force delete pods, the scheduler may place new pods on those nodes before the node
    70  		has released those resources and causing those pods to be evicted immediately.
    71  
    72  		Note that the delete command does NOT do resource version checks, so if someone submits an
    73  		update to a resource right when you submit a delete, their update will be lost along with the
    74  		rest of the resource.
    75  
    76  		After a CustomResourceDefinition is deleted, invalidation of discovery cache may take up
    77  		to 6 hours. If you don't want to wait, you might want to run "kubectl api-resources" to refresh
    78  		the discovery cache.`))
    79  
    80  	deleteExample = templates.Examples(i18n.T(`
    81  		# Delete a pod using the type and name specified in pod.json
    82  		kubectl delete -f ./pod.json
    83  
    84  		# Delete resources from a directory containing kustomization.yaml - e.g. dir/kustomization.yaml
    85  		kubectl delete -k dir
    86  
    87  		# Delete resources from all files that end with '.json'
    88  		kubectl delete -f '*.json'
    89  
    90  		# Delete a pod based on the type and name in the JSON passed into stdin
    91  		cat pod.json | kubectl delete -f -
    92  
    93  		# Delete pods and services with same names "baz" and "foo"
    94  		kubectl delete pod,service baz foo
    95  
    96  		# Delete pods and services with label name=myLabel
    97  		kubectl delete pods,services -l name=myLabel
    98  
    99  		# Delete a pod with minimal delay
   100  		kubectl delete pod foo --now
   101  
   102  		# Force delete a pod on a dead node
   103  		kubectl delete pod foo --force
   104  
   105  		# Delete all pods
   106  		kubectl delete pods --all`))
   107  )
   108  
   109  type DeleteOptions struct {
   110  	resource.FilenameOptions
   111  
   112  	LabelSelector       string
   113  	FieldSelector       string
   114  	DeleteAll           bool
   115  	DeleteAllNamespaces bool
   116  	CascadingStrategy   metav1.DeletionPropagation
   117  	IgnoreNotFound      bool
   118  	DeleteNow           bool
   119  	ForceDeletion       bool
   120  	WaitForDeletion     bool
   121  	Quiet               bool
   122  	WarnClusterScope    bool
   123  	Raw                 string
   124  	Interactive         bool
   125  
   126  	GracePeriod int
   127  	Timeout     time.Duration
   128  
   129  	DryRunStrategy cmdutil.DryRunStrategy
   130  
   131  	Output string
   132  
   133  	DynamicClient      dynamic.Interface
   134  	Mapper             meta.RESTMapper
   135  	Result             *resource.Result
   136  	PreviewResult      *resource.Result
   137  	previewResourceMap map[cmdwait.ResourceLocation]struct{}
   138  
   139  	genericiooptions.IOStreams
   140  	WarningPrinter *printers.WarningPrinter
   141  }
   142  
   143  func NewCmdDelete(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   144  	deleteFlags := NewDeleteCommandFlags("containing the resource to delete.")
   145  
   146  	cmd := &cobra.Command{
   147  		Use:                   "delete ([-f FILENAME] | [-k DIRECTORY] | TYPE [(NAME | -l label | --all)])",
   148  		DisableFlagsInUseLine: true,
   149  		Short:                 i18n.T("Delete resources by file names, stdin, resources and names, or by resources and label selector"),
   150  		Long:                  deleteLong,
   151  		Example:               deleteExample,
   152  		ValidArgsFunction:     completion.ResourceTypeAndNameCompletionFunc(f),
   153  		Run: func(cmd *cobra.Command, args []string) {
   154  			o, err := deleteFlags.ToOptions(nil, streams)
   155  			cmdutil.CheckErr(err)
   156  			cmdutil.CheckErr(o.Complete(f, args, cmd))
   157  			cmdutil.CheckErr(o.Validate())
   158  			cmdutil.CheckErr(o.RunDelete(f))
   159  		},
   160  		SuggestFor: []string{"rm"},
   161  	}
   162  
   163  	deleteFlags.AddFlags(cmd)
   164  	cmdutil.AddDryRunFlag(cmd)
   165  
   166  	return cmd
   167  }
   168  
   169  func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Command) error {
   170  	cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
   171  	if err != nil {
   172  		return err
   173  	}
   174  
   175  	o.WarnClusterScope = enforceNamespace && !o.DeleteAllNamespaces
   176  
   177  	if o.DeleteAll || len(o.LabelSelector) > 0 || len(o.FieldSelector) > 0 {
   178  		if f := cmd.Flags().Lookup("ignore-not-found"); f != nil && !f.Changed {
   179  			// If the user didn't explicitly set the option, default to ignoring NotFound errors when used with --all, -l, or --field-selector
   180  			o.IgnoreNotFound = true
   181  		}
   182  	}
   183  	if o.DeleteNow {
   184  		if o.GracePeriod != -1 {
   185  			return fmt.Errorf("--now and --grace-period cannot be specified together")
   186  		}
   187  		o.GracePeriod = 1
   188  	}
   189  	if o.GracePeriod == 0 && !o.ForceDeletion {
   190  		// To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0
   191  		// into --grace-period=1. Users may provide --force to bypass this conversion.
   192  		o.GracePeriod = 1
   193  	}
   194  	if o.ForceDeletion && o.GracePeriod < 0 {
   195  		o.GracePeriod = 0
   196  	}
   197  
   198  	o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
   199  	if err != nil {
   200  		return err
   201  	}
   202  
   203  	// Set default WarningPrinter if not already set.
   204  	if o.WarningPrinter == nil {
   205  		o.WarningPrinter = printers.NewWarningPrinter(o.ErrOut, printers.WarningPrinterOptions{Color: term.AllowsColorOutput(o.ErrOut)})
   206  	}
   207  
   208  	if len(o.Raw) != 0 {
   209  		return nil
   210  	}
   211  
   212  	r := f.NewBuilder().
   213  		Unstructured().
   214  		ContinueOnError().
   215  		NamespaceParam(cmdNamespace).DefaultNamespace().
   216  		FilenameParam(enforceNamespace, &o.FilenameOptions).
   217  		LabelSelectorParam(o.LabelSelector).
   218  		FieldSelectorParam(o.FieldSelector).
   219  		SelectAllParam(o.DeleteAll).
   220  		AllNamespaces(o.DeleteAllNamespaces).
   221  		ResourceTypeOrNameArgs(false, args...).RequireObject(false).
   222  		Flatten().
   223  		Do()
   224  	err = r.Err()
   225  	if err != nil {
   226  		return err
   227  	}
   228  	o.Result = r
   229  
   230  	if o.Interactive {
   231  		// preview result will be used to list resources for confirmation prior to actual delete.
   232  		// We can not use r as result object because it can only be used once. But we need to traverse
   233  		// twice. Parameters in preview result must be equal to genuine result.
   234  		previewr := f.NewBuilder().
   235  			Unstructured().
   236  			ContinueOnError().
   237  			NamespaceParam(cmdNamespace).DefaultNamespace().
   238  			FilenameParam(enforceNamespace, &o.FilenameOptions).
   239  			LabelSelectorParam(o.LabelSelector).
   240  			FieldSelectorParam(o.FieldSelector).
   241  			SelectAllParam(o.DeleteAll).
   242  			AllNamespaces(o.DeleteAllNamespaces).
   243  			ResourceTypeOrNameArgs(false, args...).RequireObject(false).
   244  			Flatten().
   245  			Do()
   246  		err = previewr.Err()
   247  		if err != nil {
   248  			return err
   249  		}
   250  		o.PreviewResult = previewr
   251  		o.previewResourceMap = make(map[cmdwait.ResourceLocation]struct{})
   252  	}
   253  
   254  	o.Mapper, err = f.ToRESTMapper()
   255  	if err != nil {
   256  		return err
   257  	}
   258  
   259  	o.DynamicClient, err = f.DynamicClient()
   260  	if err != nil {
   261  		return err
   262  	}
   263  
   264  	return nil
   265  }
   266  
   267  func (o *DeleteOptions) Validate() error {
   268  	if o.Output != "" && o.Output != "name" {
   269  		return fmt.Errorf("unexpected -o output mode: %v. We only support '-o name'", o.Output)
   270  	}
   271  
   272  	if o.DeleteAll && len(o.LabelSelector) > 0 {
   273  		return fmt.Errorf("cannot set --all and --selector at the same time")
   274  	}
   275  	if o.DeleteAll && len(o.FieldSelector) > 0 {
   276  		return fmt.Errorf("cannot set --all and --field-selector at the same time")
   277  	}
   278  	if o.WarningPrinter == nil {
   279  		return fmt.Errorf("WarningPrinter can not be used without initialization")
   280  	}
   281  
   282  	switch {
   283  	case o.GracePeriod == 0 && o.ForceDeletion:
   284  		o.WarningPrinter.Print("Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.")
   285  	case o.GracePeriod > 0 && o.ForceDeletion:
   286  		return fmt.Errorf("--force and --grace-period greater than 0 cannot be specified together")
   287  	}
   288  
   289  	if len(o.Raw) == 0 {
   290  		return nil
   291  	}
   292  
   293  	if o.Interactive {
   294  		return fmt.Errorf("--interactive can not be used with --raw")
   295  	}
   296  	if len(o.FilenameOptions.Filenames) > 1 {
   297  		return fmt.Errorf("--raw can only use a single local file or stdin")
   298  	} else if len(o.FilenameOptions.Filenames) == 1 {
   299  		if strings.Index(o.FilenameOptions.Filenames[0], "http://") == 0 || strings.Index(o.FilenameOptions.Filenames[0], "https://") == 0 {
   300  			return fmt.Errorf("--raw cannot read from a url")
   301  		}
   302  	}
   303  
   304  	if o.FilenameOptions.Recursive {
   305  		return fmt.Errorf("--raw and --recursive are mutually exclusive")
   306  	}
   307  	if len(o.Output) > 0 {
   308  		return fmt.Errorf("--raw and --output are mutually exclusive")
   309  	}
   310  	if _, err := url.ParseRequestURI(o.Raw); err != nil {
   311  		return fmt.Errorf("--raw must be a valid URL path: %v", err)
   312  	}
   313  
   314  	return nil
   315  }
   316  
   317  func (o *DeleteOptions) RunDelete(f cmdutil.Factory) error {
   318  	if len(o.Raw) > 0 {
   319  		restClient, err := f.RESTClient()
   320  		if err != nil {
   321  			return err
   322  		}
   323  		if len(o.Filenames) == 0 {
   324  			return rawhttp.RawDelete(restClient, o.IOStreams, o.Raw, "")
   325  		}
   326  		return rawhttp.RawDelete(restClient, o.IOStreams, o.Raw, o.Filenames[0])
   327  	}
   328  
   329  	if o.Interactive {
   330  		previewInfos := []*resource.Info{}
   331  		if o.IgnoreNotFound {
   332  			o.PreviewResult = o.PreviewResult.IgnoreErrors(errors.IsNotFound)
   333  		}
   334  		err := o.PreviewResult.Visit(func(info *resource.Info, err error) error {
   335  			if err != nil {
   336  				return err
   337  			}
   338  			previewInfos = append(previewInfos, info)
   339  			o.previewResourceMap[cmdwait.ResourceLocation{
   340  				GroupResource: info.Mapping.Resource.GroupResource(),
   341  				Namespace:     info.Namespace,
   342  				Name:          info.Name,
   343  			}] = struct{}{}
   344  
   345  			return nil
   346  		})
   347  		if err != nil {
   348  			return err
   349  		}
   350  		if len(previewInfos) == 0 {
   351  			fmt.Fprintf(o.Out, "No resources found\n")
   352  			return nil
   353  		}
   354  
   355  		if !o.confirmation(previewInfos) {
   356  			fmt.Fprintf(o.Out, "deletion is cancelled\n")
   357  			return nil
   358  		}
   359  	}
   360  
   361  	return o.DeleteResult(o.Result)
   362  }
   363  
   364  func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
   365  	found := 0
   366  	if o.IgnoreNotFound {
   367  		r = r.IgnoreErrors(errors.IsNotFound)
   368  	}
   369  	warnClusterScope := o.WarnClusterScope
   370  	deletedInfos := []*resource.Info{}
   371  	uidMap := cmdwait.UIDMap{}
   372  	err := r.Visit(func(info *resource.Info, err error) error {
   373  		if err != nil {
   374  			return err
   375  		}
   376  
   377  		if o.Interactive {
   378  			if _, ok := o.previewResourceMap[cmdwait.ResourceLocation{
   379  				GroupResource: info.Mapping.Resource.GroupResource(),
   380  				Namespace:     info.Namespace,
   381  				Name:          info.Name,
   382  			}]; !ok {
   383  				// resource not in the list of previewed resources based on resourceLocation
   384  				return nil
   385  			}
   386  		}
   387  
   388  		deletedInfos = append(deletedInfos, info)
   389  		found++
   390  
   391  		options := &metav1.DeleteOptions{}
   392  		if o.GracePeriod >= 0 {
   393  			options = metav1.NewDeleteOptions(int64(o.GracePeriod))
   394  		}
   395  		options.PropagationPolicy = &o.CascadingStrategy
   396  
   397  		if warnClusterScope && info.Mapping.Scope.Name() == meta.RESTScopeNameRoot {
   398  			o.WarningPrinter.Print("deleting cluster-scoped resources, not scoped to the provided namespace")
   399  			warnClusterScope = false
   400  		}
   401  
   402  		if o.DryRunStrategy == cmdutil.DryRunClient {
   403  			if !o.Quiet {
   404  				o.PrintObj(info)
   405  			}
   406  			return nil
   407  		}
   408  		response, err := o.deleteResource(info, options)
   409  		if err != nil {
   410  			return err
   411  		}
   412  		resourceLocation := cmdwait.ResourceLocation{
   413  			GroupResource: info.Mapping.Resource.GroupResource(),
   414  			Namespace:     info.Namespace,
   415  			Name:          info.Name,
   416  		}
   417  		if status, ok := response.(*metav1.Status); ok && status.Details != nil {
   418  			uidMap[resourceLocation] = status.Details.UID
   419  			return nil
   420  		}
   421  		responseMetadata, err := meta.Accessor(response)
   422  		if err != nil {
   423  			// we don't have UID, but we didn't fail the delete, next best thing is just skipping the UID
   424  			klog.V(1).Info(err)
   425  			return nil
   426  		}
   427  		uidMap[resourceLocation] = responseMetadata.GetUID()
   428  
   429  		return nil
   430  	})
   431  	if err != nil {
   432  		return err
   433  	}
   434  	if found == 0 {
   435  		fmt.Fprintf(o.Out, "No resources found\n")
   436  		return nil
   437  	}
   438  	if !o.WaitForDeletion {
   439  		return nil
   440  	}
   441  	// if we don't have a dynamic client, we don't want to wait.  Eventually when delete is cleaned up, this will likely
   442  	// drop out.
   443  	if o.DynamicClient == nil {
   444  		return nil
   445  	}
   446  
   447  	// If we are dry-running, then we don't want to wait
   448  	if o.DryRunStrategy != cmdutil.DryRunNone {
   449  		return nil
   450  	}
   451  
   452  	effectiveTimeout := o.Timeout
   453  	if effectiveTimeout == 0 {
   454  		// if we requested to wait forever, set it to a week.
   455  		effectiveTimeout = 168 * time.Hour
   456  	}
   457  	waitOptions := cmdwait.WaitOptions{
   458  		ResourceFinder: genericclioptions.ResourceFinderForResult(resource.InfoListVisitor(deletedInfos)),
   459  		UIDMap:         uidMap,
   460  		DynamicClient:  o.DynamicClient,
   461  		Timeout:        effectiveTimeout,
   462  
   463  		Printer:     printers.NewDiscardingPrinter(),
   464  		ConditionFn: cmdwait.IsDeleted,
   465  		IOStreams:   o.IOStreams,
   466  	}
   467  	err = waitOptions.RunWait()
   468  	if errors.IsForbidden(err) || errors.IsMethodNotSupported(err) {
   469  		// if we're forbidden from waiting, we shouldn't fail.
   470  		// if the resource doesn't support a verb we need, we shouldn't fail.
   471  		klog.V(1).Info(err)
   472  		return nil
   473  	}
   474  	return err
   475  }
   476  
   477  func (o *DeleteOptions) deleteResource(info *resource.Info, deleteOptions *metav1.DeleteOptions) (runtime.Object, error) {
   478  	deleteResponse, err := resource.
   479  		NewHelper(info.Client, info.Mapping).
   480  		DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
   481  		DeleteWithOptions(info.Namespace, info.Name, deleteOptions)
   482  	if err != nil {
   483  		return nil, cmdutil.AddSourceToErr("deleting", info.Source, err)
   484  	}
   485  
   486  	if !o.Quiet {
   487  		o.PrintObj(info)
   488  	}
   489  	return deleteResponse, nil
   490  }
   491  
   492  // PrintObj for deleted objects is special because we do not have an object to print.
   493  // This mirrors name printer behavior
   494  func (o *DeleteOptions) PrintObj(info *resource.Info) {
   495  	operation := "deleted"
   496  	groupKind := info.Mapping.GroupVersionKind
   497  	kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group)
   498  	if len(groupKind.Group) == 0 {
   499  		kindString = strings.ToLower(groupKind.Kind)
   500  	}
   501  
   502  	if o.GracePeriod == 0 {
   503  		operation = "force deleted"
   504  	}
   505  
   506  	switch o.DryRunStrategy {
   507  	case cmdutil.DryRunClient:
   508  		operation = fmt.Sprintf("%s (dry run)", operation)
   509  	case cmdutil.DryRunServer:
   510  		operation = fmt.Sprintf("%s (server dry run)", operation)
   511  	}
   512  
   513  	if o.Output == "name" {
   514  		// -o name: prints resource/name
   515  		fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
   516  		return
   517  	}
   518  
   519  	// understandable output by default
   520  	fmt.Fprintf(o.Out, "%s \"%s\" %s\n", kindString, info.Name, operation)
   521  }
   522  
   523  func (o *DeleteOptions) confirmation(infos []*resource.Info) bool {
   524  	fmt.Fprintf(o.Out, i18n.T("You are about to delete the following %d resource(s):\n"), len(infos))
   525  	for _, info := range infos {
   526  		groupKind := info.Mapping.GroupVersionKind
   527  		kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group)
   528  		if len(groupKind.Group) == 0 {
   529  			kindString = strings.ToLower(groupKind.Kind)
   530  		}
   531  
   532  		fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
   533  	}
   534  	fmt.Fprintf(o.Out, i18n.T("Do you want to continue?")+" (y/n): ")
   535  	var input string
   536  	_, err := fmt.Fscan(o.In, &input)
   537  	if err != nil {
   538  		return false
   539  	}
   540  
   541  	return strings.EqualFold(input, "y")
   542  }
   543  

View as plain text