mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-04 07:49:35 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			382 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			382 lines
		
	
	
		
			12 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 cmd
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/golang/glog"
 | 
						|
	"github.com/spf13/cobra"
 | 
						|
 | 
						|
	appsv1 "k8s.io/api/apps/v1"
 | 
						|
	"k8s.io/apimachinery/pkg/api/errors"
 | 
						|
	"k8s.io/apimachinery/pkg/api/meta"
 | 
						|
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						|
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 | 
						|
	"k8s.io/apimachinery/pkg/runtime"
 | 
						|
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
						|
	"k8s.io/apimachinery/pkg/util/uuid"
 | 
						|
	"k8s.io/apimachinery/pkg/util/wait"
 | 
						|
	"k8s.io/client-go/dynamic"
 | 
						|
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
 | 
						|
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
 | 
						|
	kubectlwait "k8s.io/kubernetes/pkg/kubectl/cmd/wait"
 | 
						|
	"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
 | 
						|
	"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
 | 
						|
	"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
 | 
						|
	"k8s.io/kubernetes/pkg/kubectl/util/i18n"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	delete_long = 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.`))
 | 
						|
 | 
						|
	delete_example = templates.Examples(i18n.T(`
 | 
						|
		# Delete a pod using the type and name specified in pod.json.
 | 
						|
		kubectl delete -f ./pod.json
 | 
						|
 | 
						|
		# 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
 | 
						|
	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] | 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:    delete_long,
 | 
						|
		Example: delete_example,
 | 
						|
		Run: func(cmd *cobra.Command, args []string) {
 | 
						|
			o := deleteFlags.ToOptions(nil, streams)
 | 
						|
			cmdutil.CheckErr(o.Complete(f, args, cmd))
 | 
						|
			cmdutil.CheckErr(o.Validate(cmd))
 | 
						|
			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
 | 
						|
	}
 | 
						|
 | 
						|
	includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
 | 
						|
	r := f.NewBuilder().
 | 
						|
		Unstructured().
 | 
						|
		ContinueOnError().
 | 
						|
		NamespaceParam(cmdNamespace).DefaultNamespace().
 | 
						|
		FilenameParam(enforceNamespace, &o.FilenameOptions).
 | 
						|
		LabelSelectorParam(o.LabelSelector).
 | 
						|
		FieldSelectorParam(o.FieldSelector).
 | 
						|
		IncludeUninitialized(includeUninitialized).
 | 
						|
		SelectAllParam(o.DeleteAll).
 | 
						|
		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(cmd *cobra.Command) error {
 | 
						|
	if o.Output != "" && o.Output != "name" {
 | 
						|
		return cmdutil.UsageErrorf(cmd, "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")
 | 
						|
	}
 | 
						|
 | 
						|
	if o.GracePeriod == 0 && !o.ForceDeletion && !o.WaitForDeletion {
 | 
						|
		// With the explicit --wait flag we need extra validation for backward compatibility
 | 
						|
		return fmt.Errorf("--grace-period=0 must have either --force specified, or --wait to be set to true")
 | 
						|
	}
 | 
						|
 | 
						|
	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)
 | 
						|
	}
 | 
						|
	err := r.Visit(func(info *resource.Info, err error) error {
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		found++
 | 
						|
 | 
						|
		options := &metav1.DeleteOptions{}
 | 
						|
		if o.GracePeriod >= 0 {
 | 
						|
			options = metav1.NewDeleteOptions(int64(o.GracePeriod))
 | 
						|
		}
 | 
						|
		policy := metav1.DeletePropagationForeground
 | 
						|
		if !o.Cascade {
 | 
						|
			policy = metav1.DeletePropagationOrphan
 | 
						|
		}
 | 
						|
		options.PropagationPolicy = &policy
 | 
						|
		return o.deleteResource(info, options)
 | 
						|
	})
 | 
						|
	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 := kubectlwait.WaitOptions{
 | 
						|
		ResourceFinder: genericclioptions.ResourceFinderForResult(r),
 | 
						|
		DynamicClient:  o.DynamicClient,
 | 
						|
		Timeout:        effectiveTimeout,
 | 
						|
 | 
						|
		Printer:     printers.NewDiscardingPrinter(),
 | 
						|
		ConditionFn: kubectlwait.IsDeleted,
 | 
						|
		IOStreams:   o.IOStreams,
 | 
						|
	}
 | 
						|
	err = waitOptions.RunWait()
 | 
						|
	if errors.IsForbidden(err) {
 | 
						|
		// if we're forbidden from waiting, we shouldn't fail.
 | 
						|
		glog.V(1).Info(err)
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
func (o *DeleteOptions) deleteResource(info *resource.Info, deleteOptions *metav1.DeleteOptions) error {
 | 
						|
	// TODO: this should be removed as soon as DaemonSet controller properly handles object deletion
 | 
						|
	// see https://github.com/kubernetes/kubernetes/issues/64313 for details
 | 
						|
	mapping := info.ResourceMapping()
 | 
						|
	if mapping.Resource.GroupResource() == (schema.GroupResource{Group: "extensions", Resource: "daemonsets"}) ||
 | 
						|
		mapping.Resource.GroupResource() == (schema.GroupResource{Group: "apps", Resource: "daemonsets"}) {
 | 
						|
		if err := updateDaemonSet(info.Namespace, info.Name, o.DynamicClient); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, deleteOptions); err != nil {
 | 
						|
		return cmdutil.AddSourceToErr("deleting", info.Source, err)
 | 
						|
	}
 | 
						|
 | 
						|
	o.PrintObj(info)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func updateDaemonSet(namespace, name string, dynamicClient dynamic.Interface) error {
 | 
						|
	dsClient := dynamicClient.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "daemonsets"}).Namespace(namespace)
 | 
						|
	obj, err := dsClient.Get(name, metav1.GetOptions{})
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	ds := &appsv1.DaemonSet{}
 | 
						|
	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, ds); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// We set the nodeSelector to a random label. This label is nearly guaranteed
 | 
						|
	// to not be set on any node so the DameonSetController will start deleting
 | 
						|
	// daemon pods. Once it's done deleting the daemon pods, it's safe to delete
 | 
						|
	// the DaemonSet.
 | 
						|
	ds.Spec.Template.Spec.NodeSelector = map[string]string{
 | 
						|
		string(uuid.NewUUID()): string(uuid.NewUUID()),
 | 
						|
	}
 | 
						|
	// force update to avoid version conflict
 | 
						|
	ds.ResourceVersion = ""
 | 
						|
 | 
						|
	out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ds)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if _, err = dsClient.Update(&unstructured.Unstructured{Object: out}); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// Wait for the daemon set controller to kill all the daemon pods.
 | 
						|
	if err := wait.Poll(1*time.Second, 5*time.Minute, func() (bool, error) {
 | 
						|
		updatedObj, err := dsClient.Get(name, metav1.GetOptions{})
 | 
						|
		if err != nil {
 | 
						|
			return false, nil
 | 
						|
		}
 | 
						|
		updatedDS := &appsv1.DaemonSet{}
 | 
						|
		if err := runtime.DefaultUnstructuredConverter.FromUnstructured(updatedObj.Object, ds); err != nil {
 | 
						|
			return false, nil
 | 
						|
		}
 | 
						|
 | 
						|
		return updatedDS.Status.CurrentNumberScheduled+updatedDS.Status.NumberMisscheduled == 0, nil
 | 
						|
	}); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// deletion printing 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)
 | 
						|
}
 |