1
16
17 package delete
18
19 import (
20 "encoding/json"
21 "fmt"
22 "io"
23 "net/http"
24 "strconv"
25 "strings"
26 "testing"
27
28 "github.com/spf13/cobra"
29
30 corev1 "k8s.io/api/core/v1"
31 "k8s.io/apimachinery/pkg/api/errors"
32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33 "k8s.io/cli-runtime/pkg/genericiooptions"
34 "k8s.io/cli-runtime/pkg/resource"
35 "k8s.io/client-go/rest/fake"
36 cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
37 cmdutil "k8s.io/kubectl/pkg/cmd/util"
38 "k8s.io/kubectl/pkg/scheme"
39 "k8s.io/utils/pointer"
40 )
41
42 func fakecmd() *cobra.Command {
43 cmd := &cobra.Command{
44 Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])",
45 DisableFlagsInUseLine: true,
46 Run: func(cmd *cobra.Command, args []string) {},
47 }
48 cmdutil.AddDryRunFlag(cmd)
49 return cmd
50 }
51
52 func TestDeleteFlagValidation(t *testing.T) {
53 f := cmdtesting.NewTestFactory()
54 defer f.Cleanup()
55
56 tests := []struct {
57 flags DeleteFlags
58 args [][]string
59 expectedErr string
60 }{
61 {
62 flags: DeleteFlags{
63 Raw: pointer.String("test"),
64 Interactive: pointer.Bool(true),
65 },
66 expectedErr: "--interactive can not be used with --raw",
67 },
68 }
69
70 for _, test := range tests {
71 cmd := fakecmd()
72 deleteOptions, err := test.flags.ToOptions(nil, genericiooptions.NewTestIOStreamsDiscard())
73 if err != nil {
74 t.Fatalf("unexpected error creating delete options: %s", err)
75 }
76 deleteOptions.Filenames = []string{"../../../testdata/redis-master-controller.yaml"}
77 err = deleteOptions.Complete(f, nil, cmd)
78 if err != nil {
79 t.Fatalf("unexpected error creating delete options: %s", err)
80 }
81 err = deleteOptions.Validate()
82 if err == nil {
83 t.Fatalf("missing expected error")
84 }
85 if test.expectedErr != err.Error() {
86 t.Errorf("expected error %s, got %s", test.expectedErr, err)
87 }
88 }
89 }
90
91 func TestDeleteObjectByTuple(t *testing.T) {
92 cmdtesting.InitTestErrorHandler(t)
93 _, _, rc := cmdtesting.TestData()
94
95 tf := cmdtesting.NewTestFactory().WithNamespace("test")
96 defer tf.Cleanup()
97
98 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
99
100 tf.UnstructuredClient = &fake.RESTClient{
101 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
102 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
103 switch p, m := req.URL.Path, req.Method; {
104
105
106 case p == "/namespaces/test/replicationcontrollers/redis-master-controller" && m == "DELETE":
107 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
108
109
110 case p == "/namespaces/test/secrets/mysecret" && m == "DELETE":
111 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
112
113 default:
114
115 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
116 return nil, nil
117 }
118 }),
119 }
120
121 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
122 cmd := NewCmdDelete(tf, streams)
123 cmd.Flags().Set("namespace", "test")
124 cmd.Flags().Set("cascade", "false")
125 cmd.Flags().Set("output", "name")
126 cmd.Run(cmd, []string{"replicationcontrollers/redis-master-controller"})
127 if buf.String() != "replicationcontroller/redis-master-controller\n" {
128 t.Errorf("unexpected output: %s", buf.String())
129 }
130
131
132 streams, _, buf, _ = genericiooptions.NewTestIOStreams()
133 cmd = NewCmdDelete(tf, streams)
134 cmd.Flags().Set("namespace", "test")
135 cmd.Flags().Set("output", "name")
136 cmd.Run(cmd, []string{"secrets/mysecret"})
137 if buf.String() != "secret/mysecret\n" {
138 t.Errorf("unexpected output: %s", buf.String())
139 }
140 }
141
142 func hasExpectedPropagationPolicy(body io.ReadCloser, policy *metav1.DeletionPropagation) bool {
143 if body == nil || policy == nil {
144 return body == nil && policy == nil
145 }
146 var parsedBody metav1.DeleteOptions
147 rawBody, _ := io.ReadAll(body)
148 json.Unmarshal(rawBody, &parsedBody)
149 if parsedBody.PropagationPolicy == nil {
150 return false
151 }
152 return *policy == *parsedBody.PropagationPolicy
153 }
154
155
156 func TestCascadingStrategy(t *testing.T) {
157 cmdtesting.InitTestErrorHandler(t)
158 _, _, rc := cmdtesting.TestData()
159
160 tf := cmdtesting.NewTestFactory().WithNamespace("test")
161 defer tf.Cleanup()
162
163 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
164
165 var policy *metav1.DeletionPropagation
166 tf.UnstructuredClient = &fake.RESTClient{
167 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
168 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
169 switch p, m, b := req.URL.Path, req.Method, req.Body; {
170
171 case p == "/namespaces/test/secrets/mysecret" && m == "DELETE" && hasExpectedPropagationPolicy(b, policy):
172
173 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
174 default:
175 return nil, nil
176 }
177 }),
178 }
179
180
181 backgroundPolicy := metav1.DeletePropagationBackground
182 policy = &backgroundPolicy
183 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
184 cmd := NewCmdDelete(tf, streams)
185 cmd.Flags().Set("namespace", "test")
186 cmd.Flags().Set("output", "name")
187 cmd.Run(cmd, []string{"secrets/mysecret"})
188 if buf.String() != "secret/mysecret\n" {
189 t.Errorf("unexpected output: %s", buf.String())
190 }
191
192
193 foregroundPolicy := metav1.DeletePropagationForeground
194 policy = &foregroundPolicy
195 streams, _, buf, _ = genericiooptions.NewTestIOStreams()
196 cmd = NewCmdDelete(tf, streams)
197 cmd.Flags().Set("namespace", "test")
198 cmd.Flags().Set("cascade", "foreground")
199 cmd.Flags().Set("output", "name")
200 cmd.Run(cmd, []string{"secrets/mysecret"})
201 if buf.String() != "secret/mysecret\n" {
202 t.Errorf("unexpected output: %s", buf.String())
203 }
204
205
206 orphanPolicy := metav1.DeletePropagationOrphan
207 policy = &orphanPolicy
208 streams, _, buf, _ = genericiooptions.NewTestIOStreams()
209 cmd = NewCmdDelete(tf, streams)
210 cmd.Flags().Set("namespace", "test")
211 cmd.Flags().Set("cascade", "orphan")
212 cmd.Flags().Set("output", "name")
213 cmd.Run(cmd, []string{"secrets/mysecret"})
214 if buf.String() != "secret/mysecret\n" {
215 t.Errorf("unexpected output: %s", buf.String())
216 }
217 }
218
219 func TestDeleteNamedObject(t *testing.T) {
220 cmdtesting.InitTestErrorHandler(t)
221 cmdtesting.InitTestErrorHandler(t)
222 _, _, rc := cmdtesting.TestData()
223
224 tf := cmdtesting.NewTestFactory().WithNamespace("test")
225 defer tf.Cleanup()
226
227 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
228
229 tf.UnstructuredClient = &fake.RESTClient{
230 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
231 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
232 switch p, m := req.URL.Path, req.Method; {
233
234
235 case p == "/namespaces/test/replicationcontrollers/redis-master-controller" && m == "DELETE":
236 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
237
238
239 case p == "/namespaces/test/secrets/mysecret" && m == "DELETE":
240 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
241
242 default:
243
244 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
245 return nil, nil
246 }
247 }),
248 }
249
250 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
251 cmd := NewCmdDelete(tf, streams)
252 cmd.Flags().Set("namespace", "test")
253 cmd.Flags().Set("cascade", "false")
254 cmd.Flags().Set("output", "name")
255 cmd.Run(cmd, []string{"replicationcontrollers", "redis-master-controller"})
256 if buf.String() != "replicationcontroller/redis-master-controller\n" {
257 t.Errorf("unexpected output: %s", buf.String())
258 }
259
260
261 streams, _, buf, _ = genericiooptions.NewTestIOStreams()
262 cmd = NewCmdDelete(tf, streams)
263 cmd.Flags().Set("namespace", "test")
264 cmd.Flags().Set("cascade", "false")
265 cmd.Flags().Set("output", "name")
266 cmd.Run(cmd, []string{"secrets", "mysecret"})
267 if buf.String() != "secret/mysecret\n" {
268 t.Errorf("unexpected output: %s", buf.String())
269 }
270 }
271
272 func TestDeleteObject(t *testing.T) {
273 cmdtesting.InitTestErrorHandler(t)
274 _, _, rc := cmdtesting.TestData()
275
276 tf := cmdtesting.NewTestFactory().WithNamespace("test")
277 defer tf.Cleanup()
278
279 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
280
281 tf.UnstructuredClient = &fake.RESTClient{
282 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
283 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
284 switch p, m := req.URL.Path, req.Method; {
285 case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
286 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
287 default:
288 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
289 return nil, nil
290 }
291 }),
292 }
293
294 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
295 cmd := NewCmdDelete(tf, streams)
296 cmd.Flags().Set("filename", "../../../testdata/redis-master-controller.yaml")
297 cmd.Flags().Set("cascade", "false")
298 cmd.Flags().Set("output", "name")
299 cmd.Run(cmd, []string{})
300
301
302 if buf.String() != "replicationcontroller/redis-master\n" {
303 t.Errorf("unexpected output: %s", buf.String())
304 }
305 }
306
307 func TestPreviewResultEqualToResult(t *testing.T) {
308 deleteFlags := NewDeleteCommandFlags("")
309 deleteFlags.Interactive = pointer.Bool(true)
310
311 tf := cmdtesting.NewTestFactory().WithNamespace("test")
312 defer tf.Cleanup()
313
314 streams, _, _, _ := genericiooptions.NewTestIOStreams()
315
316 deleteOptions, err := deleteFlags.ToOptions(nil, streams)
317 deleteOptions.Filenames = []string{"../../../testdata/redis-master-controller.yaml"}
318 if err != nil {
319 t.Errorf("unexpected error %v", err)
320 }
321 err = deleteOptions.Complete(tf, nil, fakecmd())
322 if err != nil {
323 t.Errorf("unexpected error %v", err)
324 }
325
326 infos, err := deleteOptions.Result.Infos()
327 if err != nil {
328 t.Errorf("unexpected error %v", err)
329 }
330 previewInfos, err := deleteOptions.PreviewResult.Infos()
331 if err != nil {
332 t.Errorf("unexpected error %v", err)
333 }
334 if len(infos) != len(previewInfos) {
335 t.Errorf("result and previewResult must match")
336 }
337 }
338
339 func TestDeleteObjectWithInteractive(t *testing.T) {
340 cmdtesting.InitTestErrorHandler(t)
341 _, _, rc := cmdtesting.TestData()
342
343 tf := cmdtesting.NewTestFactory().WithNamespace("test")
344 defer tf.Cleanup()
345
346 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
347
348 tf.UnstructuredClient = &fake.RESTClient{
349 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
350 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
351 switch p, m := req.URL.Path, req.Method; {
352 case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
353 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
354 default:
355 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
356 return nil, nil
357 }
358 }),
359 }
360
361 streams, in, buf, _ := genericiooptions.NewTestIOStreams()
362 fmt.Fprint(in, "y")
363 cmd := NewCmdDelete(tf, streams)
364 err := cmd.Flags().Set("filename", "../../../testdata/redis-master-controller.yaml")
365 if err != nil {
366 t.Errorf("unexpected error %v", err)
367 }
368 err = cmd.Flags().Set("output", "name")
369 if err != nil {
370 t.Errorf("unexpected error %v", err)
371 }
372 err = cmd.Flags().Set("interactive", "true")
373 if err != nil {
374 t.Errorf("unexpected error %v", err)
375 }
376 cmd.Run(cmd, []string{})
377
378 if buf.String() != "You are about to delete the following 1 resource(s):\nreplicationcontroller/redis-master\nDo you want to continue? (y/n): replicationcontroller/redis-master\n" {
379 t.Errorf("unexpected output: %s", buf.String())
380 }
381
382 streams, in, buf, _ = genericiooptions.NewTestIOStreams()
383 fmt.Fprint(in, "n")
384 cmd = NewCmdDelete(tf, streams)
385 err = cmd.Flags().Set("filename", "../../../testdata/redis-master-controller.yaml")
386 if err != nil {
387 t.Errorf("unexpected error %v", err)
388 }
389 err = cmd.Flags().Set("output", "name")
390 if err != nil {
391 t.Errorf("unexpected error %v", err)
392 }
393 err = cmd.Flags().Set("interactive", "true")
394 if err != nil {
395 t.Errorf("unexpected error %v", err)
396 }
397 cmd.Run(cmd, []string{})
398
399 if buf.String() != "You are about to delete the following 1 resource(s):\nreplicationcontroller/redis-master\nDo you want to continue? (y/n): deletion is cancelled\n" {
400 t.Errorf("unexpected output: %s", buf.String())
401 }
402 if buf.String() == ": replicationcontroller/redis-master\n" {
403 t.Errorf("unexpected output: %s", buf.String())
404 }
405 }
406
407 func TestGracePeriodScenarios(t *testing.T) {
408 pods, _, _ := cmdtesting.TestData()
409
410 tf := cmdtesting.NewTestFactory().WithNamespace("test")
411 defer tf.Cleanup()
412
413 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
414
415 tc := []struct {
416 name string
417 cmdArgs []string
418 forceFlag bool
419 nowFlag bool
420 gracePeriodFlag string
421 expectedGracePeriod string
422 expectedOut string
423 expectedErrOut string
424 expectedDeleteRequestPath string
425 expectedExitCode int
426 }{
427 {
428 name: "Deleting an object with --force should use grace period = 0",
429 cmdArgs: []string{"pods/foo"},
430 forceFlag: true,
431 expectedGracePeriod: "0",
432 expectedOut: "pod/foo\n",
433 expectedErrOut: "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",
434 expectedDeleteRequestPath: "/namespaces/test/pods/foo",
435 },
436 {
437 name: "Deleting an object with --force and --grace-period 0 should use grade period = 0",
438 cmdArgs: []string{"pods/foo"},
439 forceFlag: true,
440 gracePeriodFlag: "0",
441 expectedGracePeriod: "0",
442 expectedOut: "pod/foo\n",
443 expectedErrOut: "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",
444 expectedDeleteRequestPath: "/namespaces/test/pods/foo",
445 },
446 {
447 name: "Deleting an object with --force and --grace-period > 0 should fail",
448 cmdArgs: []string{"pods/foo"},
449 forceFlag: true,
450 gracePeriodFlag: "10",
451 expectedErrOut: "error: --force and --grace-period greater than 0 cannot be specified together",
452 expectedExitCode: 1,
453 },
454 {
455 name: "Deleting an object with --grace-period 0 should use a grace period of 1",
456 cmdArgs: []string{"pods/foo"},
457 gracePeriodFlag: "0",
458 expectedGracePeriod: "1",
459 expectedOut: "pod/foo\n",
460 expectedDeleteRequestPath: "/namespaces/test/pods/foo",
461 },
462 {
463 name: "Deleting an object with --grace-period > 0 should use the specified grace period",
464 cmdArgs: []string{"pods/foo"},
465 gracePeriodFlag: "10",
466 expectedGracePeriod: "10",
467 expectedOut: "pod/foo\n",
468 expectedDeleteRequestPath: "/namespaces/test/pods/foo",
469 },
470 {
471 name: "Deleting an object with the --now flag should use grace period = 1",
472 cmdArgs: []string{"pods/foo"},
473 nowFlag: true,
474 expectedGracePeriod: "1",
475 expectedOut: "pod/foo\n",
476 expectedDeleteRequestPath: "/namespaces/test/pods/foo",
477 },
478 {
479 name: "Deleting an object with --now and --grace-period should fail",
480 cmdArgs: []string{"pods/foo"},
481 nowFlag: true,
482 gracePeriodFlag: "10",
483 expectedErrOut: "error: --now and --grace-period cannot be specified together",
484 expectedExitCode: 1,
485 },
486 }
487
488 for _, test := range tc {
489 t.Run(test.name, func(t *testing.T) {
490
491
492
493 cmdutil.BehaviorOnFatal(func(actualErrOut string, actualExitCode int) {
494 if test.expectedExitCode != actualExitCode {
495 t.Errorf("unexpected exit code:\n\tExpected: %d\n\tActual: %d\n", test.expectedExitCode, actualExitCode)
496 }
497 if test.expectedErrOut != actualErrOut {
498 t.Errorf("unexpected error:\n\tExpected: %s\n\tActual: %s\n", test.expectedErrOut, actualErrOut)
499 }
500 panic(nil)
501 })
502 defer func() {
503 if test.expectedExitCode != 0 {
504 recover()
505 }
506 }()
507
508
509
510 actualGracePeriod := ""
511 deleteOccurred := false
512 tf.UnstructuredClient = &fake.RESTClient{
513 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
514 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
515 switch p, m := req.URL.Path, req.Method; {
516 case m == "DELETE" && p == test.expectedDeleteRequestPath:
517 data := make(map[string]interface{})
518 _ = json.NewDecoder(req.Body).Decode(&data)
519 actualGracePeriod = strconv.FormatFloat(data["gracePeriodSeconds"].(float64), 'f', 0, 64)
520 deleteOccurred = true
521 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
522 default:
523 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
524 return nil, nil
525 }
526 }),
527 }
528
529
530 streams, _, out, errOut := genericiooptions.NewTestIOStreams()
531 cmd := NewCmdDelete(tf, streams)
532 cmd.Flags().Set("output", "name")
533 if test.forceFlag {
534 cmd.Flags().Set("force", "true")
535 }
536 if test.nowFlag {
537 cmd.Flags().Set("now", "true")
538 }
539 if len(test.gracePeriodFlag) > 0 {
540 cmd.Flags().Set("grace-period", test.gracePeriodFlag)
541 }
542 cmd.Run(cmd, test.cmdArgs)
543
544
545 if len(test.expectedDeleteRequestPath) > 0 && !deleteOccurred {
546 t.Errorf("expected http delete request to %s but it did not occur", test.expectedDeleteRequestPath)
547 }
548 if test.expectedGracePeriod != actualGracePeriod {
549 t.Errorf("unexpected grace period:\n\tExpected: %s\n\tActual: %s\n", test.expectedGracePeriod, actualGracePeriod)
550 }
551 if out.String() != test.expectedOut {
552 t.Errorf("unexpected output:\n\tExpected: %s\n\tActual: %s\n", test.expectedOut, out.String())
553 }
554 if errOut.String() != test.expectedErrOut {
555 t.Errorf("unexpected error output:\n\tExpected: %s\n\tActual: %s\n", test.expectedErrOut, errOut.String())
556 }
557 })
558 }
559 }
560
561 func TestDeleteObjectNotFound(t *testing.T) {
562 cmdtesting.InitTestErrorHandler(t)
563 tf := cmdtesting.NewTestFactory().WithNamespace("test")
564 defer tf.Cleanup()
565
566 tf.UnstructuredClient = &fake.RESTClient{
567 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
568 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
569 switch p, m := req.URL.Path, req.Method; {
570 case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
571 return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
572 default:
573 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
574 return nil, nil
575 }
576 }),
577 }
578
579 options := &DeleteOptions{
580 FilenameOptions: resource.FilenameOptions{
581 Filenames: []string{"../../../testdata/redis-master-controller.yaml"},
582 },
583 GracePeriod: -1,
584 CascadingStrategy: metav1.DeletePropagationOrphan,
585 Output: "name",
586 IOStreams: genericiooptions.NewTestIOStreamsDiscard(),
587 }
588 err := options.Complete(tf, []string{}, fakecmd())
589 if err != nil {
590 t.Errorf("unexpected error: %v", err)
591 }
592 err = options.RunDelete(nil)
593 if err == nil || !errors.IsNotFound(err) {
594 t.Errorf("unexpected error: expected NotFound, got %v", err)
595 }
596 }
597
598 func TestDeleteObjectIgnoreNotFound(t *testing.T) {
599 cmdtesting.InitTestErrorHandler(t)
600 tf := cmdtesting.NewTestFactory().WithNamespace("test")
601 defer tf.Cleanup()
602
603 tf.UnstructuredClient = &fake.RESTClient{
604 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
605 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
606 switch p, m := req.URL.Path, req.Method; {
607 case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
608 return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
609 default:
610 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
611 return nil, nil
612 }
613 }),
614 }
615 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
616
617 cmd := NewCmdDelete(tf, streams)
618 cmd.Flags().Set("filename", "../../../testdata/redis-master-controller.yaml")
619 cmd.Flags().Set("cascade", "false")
620 cmd.Flags().Set("ignore-not-found", "true")
621 cmd.Flags().Set("output", "name")
622 cmd.Run(cmd, []string{})
623
624 if buf.String() != "" {
625 t.Errorf("unexpected output: %s", buf.String())
626 }
627 }
628
629 func TestDeleteAllNotFound(t *testing.T) {
630 cmdtesting.InitTestErrorHandler(t)
631 _, svc, _ := cmdtesting.TestData()
632
633 svc.Items = append(svc.Items, corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
634 notFoundError := &errors.NewNotFound(corev1.Resource("services"), "foo").ErrStatus
635
636 tf := cmdtesting.NewTestFactory().WithNamespace("test")
637 defer tf.Cleanup()
638
639 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
640
641 tf.UnstructuredClient = &fake.RESTClient{
642 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
643 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
644 switch p, m := req.URL.Path, req.Method; {
645 case p == "/namespaces/test/services" && m == "GET":
646 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, svc)}, nil
647 case p == "/namespaces/test/services/foo" && m == "DELETE":
648 return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, notFoundError)}, nil
649 case p == "/namespaces/test/services/baz" && m == "DELETE":
650 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
651 default:
652 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
653 return nil, nil
654 }
655 }),
656 }
657
658
659 options := &DeleteOptions{
660 FilenameOptions: resource.FilenameOptions{},
661 GracePeriod: -1,
662 CascadingStrategy: metav1.DeletePropagationOrphan,
663 DeleteAll: true,
664 IgnoreNotFound: false,
665 Output: "name",
666 IOStreams: genericiooptions.NewTestIOStreamsDiscard(),
667 }
668 err := options.Complete(tf, []string{"services"}, fakecmd())
669 if err != nil {
670 t.Errorf("unexpected error: %v", err)
671 }
672 err = options.RunDelete(nil)
673 if err == nil || !errors.IsNotFound(err) {
674 t.Errorf("unexpected error: expected NotFound, got %v", err)
675 }
676 }
677
678 func TestDeleteAllIgnoreNotFound(t *testing.T) {
679 cmdtesting.InitTestErrorHandler(t)
680 _, svc, _ := cmdtesting.TestData()
681
682 tf := cmdtesting.NewTestFactory().WithNamespace("test")
683 defer tf.Cleanup()
684
685 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
686
687
688 svc.Items = append(svc.Items, corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
689 notFoundError := &errors.NewNotFound(corev1.Resource("services"), "foo").ErrStatus
690
691 tf.UnstructuredClient = &fake.RESTClient{
692 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
693 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
694 switch p, m := req.URL.Path, req.Method; {
695 case p == "/namespaces/test/services" && m == "GET":
696 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, svc)}, nil
697 case p == "/namespaces/test/services/foo" && m == "DELETE":
698 return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, notFoundError)}, nil
699 case p == "/namespaces/test/services/baz" && m == "DELETE":
700 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
701 default:
702 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
703 return nil, nil
704 }
705 }),
706 }
707 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
708
709 cmd := NewCmdDelete(tf, streams)
710 cmd.Flags().Set("all", "true")
711 cmd.Flags().Set("cascade", "false")
712 cmd.Flags().Set("output", "name")
713 cmd.Run(cmd, []string{"services"})
714
715 if buf.String() != "service/baz\n" {
716 t.Errorf("unexpected output: %s", buf.String())
717 }
718 }
719
720 func TestDeleteMultipleObject(t *testing.T) {
721 cmdtesting.InitTestErrorHandler(t)
722 _, svc, rc := cmdtesting.TestData()
723
724 tf := cmdtesting.NewTestFactory().WithNamespace("test")
725 defer tf.Cleanup()
726
727 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
728
729 tf.UnstructuredClient = &fake.RESTClient{
730 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
731 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
732 switch p, m := req.URL.Path, req.Method; {
733 case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
734 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
735 case p == "/namespaces/test/services/frontend" && m == "DELETE":
736 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
737 default:
738 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
739 return nil, nil
740 }
741 }),
742 }
743 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
744
745 cmd := NewCmdDelete(tf, streams)
746 cmd.Flags().Set("filename", "../../../testdata/redis-master-controller.yaml")
747 cmd.Flags().Set("filename", "../../../testdata/frontend-service.yaml")
748 cmd.Flags().Set("cascade", "false")
749 cmd.Flags().Set("output", "name")
750 cmd.Run(cmd, []string{})
751
752 if buf.String() != "replicationcontroller/redis-master\nservice/frontend\n" {
753 t.Errorf("unexpected output: %s", buf.String())
754 }
755 }
756
757 func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) {
758 cmdtesting.InitTestErrorHandler(t)
759 _, svc, _ := cmdtesting.TestData()
760
761 tf := cmdtesting.NewTestFactory().WithNamespace("test")
762 defer tf.Cleanup()
763
764 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
765
766 tf.UnstructuredClient = &fake.RESTClient{
767 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
768 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
769 switch p, m := req.URL.Path, req.Method; {
770 case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
771 return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
772 case p == "/namespaces/test/services/frontend" && m == "DELETE":
773 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
774 default:
775 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
776 return nil, nil
777 }
778 }),
779 }
780 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
781
782 options := &DeleteOptions{
783 FilenameOptions: resource.FilenameOptions{
784 Filenames: []string{"../../../testdata/redis-master-controller.yaml", "../../../testdata/frontend-service.yaml"},
785 },
786 GracePeriod: -1,
787 CascadingStrategy: metav1.DeletePropagationOrphan,
788 Output: "name",
789 IOStreams: streams,
790 }
791 err := options.Complete(tf, []string{}, fakecmd())
792 if err != nil {
793 t.Errorf("unexpected error: %v", err)
794 }
795 err = options.RunDelete(nil)
796 if err == nil || !errors.IsNotFound(err) {
797 t.Errorf("unexpected error: expected NotFound, got %v", err)
798 }
799
800 if buf.String() != "service/frontend\n" {
801 t.Errorf("unexpected output: %s", buf.String())
802 }
803 }
804
805 func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) {
806 cmdtesting.InitTestErrorHandler(t)
807 _, svc, rc := cmdtesting.TestData()
808 tf := cmdtesting.NewTestFactory().WithNamespace("test")
809 defer tf.Cleanup()
810
811 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
812
813 tf.UnstructuredClient = &fake.RESTClient{
814 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
815 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
816 switch p, m := req.URL.Path, req.Method; {
817 case p == "/namespaces/test/replicationcontrollers/baz" && m == "DELETE":
818 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
819 case p == "/namespaces/test/replicationcontrollers/foo" && m == "DELETE":
820 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
821 case p == "/namespaces/test/services/baz" && m == "DELETE":
822 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
823 case p == "/namespaces/test/services/foo" && m == "DELETE":
824 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
825 default:
826
827 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
828 return nil, nil
829 }
830 }),
831 }
832 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
833
834 cmd := NewCmdDelete(tf, streams)
835 cmd.Flags().Set("namespace", "test")
836 cmd.Flags().Set("cascade", "false")
837 cmd.Flags().Set("output", "name")
838 cmd.Run(cmd, []string{"replicationcontrollers,services", "baz", "foo"})
839 if buf.String() != "replicationcontroller/baz\nreplicationcontroller/foo\nservice/baz\nservice/foo\n" {
840 t.Errorf("unexpected output: %s", buf.String())
841 }
842 }
843
844 func TestDeleteDirectory(t *testing.T) {
845 cmdtesting.InitTestErrorHandler(t)
846 _, _, rc := cmdtesting.TestData()
847
848 tf := cmdtesting.NewTestFactory().WithNamespace("test")
849 defer tf.Cleanup()
850
851 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
852
853 tf.UnstructuredClient = &fake.RESTClient{
854 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
855 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
856 switch p, m := req.URL.Path, req.Method; {
857 case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && m == "DELETE":
858 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
859 default:
860 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
861 return nil, nil
862 }
863 }),
864 }
865 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
866
867 cmd := NewCmdDelete(tf, streams)
868 cmd.Flags().Set("filename", "../../../testdata/replace/legacy")
869 cmd.Flags().Set("cascade", "false")
870 cmd.Flags().Set("output", "name")
871 cmd.Run(cmd, []string{})
872
873 if buf.String() != "replicationcontroller/frontend\nreplicationcontroller/redis-master\nreplicationcontroller/redis-slave\n" {
874 t.Errorf("unexpected output: %s", buf.String())
875 }
876 }
877
878 func TestDeleteMultipleSelector(t *testing.T) {
879 cmdtesting.InitTestErrorHandler(t)
880 pods, svc, _ := cmdtesting.TestData()
881
882 tf := cmdtesting.NewTestFactory().WithNamespace("test")
883 defer tf.Cleanup()
884
885 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
886
887 tf.UnstructuredClient = &fake.RESTClient{
888 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
889 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
890 switch p, m := req.URL.Path, req.Method; {
891 case p == "/namespaces/test/pods" && m == "GET":
892 if req.URL.Query().Get(metav1.LabelSelectorQueryParam("v1")) != "a=b" {
893 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
894 }
895 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pods)}, nil
896 case p == "/namespaces/test/services" && m == "GET":
897 if req.URL.Query().Get(metav1.LabelSelectorQueryParam("v1")) != "a=b" {
898 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
899 }
900 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, svc)}, nil
901 case strings.HasPrefix(p, "/namespaces/test/pods/") && m == "DELETE":
902 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
903 case strings.HasPrefix(p, "/namespaces/test/services/") && m == "DELETE":
904 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
905 default:
906 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
907 return nil, nil
908 }
909 }),
910 }
911 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
912
913 cmd := NewCmdDelete(tf, streams)
914 cmd.Flags().Set("selector", "a=b")
915 cmd.Flags().Set("cascade", "false")
916 cmd.Flags().Set("output", "name")
917 cmd.Run(cmd, []string{"pods,services"})
918
919 if buf.String() != "pod/foo\npod/bar\nservice/baz\n" {
920 t.Errorf("unexpected output: %s", buf.String())
921 }
922 }
923
924 func TestResourceErrors(t *testing.T) {
925 cmdtesting.InitTestErrorHandler(t)
926 testCases := map[string]struct {
927 args []string
928 errFn func(error) bool
929 }{
930 "no args": {
931 args: []string{},
932 errFn: func(err error) bool { return strings.Contains(err.Error(), "You must provide one or more resources") },
933 },
934 "resources but no selectors": {
935 args: []string{"pods"},
936 errFn: func(err error) bool {
937 return strings.Contains(err.Error(), "resource(s) were provided, but no name was specified")
938 },
939 },
940 "multiple resources but no selectors": {
941 args: []string{"pods,deployments"},
942 errFn: func(err error) bool {
943 return strings.Contains(err.Error(), "resource(s) were provided, but no name was specified")
944 },
945 },
946 }
947
948 for k, testCase := range testCases {
949 t.Run(k, func(t *testing.T) {
950 tf := cmdtesting.NewTestFactory().WithNamespace("test")
951 defer tf.Cleanup()
952
953 tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
954
955 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
956 options := &DeleteOptions{
957 FilenameOptions: resource.FilenameOptions{},
958 GracePeriod: -1,
959 CascadingStrategy: metav1.DeletePropagationOrphan,
960 Output: "name",
961 IOStreams: streams,
962 }
963 err := options.Complete(tf, testCase.args, fakecmd())
964 if !testCase.errFn(err) {
965 t.Errorf("%s: unexpected error: %v", k, err)
966 return
967 }
968
969 if buf.Len() > 0 {
970 t.Errorf("buffer should be empty: %s", buf.String())
971 }
972 })
973 }
974 }
975
View as plain text