github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/delete/delete.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package delete
    21  
    22  import (
    23  	"fmt"
    24  	"strings"
    25  
    26  	"github.com/spf13/cobra"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/cli-runtime/pkg/genericiooptions"
    31  	"k8s.io/cli-runtime/pkg/resource"
    32  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    33  
    34  	"github.com/1aal/kubeblocks/pkg/cli/printer"
    35  	"github.com/1aal/kubeblocks/pkg/cli/util"
    36  	"github.com/1aal/kubeblocks/pkg/cli/util/prompt"
    37  )
    38  
    39  type DeleteHook func(options *DeleteOptions, object runtime.Object) error
    40  
    41  type DeleteOptions struct {
    42  	Factory       cmdutil.Factory
    43  	Namespace     string
    44  	LabelSelector string
    45  	AllNamespaces bool
    46  	Force         bool
    47  	GracePeriod   int
    48  	Now           bool
    49  	AutoApprove   bool
    50  
    51  	// Names are the resource names
    52  	Names []string
    53  	// ConfirmedNames used to double-check the resource names to delete, sometimes Names are used to build
    54  	// label selector and be set to nil, ConfirmedNames should be used to record the names to be confirmed.
    55  	ConfirmedNames []string
    56  	GVR            schema.GroupVersionResource
    57  	Result         *resource.Result
    58  
    59  	PreDeleteHook  DeleteHook
    60  	PostDeleteHook DeleteHook
    61  
    62  	genericiooptions.IOStreams
    63  }
    64  
    65  func NewDeleteOptions(f cmdutil.Factory, streams genericiooptions.IOStreams, gvr schema.GroupVersionResource) *DeleteOptions {
    66  	return &DeleteOptions{
    67  		Factory:   f,
    68  		IOStreams: streams,
    69  		GVR:       gvr,
    70  	}
    71  }
    72  
    73  func (o *DeleteOptions) Run() error {
    74  	if err := o.validate(); err != nil {
    75  		return err
    76  	}
    77  
    78  	if err := o.complete(); err != nil {
    79  		return err
    80  	}
    81  
    82  	// delete results
    83  	return o.deleteResult(o.Result)
    84  }
    85  
    86  func (o *DeleteOptions) validate() error {
    87  	switch {
    88  	case o.GracePeriod == 0 && o.Force:
    89  		fmt.Fprintf(o.ErrOut, "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated.\n")
    90  	case o.GracePeriod > 0 && o.Force:
    91  		return fmt.Errorf("--force and --grace-period greater than 0 cannot be specified together")
    92  	}
    93  
    94  	if o.Now {
    95  		if o.GracePeriod != -1 {
    96  			return fmt.Errorf("--now and --grace-period cannot be specified together")
    97  		}
    98  		o.GracePeriod = 1
    99  	}
   100  	if o.GracePeriod == 0 && !o.Force {
   101  		o.GracePeriod = 1
   102  	}
   103  	if o.Force && o.GracePeriod < 0 {
   104  		o.GracePeriod = 0
   105  	}
   106  
   107  	if len(o.Names) > 0 && len(o.LabelSelector) > 0 {
   108  		return fmt.Errorf("name cannot be provided when a selector is specified")
   109  	}
   110  	// names and all namespaces cannot be used together
   111  	if len(o.Names) > 0 && o.AllNamespaces {
   112  		return fmt.Errorf("a resource cannot be retrieved by name across all namespaces")
   113  	}
   114  	if len(o.Names) == 0 && len(o.LabelSelector) == 0 {
   115  		return fmt.Errorf("no name was specified. one of names, label selector must be provided")
   116  	}
   117  	return nil
   118  }
   119  
   120  func (o *DeleteOptions) complete() error {
   121  	namespace, _, err := o.Factory.ToRawKubeConfigLoader().Namespace()
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	// get the resources to delete
   127  	r := o.Factory.NewBuilder().
   128  		Unstructured().
   129  		ContinueOnError().
   130  		NamespaceParam(namespace).DefaultNamespace().
   131  		LabelSelectorParam(o.LabelSelector).
   132  		AllNamespaces(o.AllNamespaces).
   133  		ResourceTypeOrNameArgs(false, append([]string{util.GVRToString(o.GVR)}, o.Names...)...).
   134  		RequireObject(false).
   135  		Flatten().
   136  		Do()
   137  	err = r.Err()
   138  	if err != nil {
   139  		return err
   140  	}
   141  	// confirm names to delete, use ConfirmedNames first or the names selected by labels, if it is empty, use Names
   142  	// if it uses the label-selector, confirm the resourcesā€˜ names that meet the label requirements
   143  	if !o.AutoApprove {
   144  		names := o.ConfirmedNames
   145  		if len(o.LabelSelector) != 0 {
   146  			var infos []*resource.Info
   147  			if infos, err = r.Infos(); err != nil {
   148  				return err
   149  			}
   150  			for i := range infos {
   151  				names = append(names, infos[i].Name)
   152  			}
   153  		}
   154  		if len(names) == 0 {
   155  			names = o.Names
   156  		}
   157  		if err = prompt.Confirm(names, o.In, fmt.Sprintf("%s to be deleted:[%s]", o.GVR.Resource, printer.BoldRed(strings.Join(names, " "))), ""); err != nil {
   158  			return err
   159  		}
   160  	}
   161  	o.Result = r
   162  	return err
   163  }
   164  
   165  func (o *DeleteOptions) AddFlags(cmd *cobra.Command) {
   166  	cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", false, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
   167  	cmd.Flags().StringVarP(&o.LabelSelector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints.")
   168  	cmd.Flags().BoolVar(&o.Force, "force", false, "If true, immediately remove resources from API and bypass graceful deletion. Note that immediate deletion of some resources may result in inconsistency or data loss and requires confirmation.")
   169  	cmd.Flags().BoolVar(&o.Now, "now", false, "If true, resources are signaled for immediate shutdown (same as --grace-period=1).")
   170  	cmd.Flags().IntVar(&o.GracePeriod, "grace-period", -1, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative. Set to 1 for immediate shutdown. Can only be set to 0 when --force is true (force deletion).")
   171  	cmd.Flags().BoolVar(&o.AutoApprove, "auto-approve", false, "Skip interactive approval before deleting")
   172  }
   173  
   174  func (o *DeleteOptions) deleteResult(r *resource.Result) error {
   175  	found := 0
   176  	var deleteInfos []*resource.Info
   177  	err := r.Visit(func(info *resource.Info, err error) error {
   178  		if err != nil {
   179  			return err
   180  		}
   181  		deleteInfos = append(deleteInfos, info)
   182  		found++
   183  
   184  		options := &metav1.DeleteOptions{}
   185  		if o.GracePeriod >= 0 {
   186  			options = metav1.NewDeleteOptions(int64(o.GracePeriod))
   187  		}
   188  		if err = o.preDeleteResource(info); err != nil {
   189  			return err
   190  		}
   191  		if _, err = o.deleteResource(info, options); err != nil {
   192  			return err
   193  		}
   194  		if err = o.postDeleteResource(info.Object); err != nil {
   195  			return err
   196  		}
   197  		fmt.Fprintf(o.Out, "%s %s deleted\n", info.Mapping.GroupVersionKind.Kind, info.Name)
   198  		return nil
   199  	})
   200  	if err != nil {
   201  		return err
   202  	}
   203  	if found == 0 {
   204  		fmt.Fprintf(o.Out, "No %s found\n", o.GVR.Resource)
   205  	}
   206  
   207  	return nil
   208  }
   209  
   210  func (o *DeleteOptions) deleteResource(info *resource.Info, deleteOptions *metav1.DeleteOptions) (runtime.Object, error) {
   211  	response, err := resource.
   212  		NewHelper(info.Client, info.Mapping).
   213  		DryRun(false).
   214  		DeleteWithOptions(info.Namespace, info.Name, deleteOptions)
   215  	if err != nil {
   216  		return nil, cmdutil.AddSourceToErr("deleting", info.Source, err)
   217  	}
   218  	return response, nil
   219  }
   220  
   221  func (o *DeleteOptions) preDeleteResource(info *resource.Info) error {
   222  	if o.PreDeleteHook == nil {
   223  		return nil
   224  	}
   225  
   226  	if info.Object == nil {
   227  		if err := info.Get(); err != nil {
   228  			return err
   229  		}
   230  	}
   231  	return o.PreDeleteHook(o, info.Object)
   232  }
   233  
   234  func (o *DeleteOptions) postDeleteResource(object runtime.Object) error {
   235  	if o.PostDeleteHook != nil {
   236  		return o.PostDeleteHook(o, object)
   237  	}
   238  	return nil
   239  }