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 }