mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 13:50:01 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			346 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			346 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2014 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package delete
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/spf13/cobra"
 | |
| 	"k8s.io/klog"
 | |
| 
 | |
| 	"k8s.io/apimachinery/pkg/api/errors"
 | |
| 	"k8s.io/apimachinery/pkg/api/meta"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	"k8s.io/apimachinery/pkg/runtime"
 | |
| 	"k8s.io/cli-runtime/pkg/genericclioptions"
 | |
| 	"k8s.io/cli-runtime/pkg/genericclioptions/printers"
 | |
| 	"k8s.io/cli-runtime/pkg/genericclioptions/resource"
 | |
| 	"k8s.io/client-go/dynamic"
 | |
| 	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
 | |
| 	cmdwait "k8s.io/kubernetes/pkg/kubectl/cmd/wait"
 | |
| 	"k8s.io/kubernetes/pkg/kubectl/util/i18n"
 | |
| 	"k8s.io/kubernetes/pkg/kubectl/util/templates"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	deleteLong = templates.LongDesc(i18n.T(`
 | |
| 		Delete resources by filenames, stdin, resources and names, or by resources and label selector.
 | |
| 
 | |
| 		JSON and YAML formats are accepted. Only one type of the arguments may be specified: filenames,
 | |
| 		resources and names, or resources and label selector.
 | |
| 
 | |
| 		Some resources, such as pods, support graceful deletion. These resources define a default period
 | |
| 		before they are forcibly terminated (the grace period) but you may override that value with
 | |
| 		the --grace-period flag, or pass --now to set a grace-period of 1. Because these resources often
 | |
| 		represent entities in the cluster, deletion may not be acknowledged immediately. If the node
 | |
| 		hosting a pod is down or cannot reach the API server, termination may take significantly longer
 | |
| 		than the grace period. To force delete a resource, you must pass a grace period of 0 and specify
 | |
| 		the --force flag.
 | |
| 
 | |
| 		IMPORTANT: Force deleting pods does not wait for confirmation that the pod's processes have been
 | |
| 		terminated, which can leave those processes running until the node detects the deletion and
 | |
| 		completes graceful deletion. If your processes use shared storage or talk to a remote API and
 | |
| 		depend on the name of the pod to identify themselves, force deleting those pods may result in
 | |
| 		multiple processes running on different machines using the same identification which may lead
 | |
| 		to data corruption or inconsistency. Only force delete pods when you are sure the pod is
 | |
| 		terminated, or if your application can tolerate multiple copies of the same pod running at once.
 | |
| 		Also, if you force delete pods the scheduler may place new pods on those nodes before the node
 | |
| 		has released those resources and causing those pods to be evicted immediately.
 | |
| 
 | |
| 		Note that the delete command does NOT do resource version checks, so if someone submits an
 | |
| 		update to a resource right when you submit a delete, their update will be lost along with the
 | |
| 		rest of the resource.`))
 | |
| 
 | |
| 	deleteExample = templates.Examples(i18n.T(`
 | |
| 		# Delete a pod using the type and name specified in pod.json.
 | |
| 		kubectl delete -f ./pod.json
 | |
| 
 | |
| 		# Delete resources from a directory containing kustomization.yaml - e.g. dir/kustomization.yaml.
 | |
| 		kubectl delete -k dir
 | |
| 
 | |
| 		# Delete a pod based on the type and name in the JSON passed into stdin.
 | |
| 		cat pod.json | kubectl delete -f -
 | |
| 
 | |
| 		# Delete pods and services with same names "baz" and "foo"
 | |
| 		kubectl delete pod,service baz foo
 | |
| 
 | |
| 		# Delete pods and services with label name=myLabel.
 | |
| 		kubectl delete pods,services -l name=myLabel
 | |
| 
 | |
| 		# Delete a pod with minimal delay
 | |
| 		kubectl delete pod foo --now
 | |
| 
 | |
| 		# Force delete a pod on a dead node
 | |
| 		kubectl delete pod foo --grace-period=0 --force
 | |
| 
 | |
| 		# Delete all pods
 | |
| 		kubectl delete pods --all`))
 | |
| )
 | |
| 
 | |
| type DeleteOptions struct {
 | |
| 	resource.FilenameOptions
 | |
| 
 | |
| 	LabelSelector       string
 | |
| 	FieldSelector       string
 | |
| 	DeleteAll           bool
 | |
| 	DeleteAllNamespaces bool
 | |
| 	IgnoreNotFound      bool
 | |
| 	Cascade             bool
 | |
| 	DeleteNow           bool
 | |
| 	ForceDeletion       bool
 | |
| 	WaitForDeletion     bool
 | |
| 
 | |
| 	GracePeriod int
 | |
| 	Timeout     time.Duration
 | |
| 
 | |
| 	Output string
 | |
| 
 | |
| 	DynamicClient dynamic.Interface
 | |
| 	Mapper        meta.RESTMapper
 | |
| 	Result        *resource.Result
 | |
| 
 | |
| 	genericclioptions.IOStreams
 | |
| }
 | |
| 
 | |
| func NewCmdDelete(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
 | |
| 	deleteFlags := NewDeleteCommandFlags("containing the resource to delete.")
 | |
| 
 | |
| 	cmd := &cobra.Command{
 | |
| 		Use:                   "delete ([-f FILENAME] | [-k DIRECTORY] | TYPE [(NAME | -l label | --all)])",
 | |
| 		DisableFlagsInUseLine: true,
 | |
| 		Short:                 i18n.T("Delete resources by filenames, stdin, resources and names, or by resources and label selector"),
 | |
| 		Long:                  deleteLong,
 | |
| 		Example:               deleteExample,
 | |
| 		Run: func(cmd *cobra.Command, args []string) {
 | |
| 			o := deleteFlags.ToOptions(nil, streams)
 | |
| 			cmdutil.CheckErr(o.Complete(f, args, cmd))
 | |
| 			cmdutil.CheckErr(o.Validate())
 | |
| 			cmdutil.CheckErr(o.RunDelete())
 | |
| 		},
 | |
| 		SuggestFor: []string{"rm"},
 | |
| 	}
 | |
| 
 | |
| 	deleteFlags.AddFlags(cmd)
 | |
| 
 | |
| 	cmdutil.AddIncludeUninitializedFlag(cmd)
 | |
| 	return cmd
 | |
| }
 | |
| 
 | |
| func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Command) error {
 | |
| 	cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if o.DeleteAll || len(o.LabelSelector) > 0 || len(o.FieldSelector) > 0 {
 | |
| 		if f := cmd.Flags().Lookup("ignore-not-found"); f != nil && !f.Changed {
 | |
| 			// If the user didn't explicitly set the option, default to ignoring NotFound errors when used with --all, -l, or --field-selector
 | |
| 			o.IgnoreNotFound = true
 | |
| 		}
 | |
| 	}
 | |
| 	if o.DeleteNow {
 | |
| 		if o.GracePeriod != -1 {
 | |
| 			return fmt.Errorf("--now and --grace-period cannot be specified together")
 | |
| 		}
 | |
| 		o.GracePeriod = 1
 | |
| 	}
 | |
| 	if o.GracePeriod == 0 && !o.ForceDeletion {
 | |
| 		// To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0
 | |
| 		// into --grace-period=1. Users may provide --force to bypass this conversion.
 | |
| 		o.GracePeriod = 1
 | |
| 	}
 | |
| 
 | |
| 	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
 | |
| 
 | |
| 	o.Mapper, err = f.ToRESTMapper()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	o.DynamicClient, err = f.DynamicClient()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (o *DeleteOptions) Validate() error {
 | |
| 	if o.Output != "" && o.Output != "name" {
 | |
| 		return fmt.Errorf("unexpected -o output mode: %v. We only support '-o name'", o.Output)
 | |
| 	}
 | |
| 
 | |
| 	if o.DeleteAll && len(o.LabelSelector) > 0 {
 | |
| 		return fmt.Errorf("cannot set --all and --selector at the same time")
 | |
| 	}
 | |
| 	if o.DeleteAll && len(o.FieldSelector) > 0 {
 | |
| 		return fmt.Errorf("cannot set --all and --field-selector at the same time")
 | |
| 	}
 | |
| 
 | |
| 	switch {
 | |
| 	case o.GracePeriod == 0 && o.ForceDeletion:
 | |
| 		fmt.Fprintf(o.ErrOut, "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n")
 | |
| 	case o.ForceDeletion:
 | |
| 		fmt.Fprintf(o.ErrOut, "warning: --force is ignored because --grace-period is not 0.\n")
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (o *DeleteOptions) RunDelete() error {
 | |
| 	return o.DeleteResult(o.Result)
 | |
| }
 | |
| 
 | |
| func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
 | |
| 	found := 0
 | |
| 	if o.IgnoreNotFound {
 | |
| 		r = r.IgnoreErrors(errors.IsNotFound)
 | |
| 	}
 | |
| 	deletedInfos := []*resource.Info{}
 | |
| 	uidMap := cmdwait.UIDMap{}
 | |
| 	err := r.Visit(func(info *resource.Info, err error) error {
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		deletedInfos = append(deletedInfos, info)
 | |
| 		found++
 | |
| 
 | |
| 		options := &metav1.DeleteOptions{}
 | |
| 		if o.GracePeriod >= 0 {
 | |
| 			options = metav1.NewDeleteOptions(int64(o.GracePeriod))
 | |
| 		}
 | |
| 		policy := metav1.DeletePropagationBackground
 | |
| 		if !o.Cascade {
 | |
| 			policy = metav1.DeletePropagationOrphan
 | |
| 		}
 | |
| 		options.PropagationPolicy = &policy
 | |
| 
 | |
| 		response, err := o.deleteResource(info, options)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		resourceLocation := cmdwait.ResourceLocation{
 | |
| 			GroupResource: info.Mapping.Resource.GroupResource(),
 | |
| 			Namespace:     info.Namespace,
 | |
| 			Name:          info.Name,
 | |
| 		}
 | |
| 		if status, ok := response.(*metav1.Status); ok && status.Details != nil {
 | |
| 			uidMap[resourceLocation] = status.Details.UID
 | |
| 			return nil
 | |
| 		}
 | |
| 		responseMetadata, err := meta.Accessor(response)
 | |
| 		if err != nil {
 | |
| 			// we don't have UID, but we didn't fail the delete, next best thing is just skipping the UID
 | |
| 			klog.V(1).Info(err)
 | |
| 			return nil
 | |
| 		}
 | |
| 		uidMap[resourceLocation] = responseMetadata.GetUID()
 | |
| 
 | |
| 		return nil
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if found == 0 {
 | |
| 		fmt.Fprintf(o.Out, "No resources found\n")
 | |
| 		return nil
 | |
| 	}
 | |
| 	if !o.WaitForDeletion {
 | |
| 		return nil
 | |
| 	}
 | |
| 	// if we don't have a dynamic client, we don't want to wait.  Eventually when delete is cleaned up, this will likely
 | |
| 	// drop out.
 | |
| 	if o.DynamicClient == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	effectiveTimeout := o.Timeout
 | |
| 	if effectiveTimeout == 0 {
 | |
| 		// if we requested to wait forever, set it to a week.
 | |
| 		effectiveTimeout = 168 * time.Hour
 | |
| 	}
 | |
| 	waitOptions := cmdwait.WaitOptions{
 | |
| 		ResourceFinder: genericclioptions.ResourceFinderForResult(resource.InfoListVisitor(deletedInfos)),
 | |
| 		UIDMap:         uidMap,
 | |
| 		DynamicClient:  o.DynamicClient,
 | |
| 		Timeout:        effectiveTimeout,
 | |
| 
 | |
| 		Printer:     printers.NewDiscardingPrinter(),
 | |
| 		ConditionFn: cmdwait.IsDeleted,
 | |
| 		IOStreams:   o.IOStreams,
 | |
| 	}
 | |
| 	err = waitOptions.RunWait()
 | |
| 	if errors.IsForbidden(err) || errors.IsMethodNotSupported(err) {
 | |
| 		// if we're forbidden from waiting, we shouldn't fail.
 | |
| 		// if the resource doesn't support a verb we need, we shouldn't fail.
 | |
| 		klog.V(1).Info(err)
 | |
| 		return nil
 | |
| 	}
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (o *DeleteOptions) deleteResource(info *resource.Info, deleteOptions *metav1.DeleteOptions) (runtime.Object, error) {
 | |
| 	deleteResponse, err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, deleteOptions)
 | |
| 	if err != nil {
 | |
| 		return nil, cmdutil.AddSourceToErr("deleting", info.Source, err)
 | |
| 	}
 | |
| 
 | |
| 	o.PrintObj(info)
 | |
| 	return deleteResponse, nil
 | |
| }
 | |
| 
 | |
| // PrintObj for deleted objects is special because we do not have an object to print.
 | |
| // This mirrors name printer behavior
 | |
| func (o *DeleteOptions) PrintObj(info *resource.Info) {
 | |
| 	operation := "deleted"
 | |
| 	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)
 | |
| 	}
 | |
| 
 | |
| 	if o.GracePeriod == 0 {
 | |
| 		operation = "force deleted"
 | |
| 	}
 | |
| 
 | |
| 	if o.Output == "name" {
 | |
| 		// -o name: prints resource/name
 | |
| 		fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// understandable output by default
 | |
| 	fmt.Fprintf(o.Out, "%s \"%s\" %s\n", kindString, info.Name, operation)
 | |
| }
 |