123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- /*
- 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/printers"
- "k8s.io/cli-runtime/pkg/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
- Quiet bool
- WarnClusterScope 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
- }
- o.WarnClusterScope = enforceNamespace && !o.DeleteAllNamespaces
- 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)
- }
- warnClusterScope := o.WarnClusterScope
- 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
- if warnClusterScope && info.Mapping.Scope.Name() == meta.RESTScopeNameRoot {
- fmt.Fprintf(o.ErrOut, "warning: deleting cluster-scoped resources, not scoped to the provided namespace\n")
- warnClusterScope = false
- }
- 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)
- }
- if !o.Quiet {
- 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)
- }
|