annotate_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  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 annotate
  14. import (
  15. "net/http"
  16. "reflect"
  17. "strings"
  18. "testing"
  19. "k8s.io/api/core/v1"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/runtime"
  22. "k8s.io/apimachinery/pkg/runtime/schema"
  23. "k8s.io/cli-runtime/pkg/genericclioptions"
  24. "k8s.io/cli-runtime/pkg/resource"
  25. "k8s.io/client-go/rest/fake"
  26. cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
  27. "k8s.io/kubernetes/pkg/kubectl/scheme"
  28. )
  29. func TestValidateAnnotationOverwrites(t *testing.T) {
  30. tests := []struct {
  31. meta *metav1.ObjectMeta
  32. annotations map[string]string
  33. expectErr bool
  34. scenario string
  35. }{
  36. {
  37. meta: &metav1.ObjectMeta{
  38. Annotations: map[string]string{
  39. "a": "A",
  40. "b": "B",
  41. },
  42. },
  43. annotations: map[string]string{
  44. "a": "a",
  45. "c": "C",
  46. },
  47. scenario: "share first annotation",
  48. expectErr: true,
  49. },
  50. {
  51. meta: &metav1.ObjectMeta{
  52. Annotations: map[string]string{
  53. "a": "A",
  54. "c": "C",
  55. },
  56. },
  57. annotations: map[string]string{
  58. "b": "B",
  59. "c": "c",
  60. },
  61. scenario: "share second annotation",
  62. expectErr: true,
  63. },
  64. {
  65. meta: &metav1.ObjectMeta{
  66. Annotations: map[string]string{
  67. "a": "A",
  68. "c": "C",
  69. },
  70. },
  71. annotations: map[string]string{
  72. "b": "B",
  73. "d": "D",
  74. },
  75. scenario: "no overlap",
  76. },
  77. {
  78. meta: &metav1.ObjectMeta{},
  79. annotations: map[string]string{
  80. "a": "A",
  81. "b": "B",
  82. },
  83. scenario: "no annotations",
  84. },
  85. }
  86. for _, test := range tests {
  87. err := validateNoAnnotationOverwrites(test.meta, test.annotations)
  88. if test.expectErr && err == nil {
  89. t.Errorf("%s: unexpected non-error", test.scenario)
  90. } else if !test.expectErr && err != nil {
  91. t.Errorf("%s: unexpected error: %v", test.scenario, err)
  92. }
  93. }
  94. }
  95. func TestParseAnnotations(t *testing.T) {
  96. testURL := "https://test.com/index.htm?id=123#u=user-name"
  97. testJSON := `'{"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"ReplicationController","namespace":"default","name":"my-nginx","uid":"c544ee78-2665-11e5-8051-42010af0c213","apiVersion":"v1","resourceVersion":"61368"}}'`
  98. tests := []struct {
  99. annotations []string
  100. expected map[string]string
  101. expectedRemove []string
  102. scenario string
  103. expectedErr string
  104. expectErr bool
  105. }{
  106. {
  107. annotations: []string{"a=b", "c=d"},
  108. expected: map[string]string{"a": "b", "c": "d"},
  109. expectedRemove: []string{},
  110. scenario: "add two annotations",
  111. expectErr: false,
  112. },
  113. {
  114. annotations: []string{"url=" + testURL, "fake.kubernetes.io/annotation=" + testJSON},
  115. expected: map[string]string{"url": testURL, "fake.kubernetes.io/annotation": testJSON},
  116. expectedRemove: []string{},
  117. scenario: "add annotations with special characters",
  118. expectErr: false,
  119. },
  120. {
  121. annotations: []string{},
  122. expected: map[string]string{},
  123. expectedRemove: []string{},
  124. scenario: "add no annotations",
  125. expectErr: false,
  126. },
  127. {
  128. annotations: []string{"a=b", "c=d", "e-"},
  129. expected: map[string]string{"a": "b", "c": "d"},
  130. expectedRemove: []string{"e"},
  131. scenario: "add two annotations, remove one",
  132. expectErr: false,
  133. },
  134. {
  135. annotations: []string{"ab", "c=d"},
  136. expectedErr: "invalid annotation format: ab",
  137. scenario: "incorrect annotation input (missing =value)",
  138. expectErr: true,
  139. },
  140. {
  141. annotations: []string{"a="},
  142. expected: map[string]string{"a": ""},
  143. expectedRemove: []string{},
  144. scenario: "add valid annotation with empty value",
  145. expectErr: false,
  146. },
  147. {
  148. annotations: []string{"ab", "a="},
  149. expectedErr: "invalid annotation format: ab",
  150. scenario: "incorrect annotation input (missing =value)",
  151. expectErr: true,
  152. },
  153. {
  154. annotations: []string{"-"},
  155. expectedErr: "invalid annotation format: -",
  156. scenario: "incorrect annotation input (missing key)",
  157. expectErr: true,
  158. },
  159. {
  160. annotations: []string{"=bar"},
  161. expectedErr: "invalid annotation format: =bar",
  162. scenario: "incorrect annotation input (missing key)",
  163. expectErr: true,
  164. },
  165. }
  166. for _, test := range tests {
  167. annotations, remove, err := parseAnnotations(test.annotations)
  168. switch {
  169. case test.expectErr && err == nil:
  170. t.Errorf("%s: unexpected non-error, should return %v", test.scenario, test.expectedErr)
  171. case test.expectErr && err.Error() != test.expectedErr:
  172. t.Errorf("%s: unexpected error %v, expected %v", test.scenario, err, test.expectedErr)
  173. case !test.expectErr && err != nil:
  174. t.Errorf("%s: unexpected error %v", test.scenario, err)
  175. case !test.expectErr && !reflect.DeepEqual(annotations, test.expected):
  176. t.Errorf("%s: expected %v, got %v", test.scenario, test.expected, annotations)
  177. case !test.expectErr && !reflect.DeepEqual(remove, test.expectedRemove):
  178. t.Errorf("%s: expected %v, got %v", test.scenario, test.expectedRemove, remove)
  179. }
  180. }
  181. }
  182. func TestValidateAnnotations(t *testing.T) {
  183. tests := []struct {
  184. removeAnnotations []string
  185. newAnnotations map[string]string
  186. expectedErr string
  187. scenario string
  188. }{
  189. {
  190. expectedErr: "can not both modify and remove the following annotation(s) in the same command: a",
  191. removeAnnotations: []string{"a"},
  192. newAnnotations: map[string]string{"a": "b", "c": "d"},
  193. scenario: "remove an added annotation",
  194. },
  195. {
  196. expectedErr: "can not both modify and remove the following annotation(s) in the same command: a, c",
  197. removeAnnotations: []string{"a", "c"},
  198. newAnnotations: map[string]string{"a": "b", "c": "d"},
  199. scenario: "remove added annotations",
  200. },
  201. }
  202. for _, test := range tests {
  203. if err := validateAnnotations(test.removeAnnotations, test.newAnnotations); err == nil {
  204. t.Errorf("%s: unexpected non-error", test.scenario)
  205. } else if err.Error() != test.expectedErr {
  206. t.Errorf("%s: expected error %s, got %s", test.scenario, test.expectedErr, err.Error())
  207. }
  208. }
  209. }
  210. func TestUpdateAnnotations(t *testing.T) {
  211. tests := []struct {
  212. obj runtime.Object
  213. overwrite bool
  214. version string
  215. annotations map[string]string
  216. remove []string
  217. expected runtime.Object
  218. expectErr bool
  219. }{
  220. {
  221. obj: &v1.Pod{
  222. ObjectMeta: metav1.ObjectMeta{
  223. Annotations: map[string]string{"a": "b"},
  224. },
  225. },
  226. annotations: map[string]string{"a": "b"},
  227. expectErr: true,
  228. },
  229. {
  230. obj: &v1.Pod{
  231. ObjectMeta: metav1.ObjectMeta{
  232. Annotations: map[string]string{"a": "b"},
  233. },
  234. },
  235. annotations: map[string]string{"a": "c"},
  236. overwrite: true,
  237. expected: &v1.Pod{
  238. ObjectMeta: metav1.ObjectMeta{
  239. Annotations: map[string]string{"a": "c"},
  240. },
  241. },
  242. },
  243. {
  244. obj: &v1.Pod{
  245. ObjectMeta: metav1.ObjectMeta{
  246. Annotations: map[string]string{"a": "b"},
  247. },
  248. },
  249. annotations: map[string]string{"c": "d"},
  250. expected: &v1.Pod{
  251. ObjectMeta: metav1.ObjectMeta{
  252. Annotations: map[string]string{"a": "b", "c": "d"},
  253. },
  254. },
  255. },
  256. {
  257. obj: &v1.Pod{
  258. ObjectMeta: metav1.ObjectMeta{
  259. Annotations: map[string]string{"a": "b"},
  260. },
  261. },
  262. annotations: map[string]string{"c": "d"},
  263. version: "2",
  264. expected: &v1.Pod{
  265. ObjectMeta: metav1.ObjectMeta{
  266. Annotations: map[string]string{"a": "b", "c": "d"},
  267. ResourceVersion: "2",
  268. },
  269. },
  270. },
  271. {
  272. obj: &v1.Pod{
  273. ObjectMeta: metav1.ObjectMeta{
  274. Annotations: map[string]string{"a": "b"},
  275. },
  276. },
  277. annotations: map[string]string{},
  278. remove: []string{"a"},
  279. expected: &v1.Pod{
  280. ObjectMeta: metav1.ObjectMeta{
  281. Annotations: map[string]string{},
  282. },
  283. },
  284. },
  285. {
  286. obj: &v1.Pod{
  287. ObjectMeta: metav1.ObjectMeta{
  288. Annotations: map[string]string{"a": "b", "c": "d"},
  289. },
  290. },
  291. annotations: map[string]string{"e": "f"},
  292. remove: []string{"a"},
  293. expected: &v1.Pod{
  294. ObjectMeta: metav1.ObjectMeta{
  295. Annotations: map[string]string{
  296. "c": "d",
  297. "e": "f",
  298. },
  299. },
  300. },
  301. },
  302. {
  303. obj: &v1.Pod{
  304. ObjectMeta: metav1.ObjectMeta{
  305. Annotations: map[string]string{"a": "b", "c": "d"},
  306. },
  307. },
  308. annotations: map[string]string{"e": "f"},
  309. remove: []string{"g"},
  310. expected: &v1.Pod{
  311. ObjectMeta: metav1.ObjectMeta{
  312. Annotations: map[string]string{
  313. "a": "b",
  314. "c": "d",
  315. "e": "f",
  316. },
  317. },
  318. },
  319. },
  320. {
  321. obj: &v1.Pod{
  322. ObjectMeta: metav1.ObjectMeta{
  323. Annotations: map[string]string{"a": "b", "c": "d"},
  324. },
  325. },
  326. remove: []string{"e"},
  327. expected: &v1.Pod{
  328. ObjectMeta: metav1.ObjectMeta{
  329. Annotations: map[string]string{
  330. "a": "b",
  331. "c": "d",
  332. },
  333. },
  334. },
  335. },
  336. {
  337. obj: &v1.Pod{
  338. ObjectMeta: metav1.ObjectMeta{},
  339. },
  340. annotations: map[string]string{"a": "b"},
  341. expected: &v1.Pod{
  342. ObjectMeta: metav1.ObjectMeta{
  343. Annotations: map[string]string{"a": "b"},
  344. },
  345. },
  346. },
  347. }
  348. for _, test := range tests {
  349. options := &AnnotateOptions{
  350. overwrite: test.overwrite,
  351. newAnnotations: test.annotations,
  352. removeAnnotations: test.remove,
  353. resourceVersion: test.version,
  354. }
  355. err := options.updateAnnotations(test.obj)
  356. if test.expectErr {
  357. if err == nil {
  358. t.Errorf("unexpected non-error: %v", test)
  359. }
  360. continue
  361. }
  362. if !test.expectErr && err != nil {
  363. t.Errorf("unexpected error: %v %v", err, test)
  364. }
  365. if !reflect.DeepEqual(test.obj, test.expected) {
  366. t.Errorf("expected: %v, got %v", test.expected, test.obj)
  367. }
  368. }
  369. }
  370. func TestAnnotateErrors(t *testing.T) {
  371. testCases := map[string]struct {
  372. args []string
  373. flags map[string]string
  374. errFn func(error) bool
  375. }{
  376. "no args": {
  377. args: []string{},
  378. errFn: func(err error) bool { return strings.Contains(err.Error(), "one or more resources must be specified") },
  379. },
  380. "not enough annotations": {
  381. args: []string{"pods"},
  382. errFn: func(err error) bool {
  383. return strings.Contains(err.Error(), "at least one annotation update is required")
  384. },
  385. },
  386. "wrong annotations": {
  387. args: []string{"pods", "-"},
  388. errFn: func(err error) bool {
  389. return strings.Contains(err.Error(), "at least one annotation update is required")
  390. },
  391. },
  392. "wrong annotations 2": {
  393. args: []string{"pods", "=bar"},
  394. errFn: func(err error) bool {
  395. return strings.Contains(err.Error(), "at least one annotation update is required")
  396. },
  397. },
  398. "no resources remove annotations": {
  399. args: []string{"pods-"},
  400. errFn: func(err error) bool { return strings.Contains(err.Error(), "one or more resources must be specified") },
  401. },
  402. "no resources add annotations": {
  403. args: []string{"pods=bar"},
  404. errFn: func(err error) bool { return strings.Contains(err.Error(), "one or more resources must be specified") },
  405. },
  406. }
  407. for k, testCase := range testCases {
  408. t.Run(k, func(t *testing.T) {
  409. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  410. defer tf.Cleanup()
  411. tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
  412. iostreams, _, bufOut, bufErr := genericclioptions.NewTestIOStreams()
  413. cmd := NewCmdAnnotate("kubectl", tf, iostreams)
  414. cmd.SetOutput(bufOut)
  415. for k, v := range testCase.flags {
  416. cmd.Flags().Set(k, v)
  417. }
  418. options := NewAnnotateOptions(iostreams)
  419. err := options.Complete(tf, cmd, testCase.args)
  420. if err == nil {
  421. err = options.Validate()
  422. }
  423. if !testCase.errFn(err) {
  424. t.Errorf("%s: unexpected error: %v", k, err)
  425. return
  426. }
  427. if bufOut.Len() > 0 {
  428. t.Errorf("buffer should be empty: %s", string(bufOut.Bytes()))
  429. }
  430. if bufErr.Len() > 0 {
  431. t.Errorf("buffer should be empty: %s", string(bufErr.Bytes()))
  432. }
  433. })
  434. }
  435. }
  436. func TestAnnotateObject(t *testing.T) {
  437. pods, _, _ := cmdtesting.TestData()
  438. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  439. defer tf.Cleanup()
  440. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  441. tf.UnstructuredClient = &fake.RESTClient{
  442. GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
  443. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  444. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  445. switch req.Method {
  446. case "GET":
  447. switch req.URL.Path {
  448. case "/namespaces/test/pods/foo":
  449. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
  450. default:
  451. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  452. return nil, nil
  453. }
  454. case "PATCH":
  455. switch req.URL.Path {
  456. case "/namespaces/test/pods/foo":
  457. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
  458. default:
  459. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  460. return nil, nil
  461. }
  462. default:
  463. t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
  464. return nil, nil
  465. }
  466. }),
  467. }
  468. tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
  469. iostreams, _, bufOut, _ := genericclioptions.NewTestIOStreams()
  470. cmd := NewCmdAnnotate("kubectl", tf, iostreams)
  471. cmd.SetOutput(bufOut)
  472. options := NewAnnotateOptions(iostreams)
  473. args := []string{"pods/foo", "a=b", "c-"}
  474. if err := options.Complete(tf, cmd, args); err != nil {
  475. t.Fatalf("unexpected error: %v", err)
  476. }
  477. if err := options.Validate(); err != nil {
  478. t.Fatalf("unexpected error: %v", err)
  479. }
  480. if err := options.RunAnnotate(); err != nil {
  481. t.Fatalf("unexpected error: %v", err)
  482. }
  483. }
  484. func TestAnnotateObjectFromFile(t *testing.T) {
  485. pods, _, _ := cmdtesting.TestData()
  486. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  487. defer tf.Cleanup()
  488. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  489. tf.UnstructuredClient = &fake.RESTClient{
  490. GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
  491. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  492. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  493. switch req.Method {
  494. case "GET":
  495. switch req.URL.Path {
  496. case "/namespaces/test/replicationcontrollers/cassandra":
  497. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
  498. default:
  499. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  500. return nil, nil
  501. }
  502. case "PATCH":
  503. switch req.URL.Path {
  504. case "/namespaces/test/replicationcontrollers/cassandra":
  505. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
  506. default:
  507. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  508. return nil, nil
  509. }
  510. default:
  511. t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
  512. return nil, nil
  513. }
  514. }),
  515. }
  516. tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
  517. iostreams, _, bufOut, _ := genericclioptions.NewTestIOStreams()
  518. cmd := NewCmdAnnotate("kubectl", tf, iostreams)
  519. cmd.SetOutput(bufOut)
  520. options := NewAnnotateOptions(iostreams)
  521. options.Filenames = []string{"../../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}
  522. args := []string{"a=b", "c-"}
  523. if err := options.Complete(tf, cmd, args); err != nil {
  524. t.Fatalf("unexpected error: %v", err)
  525. }
  526. if err := options.Validate(); err != nil {
  527. t.Fatalf("unexpected error: %v", err)
  528. }
  529. if err := options.RunAnnotate(); err != nil {
  530. t.Fatalf("unexpected error: %v", err)
  531. }
  532. }
  533. func TestAnnotateLocal(t *testing.T) {
  534. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  535. defer tf.Cleanup()
  536. tf.UnstructuredClient = &fake.RESTClient{
  537. GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
  538. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  539. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  540. t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
  541. return nil, nil
  542. }),
  543. }
  544. tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
  545. iostreams, _, _, _ := genericclioptions.NewTestIOStreams()
  546. cmd := NewCmdAnnotate("kubectl", tf, iostreams)
  547. options := NewAnnotateOptions(iostreams)
  548. options.local = true
  549. options.Filenames = []string{"../../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}
  550. args := []string{"a=b"}
  551. if err := options.Complete(tf, cmd, args); err != nil {
  552. t.Fatalf("unexpected error: %v", err)
  553. }
  554. if err := options.Validate(); err != nil {
  555. t.Fatalf("unexpected error: %v", err)
  556. }
  557. if err := options.RunAnnotate(); err != nil {
  558. t.Fatalf("unexpected error: %v", err)
  559. }
  560. }
  561. func TestAnnotateMultipleObjects(t *testing.T) {
  562. pods, _, _ := cmdtesting.TestData()
  563. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  564. defer tf.Cleanup()
  565. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  566. tf.UnstructuredClient = &fake.RESTClient{
  567. GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
  568. NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
  569. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  570. switch req.Method {
  571. case "GET":
  572. switch req.URL.Path {
  573. case "/namespaces/test/pods":
  574. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pods)}, nil
  575. default:
  576. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  577. return nil, nil
  578. }
  579. case "PATCH":
  580. switch req.URL.Path {
  581. case "/namespaces/test/pods/foo":
  582. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
  583. case "/namespaces/test/pods/bar":
  584. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[1])}, nil
  585. default:
  586. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  587. return nil, nil
  588. }
  589. default:
  590. t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
  591. return nil, nil
  592. }
  593. }),
  594. }
  595. tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
  596. iostreams, _, _, _ := genericclioptions.NewTestIOStreams()
  597. cmd := NewCmdAnnotate("kubectl", tf, iostreams)
  598. cmd.SetOutput(iostreams.Out)
  599. options := NewAnnotateOptions(iostreams)
  600. options.all = true
  601. args := []string{"pods", "a=b", "c-"}
  602. if err := options.Complete(tf, cmd, args); err != nil {
  603. t.Fatalf("unexpected error: %v", err)
  604. }
  605. if err := options.Validate(); err != nil {
  606. t.Fatalf("unexpected error: %v", err)
  607. }
  608. if err := options.RunAnnotate(); err != nil {
  609. t.Fatalf("unexpected error: %v", err)
  610. }
  611. }