1
16
17 package delete
18
19 import (
20 "fmt"
21 "net/url"
22 "strings"
23 "time"
24
25 "github.com/spf13/cobra"
26 "k8s.io/klog/v2"
27
28 "k8s.io/apimachinery/pkg/api/errors"
29 "k8s.io/apimachinery/pkg/api/meta"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 "k8s.io/apimachinery/pkg/runtime"
32 "k8s.io/cli-runtime/pkg/genericclioptions"
33 "k8s.io/cli-runtime/pkg/genericiooptions"
34 "k8s.io/cli-runtime/pkg/printers"
35 "k8s.io/cli-runtime/pkg/resource"
36 "k8s.io/client-go/dynamic"
37 cmdutil "k8s.io/kubectl/pkg/cmd/util"
38 cmdwait "k8s.io/kubectl/pkg/cmd/wait"
39 "k8s.io/kubectl/pkg/rawhttp"
40 "k8s.io/kubectl/pkg/util/completion"
41 "k8s.io/kubectl/pkg/util/i18n"
42 "k8s.io/kubectl/pkg/util/templates"
43 "k8s.io/kubectl/pkg/util/term"
44 )
45
46 var (
47 deleteLong = templates.LongDesc(i18n.T(`
48 Delete resources by file names, stdin, resources and names, or by resources and label selector.
49
50 JSON and YAML formats are accepted. Only one type of argument may be specified: file names,
51 resources and names, or resources and label selector.
52
53 Some resources, such as pods, support graceful deletion. These resources define a default period
54 before they are forcibly terminated (the grace period) but you may override that value with
55 the --grace-period flag, or pass --now to set a grace-period of 1. Because these resources often
56 represent entities in the cluster, deletion may not be acknowledged immediately. If the node
57 hosting a pod is down or cannot reach the API server, termination may take significantly longer
58 than the grace period. To force delete a resource, you must specify the --force flag.
59 Note: only a subset of resources support graceful deletion. In absence of the support,
60 the --grace-period flag is ignored.
61
62 IMPORTANT: Force deleting pods does not wait for confirmation that the pod's processes have been
63 terminated, which can leave those processes running until the node detects the deletion and
64 completes graceful deletion. If your processes use shared storage or talk to a remote API and
65 depend on the name of the pod to identify themselves, force deleting those pods may result in
66 multiple processes running on different machines using the same identification which may lead
67 to data corruption or inconsistency. Only force delete pods when you are sure the pod is
68 terminated, or if your application can tolerate multiple copies of the same pod running at once.
69 Also, if you force delete pods, the scheduler may place new pods on those nodes before the node
70 has released those resources and causing those pods to be evicted immediately.
71
72 Note that the delete command does NOT do resource version checks, so if someone submits an
73 update to a resource right when you submit a delete, their update will be lost along with the
74 rest of the resource.
75
76 After a CustomResourceDefinition is deleted, invalidation of discovery cache may take up
77 to 6 hours. If you don't want to wait, you might want to run "kubectl api-resources" to refresh
78 the discovery cache.`))
79
80 deleteExample = templates.Examples(i18n.T(`
81 # Delete a pod using the type and name specified in pod.json
82 kubectl delete -f ./pod.json
83
84 # Delete resources from a directory containing kustomization.yaml - e.g. dir/kustomization.yaml
85 kubectl delete -k dir
86
87 # Delete resources from all files that end with '.json'
88 kubectl delete -f '*.json'
89
90 # Delete a pod based on the type and name in the JSON passed into stdin
91 cat pod.json | kubectl delete -f -
92
93 # Delete pods and services with same names "baz" and "foo"
94 kubectl delete pod,service baz foo
95
96 # Delete pods and services with label name=myLabel
97 kubectl delete pods,services -l name=myLabel
98
99 # Delete a pod with minimal delay
100 kubectl delete pod foo --now
101
102 # Force delete a pod on a dead node
103 kubectl delete pod foo --force
104
105 # Delete all pods
106 kubectl delete pods --all`))
107 )
108
109 type DeleteOptions struct {
110 resource.FilenameOptions
111
112 LabelSelector string
113 FieldSelector string
114 DeleteAll bool
115 DeleteAllNamespaces bool
116 CascadingStrategy metav1.DeletionPropagation
117 IgnoreNotFound bool
118 DeleteNow bool
119 ForceDeletion bool
120 WaitForDeletion bool
121 Quiet bool
122 WarnClusterScope bool
123 Raw string
124 Interactive bool
125
126 GracePeriod int
127 Timeout time.Duration
128
129 DryRunStrategy cmdutil.DryRunStrategy
130
131 Output string
132
133 DynamicClient dynamic.Interface
134 Mapper meta.RESTMapper
135 Result *resource.Result
136 PreviewResult *resource.Result
137 previewResourceMap map[cmdwait.ResourceLocation]struct{}
138
139 genericiooptions.IOStreams
140 WarningPrinter *printers.WarningPrinter
141 }
142
143 func NewCmdDelete(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
144 deleteFlags := NewDeleteCommandFlags("containing the resource to delete.")
145
146 cmd := &cobra.Command{
147 Use: "delete ([-f FILENAME] | [-k DIRECTORY] | TYPE [(NAME | -l label | --all)])",
148 DisableFlagsInUseLine: true,
149 Short: i18n.T("Delete resources by file names, stdin, resources and names, or by resources and label selector"),
150 Long: deleteLong,
151 Example: deleteExample,
152 ValidArgsFunction: completion.ResourceTypeAndNameCompletionFunc(f),
153 Run: func(cmd *cobra.Command, args []string) {
154 o, err := deleteFlags.ToOptions(nil, streams)
155 cmdutil.CheckErr(err)
156 cmdutil.CheckErr(o.Complete(f, args, cmd))
157 cmdutil.CheckErr(o.Validate())
158 cmdutil.CheckErr(o.RunDelete(f))
159 },
160 SuggestFor: []string{"rm"},
161 }
162
163 deleteFlags.AddFlags(cmd)
164 cmdutil.AddDryRunFlag(cmd)
165
166 return cmd
167 }
168
169 func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Command) error {
170 cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
171 if err != nil {
172 return err
173 }
174
175 o.WarnClusterScope = enforceNamespace && !o.DeleteAllNamespaces
176
177 if o.DeleteAll || len(o.LabelSelector) > 0 || len(o.FieldSelector) > 0 {
178 if f := cmd.Flags().Lookup("ignore-not-found"); f != nil && !f.Changed {
179
180 o.IgnoreNotFound = true
181 }
182 }
183 if o.DeleteNow {
184 if o.GracePeriod != -1 {
185 return fmt.Errorf("--now and --grace-period cannot be specified together")
186 }
187 o.GracePeriod = 1
188 }
189 if o.GracePeriod == 0 && !o.ForceDeletion {
190
191
192 o.GracePeriod = 1
193 }
194 if o.ForceDeletion && o.GracePeriod < 0 {
195 o.GracePeriod = 0
196 }
197
198 o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
199 if err != nil {
200 return err
201 }
202
203
204 if o.WarningPrinter == nil {
205 o.WarningPrinter = printers.NewWarningPrinter(o.ErrOut, printers.WarningPrinterOptions{Color: term.AllowsColorOutput(o.ErrOut)})
206 }
207
208 if len(o.Raw) != 0 {
209 return nil
210 }
211
212 r := f.NewBuilder().
213 Unstructured().
214 ContinueOnError().
215 NamespaceParam(cmdNamespace).DefaultNamespace().
216 FilenameParam(enforceNamespace, &o.FilenameOptions).
217 LabelSelectorParam(o.LabelSelector).
218 FieldSelectorParam(o.FieldSelector).
219 SelectAllParam(o.DeleteAll).
220 AllNamespaces(o.DeleteAllNamespaces).
221 ResourceTypeOrNameArgs(false, args...).RequireObject(false).
222 Flatten().
223 Do()
224 err = r.Err()
225 if err != nil {
226 return err
227 }
228 o.Result = r
229
230 if o.Interactive {
231
232
233
234 previewr := f.NewBuilder().
235 Unstructured().
236 ContinueOnError().
237 NamespaceParam(cmdNamespace).DefaultNamespace().
238 FilenameParam(enforceNamespace, &o.FilenameOptions).
239 LabelSelectorParam(o.LabelSelector).
240 FieldSelectorParam(o.FieldSelector).
241 SelectAllParam(o.DeleteAll).
242 AllNamespaces(o.DeleteAllNamespaces).
243 ResourceTypeOrNameArgs(false, args...).RequireObject(false).
244 Flatten().
245 Do()
246 err = previewr.Err()
247 if err != nil {
248 return err
249 }
250 o.PreviewResult = previewr
251 o.previewResourceMap = make(map[cmdwait.ResourceLocation]struct{})
252 }
253
254 o.Mapper, err = f.ToRESTMapper()
255 if err != nil {
256 return err
257 }
258
259 o.DynamicClient, err = f.DynamicClient()
260 if err != nil {
261 return err
262 }
263
264 return nil
265 }
266
267 func (o *DeleteOptions) Validate() error {
268 if o.Output != "" && o.Output != "name" {
269 return fmt.Errorf("unexpected -o output mode: %v. We only support '-o name'", o.Output)
270 }
271
272 if o.DeleteAll && len(o.LabelSelector) > 0 {
273 return fmt.Errorf("cannot set --all and --selector at the same time")
274 }
275 if o.DeleteAll && len(o.FieldSelector) > 0 {
276 return fmt.Errorf("cannot set --all and --field-selector at the same time")
277 }
278 if o.WarningPrinter == nil {
279 return fmt.Errorf("WarningPrinter can not be used without initialization")
280 }
281
282 switch {
283 case o.GracePeriod == 0 && o.ForceDeletion:
284 o.WarningPrinter.Print("Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.")
285 case o.GracePeriod > 0 && o.ForceDeletion:
286 return fmt.Errorf("--force and --grace-period greater than 0 cannot be specified together")
287 }
288
289 if len(o.Raw) == 0 {
290 return nil
291 }
292
293 if o.Interactive {
294 return fmt.Errorf("--interactive can not be used with --raw")
295 }
296 if len(o.FilenameOptions.Filenames) > 1 {
297 return fmt.Errorf("--raw can only use a single local file or stdin")
298 } else if len(o.FilenameOptions.Filenames) == 1 {
299 if strings.Index(o.FilenameOptions.Filenames[0], "http://") == 0 || strings.Index(o.FilenameOptions.Filenames[0], "https://") == 0 {
300 return fmt.Errorf("--raw cannot read from a url")
301 }
302 }
303
304 if o.FilenameOptions.Recursive {
305 return fmt.Errorf("--raw and --recursive are mutually exclusive")
306 }
307 if len(o.Output) > 0 {
308 return fmt.Errorf("--raw and --output are mutually exclusive")
309 }
310 if _, err := url.ParseRequestURI(o.Raw); err != nil {
311 return fmt.Errorf("--raw must be a valid URL path: %v", err)
312 }
313
314 return nil
315 }
316
317 func (o *DeleteOptions) RunDelete(f cmdutil.Factory) error {
318 if len(o.Raw) > 0 {
319 restClient, err := f.RESTClient()
320 if err != nil {
321 return err
322 }
323 if len(o.Filenames) == 0 {
324 return rawhttp.RawDelete(restClient, o.IOStreams, o.Raw, "")
325 }
326 return rawhttp.RawDelete(restClient, o.IOStreams, o.Raw, o.Filenames[0])
327 }
328
329 if o.Interactive {
330 previewInfos := []*resource.Info{}
331 if o.IgnoreNotFound {
332 o.PreviewResult = o.PreviewResult.IgnoreErrors(errors.IsNotFound)
333 }
334 err := o.PreviewResult.Visit(func(info *resource.Info, err error) error {
335 if err != nil {
336 return err
337 }
338 previewInfos = append(previewInfos, info)
339 o.previewResourceMap[cmdwait.ResourceLocation{
340 GroupResource: info.Mapping.Resource.GroupResource(),
341 Namespace: info.Namespace,
342 Name: info.Name,
343 }] = struct{}{}
344
345 return nil
346 })
347 if err != nil {
348 return err
349 }
350 if len(previewInfos) == 0 {
351 fmt.Fprintf(o.Out, "No resources found\n")
352 return nil
353 }
354
355 if !o.confirmation(previewInfos) {
356 fmt.Fprintf(o.Out, "deletion is cancelled\n")
357 return nil
358 }
359 }
360
361 return o.DeleteResult(o.Result)
362 }
363
364 func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
365 found := 0
366 if o.IgnoreNotFound {
367 r = r.IgnoreErrors(errors.IsNotFound)
368 }
369 warnClusterScope := o.WarnClusterScope
370 deletedInfos := []*resource.Info{}
371 uidMap := cmdwait.UIDMap{}
372 err := r.Visit(func(info *resource.Info, err error) error {
373 if err != nil {
374 return err
375 }
376
377 if o.Interactive {
378 if _, ok := o.previewResourceMap[cmdwait.ResourceLocation{
379 GroupResource: info.Mapping.Resource.GroupResource(),
380 Namespace: info.Namespace,
381 Name: info.Name,
382 }]; !ok {
383
384 return nil
385 }
386 }
387
388 deletedInfos = append(deletedInfos, info)
389 found++
390
391 options := &metav1.DeleteOptions{}
392 if o.GracePeriod >= 0 {
393 options = metav1.NewDeleteOptions(int64(o.GracePeriod))
394 }
395 options.PropagationPolicy = &o.CascadingStrategy
396
397 if warnClusterScope && info.Mapping.Scope.Name() == meta.RESTScopeNameRoot {
398 o.WarningPrinter.Print("deleting cluster-scoped resources, not scoped to the provided namespace")
399 warnClusterScope = false
400 }
401
402 if o.DryRunStrategy == cmdutil.DryRunClient {
403 if !o.Quiet {
404 o.PrintObj(info)
405 }
406 return nil
407 }
408 response, err := o.deleteResource(info, options)
409 if err != nil {
410 return err
411 }
412 resourceLocation := cmdwait.ResourceLocation{
413 GroupResource: info.Mapping.Resource.GroupResource(),
414 Namespace: info.Namespace,
415 Name: info.Name,
416 }
417 if status, ok := response.(*metav1.Status); ok && status.Details != nil {
418 uidMap[resourceLocation] = status.Details.UID
419 return nil
420 }
421 responseMetadata, err := meta.Accessor(response)
422 if err != nil {
423
424 klog.V(1).Info(err)
425 return nil
426 }
427 uidMap[resourceLocation] = responseMetadata.GetUID()
428
429 return nil
430 })
431 if err != nil {
432 return err
433 }
434 if found == 0 {
435 fmt.Fprintf(o.Out, "No resources found\n")
436 return nil
437 }
438 if !o.WaitForDeletion {
439 return nil
440 }
441
442
443 if o.DynamicClient == nil {
444 return nil
445 }
446
447
448 if o.DryRunStrategy != cmdutil.DryRunNone {
449 return nil
450 }
451
452 effectiveTimeout := o.Timeout
453 if effectiveTimeout == 0 {
454
455 effectiveTimeout = 168 * time.Hour
456 }
457 waitOptions := cmdwait.WaitOptions{
458 ResourceFinder: genericclioptions.ResourceFinderForResult(resource.InfoListVisitor(deletedInfos)),
459 UIDMap: uidMap,
460 DynamicClient: o.DynamicClient,
461 Timeout: effectiveTimeout,
462
463 Printer: printers.NewDiscardingPrinter(),
464 ConditionFn: cmdwait.IsDeleted,
465 IOStreams: o.IOStreams,
466 }
467 err = waitOptions.RunWait()
468 if errors.IsForbidden(err) || errors.IsMethodNotSupported(err) {
469
470
471 klog.V(1).Info(err)
472 return nil
473 }
474 return err
475 }
476
477 func (o *DeleteOptions) deleteResource(info *resource.Info, deleteOptions *metav1.DeleteOptions) (runtime.Object, error) {
478 deleteResponse, err := resource.
479 NewHelper(info.Client, info.Mapping).
480 DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
481 DeleteWithOptions(info.Namespace, info.Name, deleteOptions)
482 if err != nil {
483 return nil, cmdutil.AddSourceToErr("deleting", info.Source, err)
484 }
485
486 if !o.Quiet {
487 o.PrintObj(info)
488 }
489 return deleteResponse, nil
490 }
491
492
493
494 func (o *DeleteOptions) PrintObj(info *resource.Info) {
495 operation := "deleted"
496 groupKind := info.Mapping.GroupVersionKind
497 kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group)
498 if len(groupKind.Group) == 0 {
499 kindString = strings.ToLower(groupKind.Kind)
500 }
501
502 if o.GracePeriod == 0 {
503 operation = "force deleted"
504 }
505
506 switch o.DryRunStrategy {
507 case cmdutil.DryRunClient:
508 operation = fmt.Sprintf("%s (dry run)", operation)
509 case cmdutil.DryRunServer:
510 operation = fmt.Sprintf("%s (server dry run)", operation)
511 }
512
513 if o.Output == "name" {
514
515 fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
516 return
517 }
518
519
520 fmt.Fprintf(o.Out, "%s \"%s\" %s\n", kindString, info.Name, operation)
521 }
522
523 func (o *DeleteOptions) confirmation(infos []*resource.Info) bool {
524 fmt.Fprintf(o.Out, i18n.T("You are about to delete the following %d resource(s):\n"), len(infos))
525 for _, info := range infos {
526 groupKind := info.Mapping.GroupVersionKind
527 kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group)
528 if len(groupKind.Group) == 0 {
529 kindString = strings.ToLower(groupKind.Kind)
530 }
531
532 fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
533 }
534 fmt.Fprintf(o.Out, i18n.T("Do you want to continue?")+" (y/n): ")
535 var input string
536 _, err := fmt.Fscan(o.In, &input)
537 if err != nil {
538 return false
539 }
540
541 return strings.EqualFold(input, "y")
542 }
543
View as plain text