delete_test.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package delete
  14. import (
  15. "encoding/json"
  16. "io"
  17. "io/ioutil"
  18. "net/http"
  19. "strings"
  20. "testing"
  21. "github.com/spf13/cobra"
  22. corev1 "k8s.io/api/core/v1"
  23. "k8s.io/apimachinery/pkg/api/errors"
  24. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  25. "k8s.io/cli-runtime/pkg/genericclioptions"
  26. "k8s.io/cli-runtime/pkg/resource"
  27. "k8s.io/client-go/rest/fake"
  28. cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
  29. "k8s.io/kubernetes/pkg/kubectl/scheme"
  30. )
  31. func fakecmd() *cobra.Command {
  32. cmd := &cobra.Command{
  33. Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])",
  34. DisableFlagsInUseLine: true,
  35. Run: func(cmd *cobra.Command, args []string) {},
  36. }
  37. return cmd
  38. }
  39. func TestDeleteObjectByTuple(t *testing.T) {
  40. cmdtesting.InitTestErrorHandler(t)
  41. _, _, rc := cmdtesting.TestData()
  42. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  43. defer tf.Cleanup()
  44. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  45. tf.UnstructuredClient = &fake.RESTClient{
  46. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  47. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  48. switch p, m := req.URL.Path, req.Method; {
  49. // replication controller with cascade off
  50. case p == "/namespaces/test/replicationcontrollers/redis-master-controller" && m == "DELETE":
  51. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
  52. // secret with cascade on, but no client-side reaper
  53. case p == "/namespaces/test/secrets/mysecret" && m == "DELETE":
  54. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
  55. default:
  56. // Ensures no GET is performed when deleting by name
  57. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  58. return nil, nil
  59. }
  60. }),
  61. }
  62. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  63. cmd := NewCmdDelete(tf, streams)
  64. cmd.Flags().Set("namespace", "test")
  65. cmd.Flags().Set("cascade", "false")
  66. cmd.Flags().Set("output", "name")
  67. cmd.Run(cmd, []string{"replicationcontrollers/redis-master-controller"})
  68. if buf.String() != "replicationcontroller/redis-master-controller\n" {
  69. t.Errorf("unexpected output: %s", buf.String())
  70. }
  71. // Test cascading delete of object without client-side reaper doesn't make GET requests
  72. streams, _, buf, _ = genericclioptions.NewTestIOStreams()
  73. cmd = NewCmdDelete(tf, streams)
  74. cmd.Flags().Set("namespace", "test")
  75. cmd.Flags().Set("output", "name")
  76. cmd.Run(cmd, []string{"secrets/mysecret"})
  77. if buf.String() != "secret/mysecret\n" {
  78. t.Errorf("unexpected output: %s", buf.String())
  79. }
  80. }
  81. func hasExpectedPropagationPolicy(body io.ReadCloser, policy *metav1.DeletionPropagation) bool {
  82. if body == nil || policy == nil {
  83. return body == nil && policy == nil
  84. }
  85. var parsedBody metav1.DeleteOptions
  86. rawBody, _ := ioutil.ReadAll(body)
  87. json.Unmarshal(rawBody, &parsedBody)
  88. if parsedBody.PropagationPolicy == nil {
  89. return false
  90. }
  91. return *policy == *parsedBody.PropagationPolicy
  92. }
  93. // Tests that DeleteOptions.OrphanDependents is appropriately set while deleting objects.
  94. func TestOrphanDependentsInDeleteObject(t *testing.T) {
  95. cmdtesting.InitTestErrorHandler(t)
  96. _, _, rc := cmdtesting.TestData()
  97. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  98. defer tf.Cleanup()
  99. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  100. var policy *metav1.DeletionPropagation
  101. tf.UnstructuredClient = &fake.RESTClient{
  102. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  103. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  104. switch p, m, b := req.URL.Path, req.Method, req.Body; {
  105. case p == "/namespaces/test/secrets/mysecret" && m == "DELETE" && hasExpectedPropagationPolicy(b, policy):
  106. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
  107. default:
  108. return nil, nil
  109. }
  110. }),
  111. }
  112. // DeleteOptions.PropagationPolicy should be Background, when cascade is true (default).
  113. backgroundPolicy := metav1.DeletePropagationBackground
  114. policy = &backgroundPolicy
  115. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  116. cmd := NewCmdDelete(tf, streams)
  117. cmd.Flags().Set("namespace", "test")
  118. cmd.Flags().Set("output", "name")
  119. cmd.Run(cmd, []string{"secrets/mysecret"})
  120. if buf.String() != "secret/mysecret\n" {
  121. t.Errorf("unexpected output: %s", buf.String())
  122. }
  123. // Test that delete options should be set to orphan when cascade is false.
  124. orphanPolicy := metav1.DeletePropagationOrphan
  125. policy = &orphanPolicy
  126. streams, _, buf, _ = genericclioptions.NewTestIOStreams()
  127. cmd = NewCmdDelete(tf, streams)
  128. cmd.Flags().Set("namespace", "test")
  129. cmd.Flags().Set("cascade", "false")
  130. cmd.Flags().Set("output", "name")
  131. cmd.Run(cmd, []string{"secrets/mysecret"})
  132. if buf.String() != "secret/mysecret\n" {
  133. t.Errorf("unexpected output: %s", buf.String())
  134. }
  135. }
  136. func TestDeleteNamedObject(t *testing.T) {
  137. cmdtesting.InitTestErrorHandler(t)
  138. cmdtesting.InitTestErrorHandler(t)
  139. _, _, rc := cmdtesting.TestData()
  140. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  141. defer tf.Cleanup()
  142. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  143. tf.UnstructuredClient = &fake.RESTClient{
  144. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  145. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  146. switch p, m := req.URL.Path, req.Method; {
  147. // replication controller with cascade off
  148. case p == "/namespaces/test/replicationcontrollers/redis-master-controller" && m == "DELETE":
  149. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
  150. // secret with cascade on, but no client-side reaper
  151. case p == "/namespaces/test/secrets/mysecret" && m == "DELETE":
  152. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
  153. default:
  154. // Ensures no GET is performed when deleting by name
  155. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  156. return nil, nil
  157. }
  158. }),
  159. }
  160. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  161. cmd := NewCmdDelete(tf, streams)
  162. cmd.Flags().Set("namespace", "test")
  163. cmd.Flags().Set("cascade", "false")
  164. cmd.Flags().Set("output", "name")
  165. cmd.Run(cmd, []string{"replicationcontrollers", "redis-master-controller"})
  166. if buf.String() != "replicationcontroller/redis-master-controller\n" {
  167. t.Errorf("unexpected output: %s", buf.String())
  168. }
  169. // Test cascading delete of object without client-side reaper doesn't make GET requests
  170. streams, _, buf, _ = genericclioptions.NewTestIOStreams()
  171. cmd = NewCmdDelete(tf, streams)
  172. cmd.Flags().Set("namespace", "test")
  173. cmd.Flags().Set("cascade", "false")
  174. cmd.Flags().Set("output", "name")
  175. cmd.Run(cmd, []string{"secrets", "mysecret"})
  176. if buf.String() != "secret/mysecret\n" {
  177. t.Errorf("unexpected output: %s", buf.String())
  178. }
  179. }
  180. func TestDeleteObject(t *testing.T) {
  181. cmdtesting.InitTestErrorHandler(t)
  182. _, _, rc := cmdtesting.TestData()
  183. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  184. defer tf.Cleanup()
  185. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  186. tf.UnstructuredClient = &fake.RESTClient{
  187. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  188. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  189. switch p, m := req.URL.Path, req.Method; {
  190. case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
  191. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
  192. default:
  193. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  194. return nil, nil
  195. }
  196. }),
  197. }
  198. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  199. cmd := NewCmdDelete(tf, streams)
  200. cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml")
  201. cmd.Flags().Set("cascade", "false")
  202. cmd.Flags().Set("output", "name")
  203. cmd.Run(cmd, []string{})
  204. // uses the name from the file, not the response
  205. if buf.String() != "replicationcontroller/redis-master\n" {
  206. t.Errorf("unexpected output: %s", buf.String())
  207. }
  208. }
  209. func TestDeleteObjectGraceZero(t *testing.T) {
  210. cmdtesting.InitTestErrorHandler(t)
  211. pods, _, _ := cmdtesting.TestData()
  212. count := 0
  213. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  214. defer tf.Cleanup()
  215. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  216. tf.UnstructuredClient = &fake.RESTClient{
  217. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  218. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  219. t.Logf("got request %s %s", req.Method, req.URL.Path)
  220. switch p, m := req.URL.Path, req.Method; {
  221. case p == "/namespaces/test/pods/nginx" && m == "GET":
  222. count++
  223. switch count {
  224. case 1, 2, 3:
  225. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
  226. default:
  227. return &http.Response{StatusCode: 404, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &metav1.Status{})}, nil
  228. }
  229. case p == "/api/v1/namespaces/test" && m == "GET":
  230. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &corev1.Namespace{})}, nil
  231. case p == "/namespaces/test/pods/nginx" && m == "DELETE":
  232. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
  233. default:
  234. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  235. return nil, nil
  236. }
  237. }),
  238. }
  239. streams, _, buf, errBuf := genericclioptions.NewTestIOStreams()
  240. cmd := NewCmdDelete(tf, streams)
  241. cmd.Flags().Set("output", "name")
  242. cmd.Flags().Set("grace-period", "0")
  243. cmd.Run(cmd, []string{"pods/nginx"})
  244. // uses the name from the file, not the response
  245. if buf.String() != "pod/nginx\n" {
  246. t.Errorf("unexpected output: %s\n---\n%s", buf.String(), errBuf.String())
  247. }
  248. if count != 0 {
  249. t.Errorf("unexpected calls to GET: %d", count)
  250. }
  251. }
  252. func TestDeleteObjectNotFound(t *testing.T) {
  253. cmdtesting.InitTestErrorHandler(t)
  254. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  255. defer tf.Cleanup()
  256. tf.UnstructuredClient = &fake.RESTClient{
  257. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  258. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  259. switch p, m := req.URL.Path, req.Method; {
  260. case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
  261. return &http.Response{StatusCode: 404, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
  262. default:
  263. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  264. return nil, nil
  265. }
  266. }),
  267. }
  268. options := &DeleteOptions{
  269. FilenameOptions: resource.FilenameOptions{
  270. Filenames: []string{"../../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml"},
  271. },
  272. GracePeriod: -1,
  273. Cascade: false,
  274. Output: "name",
  275. IOStreams: genericclioptions.NewTestIOStreamsDiscard(),
  276. }
  277. err := options.Complete(tf, []string{}, fakecmd())
  278. if err != nil {
  279. t.Errorf("unexpected error: %v", err)
  280. }
  281. err = options.RunDelete()
  282. if err == nil || !errors.IsNotFound(err) {
  283. t.Errorf("unexpected error: expected NotFound, got %v", err)
  284. }
  285. }
  286. func TestDeleteObjectIgnoreNotFound(t *testing.T) {
  287. cmdtesting.InitTestErrorHandler(t)
  288. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  289. defer tf.Cleanup()
  290. tf.UnstructuredClient = &fake.RESTClient{
  291. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  292. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  293. switch p, m := req.URL.Path, req.Method; {
  294. case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
  295. return &http.Response{StatusCode: 404, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
  296. default:
  297. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  298. return nil, nil
  299. }
  300. }),
  301. }
  302. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  303. cmd := NewCmdDelete(tf, streams)
  304. cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml")
  305. cmd.Flags().Set("cascade", "false")
  306. cmd.Flags().Set("ignore-not-found", "true")
  307. cmd.Flags().Set("output", "name")
  308. cmd.Run(cmd, []string{})
  309. if buf.String() != "" {
  310. t.Errorf("unexpected output: %s", buf.String())
  311. }
  312. }
  313. func TestDeleteAllNotFound(t *testing.T) {
  314. cmdtesting.InitTestErrorHandler(t)
  315. _, svc, _ := cmdtesting.TestData()
  316. // Add an item to the list which will result in a 404 on delete
  317. svc.Items = append(svc.Items, corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
  318. notFoundError := &errors.NewNotFound(corev1.Resource("services"), "foo").ErrStatus
  319. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  320. defer tf.Cleanup()
  321. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  322. tf.UnstructuredClient = &fake.RESTClient{
  323. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  324. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  325. switch p, m := req.URL.Path, req.Method; {
  326. case p == "/namespaces/test/services" && m == "GET":
  327. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, svc)}, nil
  328. case p == "/namespaces/test/services/foo" && m == "DELETE":
  329. return &http.Response{StatusCode: 404, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, notFoundError)}, nil
  330. case p == "/namespaces/test/services/baz" && m == "DELETE":
  331. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
  332. default:
  333. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  334. return nil, nil
  335. }
  336. }),
  337. }
  338. // Make sure we can explicitly choose to fail on NotFound errors, even with --all
  339. options := &DeleteOptions{
  340. FilenameOptions: resource.FilenameOptions{},
  341. GracePeriod: -1,
  342. Cascade: false,
  343. DeleteAll: true,
  344. IgnoreNotFound: false,
  345. Output: "name",
  346. IOStreams: genericclioptions.NewTestIOStreamsDiscard(),
  347. }
  348. err := options.Complete(tf, []string{"services"}, fakecmd())
  349. if err != nil {
  350. t.Errorf("unexpected error: %v", err)
  351. }
  352. err = options.RunDelete()
  353. if err == nil || !errors.IsNotFound(err) {
  354. t.Errorf("unexpected error: expected NotFound, got %v", err)
  355. }
  356. }
  357. func TestDeleteAllIgnoreNotFound(t *testing.T) {
  358. cmdtesting.InitTestErrorHandler(t)
  359. _, svc, _ := cmdtesting.TestData()
  360. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  361. defer tf.Cleanup()
  362. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  363. // Add an item to the list which will result in a 404 on delete
  364. svc.Items = append(svc.Items, corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
  365. notFoundError := &errors.NewNotFound(corev1.Resource("services"), "foo").ErrStatus
  366. tf.UnstructuredClient = &fake.RESTClient{
  367. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  368. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  369. switch p, m := req.URL.Path, req.Method; {
  370. case p == "/namespaces/test/services" && m == "GET":
  371. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, svc)}, nil
  372. case p == "/namespaces/test/services/foo" && m == "DELETE":
  373. return &http.Response{StatusCode: 404, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, notFoundError)}, nil
  374. case p == "/namespaces/test/services/baz" && m == "DELETE":
  375. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
  376. default:
  377. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  378. return nil, nil
  379. }
  380. }),
  381. }
  382. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  383. cmd := NewCmdDelete(tf, streams)
  384. cmd.Flags().Set("all", "true")
  385. cmd.Flags().Set("cascade", "false")
  386. cmd.Flags().Set("output", "name")
  387. cmd.Run(cmd, []string{"services"})
  388. if buf.String() != "service/baz\n" {
  389. t.Errorf("unexpected output: %s", buf.String())
  390. }
  391. }
  392. func TestDeleteMultipleObject(t *testing.T) {
  393. cmdtesting.InitTestErrorHandler(t)
  394. _, svc, rc := cmdtesting.TestData()
  395. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  396. defer tf.Cleanup()
  397. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  398. tf.UnstructuredClient = &fake.RESTClient{
  399. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  400. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  401. switch p, m := req.URL.Path, req.Method; {
  402. case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
  403. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
  404. case p == "/namespaces/test/services/frontend" && m == "DELETE":
  405. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
  406. default:
  407. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  408. return nil, nil
  409. }
  410. }),
  411. }
  412. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  413. cmd := NewCmdDelete(tf, streams)
  414. cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml")
  415. cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/guestbook/frontend-service.yaml")
  416. cmd.Flags().Set("cascade", "false")
  417. cmd.Flags().Set("output", "name")
  418. cmd.Run(cmd, []string{})
  419. if buf.String() != "replicationcontroller/redis-master\nservice/frontend\n" {
  420. t.Errorf("unexpected output: %s", buf.String())
  421. }
  422. }
  423. func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) {
  424. cmdtesting.InitTestErrorHandler(t)
  425. _, svc, _ := cmdtesting.TestData()
  426. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  427. defer tf.Cleanup()
  428. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  429. tf.UnstructuredClient = &fake.RESTClient{
  430. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  431. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  432. switch p, m := req.URL.Path, req.Method; {
  433. case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
  434. return &http.Response{StatusCode: 404, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
  435. case p == "/namespaces/test/services/frontend" && m == "DELETE":
  436. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
  437. default:
  438. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  439. return nil, nil
  440. }
  441. }),
  442. }
  443. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  444. options := &DeleteOptions{
  445. FilenameOptions: resource.FilenameOptions{
  446. Filenames: []string{"../../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml", "../../../../test/e2e/testing-manifests/guestbook/frontend-service.yaml"},
  447. },
  448. GracePeriod: -1,
  449. Cascade: false,
  450. Output: "name",
  451. IOStreams: streams,
  452. }
  453. err := options.Complete(tf, []string{}, fakecmd())
  454. if err != nil {
  455. t.Errorf("unexpected error: %v", err)
  456. }
  457. err = options.RunDelete()
  458. if err == nil || !errors.IsNotFound(err) {
  459. t.Errorf("unexpected error: expected NotFound, got %v", err)
  460. }
  461. if buf.String() != "service/frontend\n" {
  462. t.Errorf("unexpected output: %s", buf.String())
  463. }
  464. }
  465. func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) {
  466. cmdtesting.InitTestErrorHandler(t)
  467. _, svc, rc := cmdtesting.TestData()
  468. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  469. defer tf.Cleanup()
  470. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  471. tf.UnstructuredClient = &fake.RESTClient{
  472. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  473. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  474. switch p, m := req.URL.Path, req.Method; {
  475. case p == "/namespaces/test/replicationcontrollers/baz" && m == "DELETE":
  476. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
  477. case p == "/namespaces/test/replicationcontrollers/foo" && m == "DELETE":
  478. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
  479. case p == "/namespaces/test/services/baz" && m == "DELETE":
  480. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
  481. case p == "/namespaces/test/services/foo" && m == "DELETE":
  482. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
  483. default:
  484. // Ensures no GET is performed when deleting by name
  485. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  486. return nil, nil
  487. }
  488. }),
  489. }
  490. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  491. cmd := NewCmdDelete(tf, streams)
  492. cmd.Flags().Set("namespace", "test")
  493. cmd.Flags().Set("cascade", "false")
  494. cmd.Flags().Set("output", "name")
  495. cmd.Run(cmd, []string{"replicationcontrollers,services", "baz", "foo"})
  496. if buf.String() != "replicationcontroller/baz\nreplicationcontroller/foo\nservice/baz\nservice/foo\n" {
  497. t.Errorf("unexpected output: %s", buf.String())
  498. }
  499. }
  500. func TestDeleteDirectory(t *testing.T) {
  501. cmdtesting.InitTestErrorHandler(t)
  502. _, _, rc := cmdtesting.TestData()
  503. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  504. defer tf.Cleanup()
  505. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  506. tf.UnstructuredClient = &fake.RESTClient{
  507. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  508. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  509. switch p, m := req.URL.Path, req.Method; {
  510. case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && m == "DELETE":
  511. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
  512. default:
  513. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  514. return nil, nil
  515. }
  516. }),
  517. }
  518. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  519. cmd := NewCmdDelete(tf, streams)
  520. cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/guestbook/legacy")
  521. cmd.Flags().Set("cascade", "false")
  522. cmd.Flags().Set("output", "name")
  523. cmd.Run(cmd, []string{})
  524. if buf.String() != "replicationcontroller/frontend\nreplicationcontroller/redis-master\nreplicationcontroller/redis-slave\n" {
  525. t.Errorf("unexpected output: %s", buf.String())
  526. }
  527. }
  528. func TestDeleteMultipleSelector(t *testing.T) {
  529. cmdtesting.InitTestErrorHandler(t)
  530. pods, svc, _ := cmdtesting.TestData()
  531. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  532. defer tf.Cleanup()
  533. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  534. tf.UnstructuredClient = &fake.RESTClient{
  535. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  536. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  537. switch p, m := req.URL.Path, req.Method; {
  538. case p == "/namespaces/test/pods" && m == "GET":
  539. if req.URL.Query().Get(metav1.LabelSelectorQueryParam("v1")) != "a=b" {
  540. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  541. }
  542. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pods)}, nil
  543. case p == "/namespaces/test/services" && m == "GET":
  544. if req.URL.Query().Get(metav1.LabelSelectorQueryParam("v1")) != "a=b" {
  545. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  546. }
  547. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, svc)}, nil
  548. case strings.HasPrefix(p, "/namespaces/test/pods/") && m == "DELETE":
  549. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
  550. case strings.HasPrefix(p, "/namespaces/test/services/") && m == "DELETE":
  551. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
  552. default:
  553. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  554. return nil, nil
  555. }
  556. }),
  557. }
  558. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  559. cmd := NewCmdDelete(tf, streams)
  560. cmd.Flags().Set("selector", "a=b")
  561. cmd.Flags().Set("cascade", "false")
  562. cmd.Flags().Set("output", "name")
  563. cmd.Run(cmd, []string{"pods,services"})
  564. if buf.String() != "pod/foo\npod/bar\nservice/baz\n" {
  565. t.Errorf("unexpected output: %s", buf.String())
  566. }
  567. }
  568. func TestResourceErrors(t *testing.T) {
  569. cmdtesting.InitTestErrorHandler(t)
  570. testCases := map[string]struct {
  571. args []string
  572. errFn func(error) bool
  573. }{
  574. "no args": {
  575. args: []string{},
  576. errFn: func(err error) bool { return strings.Contains(err.Error(), "You must provide one or more resources") },
  577. },
  578. "resources but no selectors": {
  579. args: []string{"pods"},
  580. errFn: func(err error) bool {
  581. return strings.Contains(err.Error(), "resource(s) were provided, but no name, label selector, or --all flag specified")
  582. },
  583. },
  584. "multiple resources but no selectors": {
  585. args: []string{"pods,deployments"},
  586. errFn: func(err error) bool {
  587. return strings.Contains(err.Error(), "resource(s) were provided, but no name, label selector, or --all flag specified")
  588. },
  589. },
  590. }
  591. for k, testCase := range testCases {
  592. t.Run(k, func(t *testing.T) {
  593. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  594. defer tf.Cleanup()
  595. tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
  596. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  597. options := &DeleteOptions{
  598. FilenameOptions: resource.FilenameOptions{},
  599. GracePeriod: -1,
  600. Cascade: false,
  601. Output: "name",
  602. IOStreams: streams,
  603. }
  604. err := options.Complete(tf, testCase.args, fakecmd())
  605. if !testCase.errFn(err) {
  606. t.Errorf("%s: unexpected error: %v", k, err)
  607. return
  608. }
  609. if buf.Len() > 0 {
  610. t.Errorf("buffer should be empty: %s", string(buf.Bytes()))
  611. }
  612. })
  613. }
  614. }