set_image_test.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  1. /*
  2. Copyright 2016 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 set
  14. import (
  15. "fmt"
  16. "io/ioutil"
  17. "net/http"
  18. "strings"
  19. "testing"
  20. "github.com/stretchr/testify/assert"
  21. appsv1 "k8s.io/api/apps/v1"
  22. appsv1beta1 "k8s.io/api/apps/v1beta1"
  23. appsv1beta2 "k8s.io/api/apps/v1beta2"
  24. batchv1 "k8s.io/api/batch/v1"
  25. corev1 "k8s.io/api/core/v1"
  26. extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
  27. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  28. "k8s.io/apimachinery/pkg/runtime"
  29. "k8s.io/apimachinery/pkg/runtime/schema"
  30. "k8s.io/cli-runtime/pkg/genericclioptions"
  31. "k8s.io/cli-runtime/pkg/resource"
  32. restclient "k8s.io/client-go/rest"
  33. "k8s.io/client-go/rest/fake"
  34. cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
  35. "k8s.io/kubernetes/pkg/kubectl/scheme"
  36. )
  37. func TestImageLocal(t *testing.T) {
  38. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  39. defer tf.Cleanup()
  40. tf.Client = &fake.RESTClient{
  41. GroupVersion: schema.GroupVersion{Version: ""},
  42. NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
  43. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  44. t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
  45. return nil, nil
  46. }),
  47. }
  48. tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
  49. outputFormat := "name"
  50. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  51. cmd := NewCmdImage(tf, streams)
  52. cmd.SetOutput(buf)
  53. cmd.Flags().Set("output", outputFormat)
  54. cmd.Flags().Set("local", "true")
  55. opts := SetImageOptions{
  56. PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
  57. FilenameOptions: resource.FilenameOptions{
  58. Filenames: []string{"../../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}},
  59. Local: true,
  60. IOStreams: streams,
  61. }
  62. err := opts.Complete(tf, cmd, []string{"cassandra=thingy"})
  63. if err == nil {
  64. err = opts.Validate()
  65. }
  66. if err == nil {
  67. err = opts.Run()
  68. }
  69. if err != nil {
  70. t.Fatalf("unexpected error: %v", err)
  71. }
  72. if !strings.Contains(buf.String(), "replicationcontroller/cassandra") {
  73. t.Errorf("did not set image: %s", buf.String())
  74. }
  75. }
  76. func TestSetImageValidation(t *testing.T) {
  77. printFlags := genericclioptions.NewPrintFlags("").WithTypeSetter(scheme.Scheme)
  78. testCases := []struct {
  79. name string
  80. imageOptions *SetImageOptions
  81. expectErr string
  82. }{
  83. {
  84. name: "test resource < 1 and filenames empty",
  85. imageOptions: &SetImageOptions{PrintFlags: printFlags},
  86. expectErr: "[one or more resources must be specified as <resource> <name> or <resource>/<name>, at least one image update is required]",
  87. },
  88. {
  89. name: "test containerImages < 1",
  90. imageOptions: &SetImageOptions{
  91. PrintFlags: printFlags,
  92. Resources: []string{"a", "b", "c"},
  93. FilenameOptions: resource.FilenameOptions{
  94. Filenames: []string{"testFile"},
  95. },
  96. },
  97. expectErr: "at least one image update is required",
  98. },
  99. {
  100. name: "test containerImages > 1 and all containers are already specified by *",
  101. imageOptions: &SetImageOptions{
  102. PrintFlags: printFlags,
  103. Resources: []string{"a", "b", "c"},
  104. FilenameOptions: resource.FilenameOptions{
  105. Filenames: []string{"testFile"},
  106. },
  107. ContainerImages: map[string]string{
  108. "test": "test",
  109. "*": "test",
  110. },
  111. },
  112. expectErr: "all containers are already specified by *, but saw more than one container_name=container_image pairs",
  113. },
  114. {
  115. name: "success case",
  116. imageOptions: &SetImageOptions{
  117. PrintFlags: printFlags,
  118. Resources: []string{"a", "b", "c"},
  119. FilenameOptions: resource.FilenameOptions{
  120. Filenames: []string{"testFile"},
  121. },
  122. ContainerImages: map[string]string{
  123. "test": "test",
  124. },
  125. },
  126. expectErr: "",
  127. },
  128. }
  129. for _, testCase := range testCases {
  130. err := testCase.imageOptions.Validate()
  131. if err != nil {
  132. if err.Error() != testCase.expectErr {
  133. t.Errorf("[%s]:expect err:%s got err:%s", testCase.name, testCase.expectErr, err.Error())
  134. }
  135. }
  136. if err == nil && (testCase.expectErr != "") {
  137. t.Errorf("[%s]:expect err:%s got err:%v", testCase.name, testCase.expectErr, err)
  138. }
  139. }
  140. }
  141. func TestSetMultiResourcesImageLocal(t *testing.T) {
  142. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  143. defer tf.Cleanup()
  144. tf.Client = &fake.RESTClient{
  145. GroupVersion: schema.GroupVersion{Version: ""},
  146. NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
  147. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  148. t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
  149. return nil, nil
  150. }),
  151. }
  152. tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
  153. outputFormat := "name"
  154. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  155. cmd := NewCmdImage(tf, streams)
  156. cmd.SetOutput(buf)
  157. cmd.Flags().Set("output", outputFormat)
  158. cmd.Flags().Set("local", "true")
  159. opts := SetImageOptions{
  160. PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
  161. FilenameOptions: resource.FilenameOptions{
  162. Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}},
  163. Local: true,
  164. IOStreams: streams,
  165. }
  166. err := opts.Complete(tf, cmd, []string{"*=thingy"})
  167. if err == nil {
  168. err = opts.Validate()
  169. }
  170. if err == nil {
  171. err = opts.Run()
  172. }
  173. if err != nil {
  174. t.Fatalf("unexpected error: %v", err)
  175. }
  176. expectedOut := "replicationcontroller/first-rc\nreplicationcontroller/second-rc\n"
  177. if buf.String() != expectedOut {
  178. t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String())
  179. }
  180. }
  181. func TestSetImageRemote(t *testing.T) {
  182. inputs := []struct {
  183. name string
  184. object runtime.Object
  185. groupVersion schema.GroupVersion
  186. path string
  187. args []string
  188. }{
  189. {
  190. name: "set image extensionsv1beta1 ReplicaSet",
  191. object: &extensionsv1beta1.ReplicaSet{
  192. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  193. Spec: extensionsv1beta1.ReplicaSetSpec{
  194. Template: corev1.PodTemplateSpec{
  195. Spec: corev1.PodSpec{
  196. Containers: []corev1.Container{
  197. {
  198. Name: "nginx",
  199. Image: "nginx",
  200. },
  201. },
  202. InitContainers: []corev1.Container{
  203. {
  204. Name: "busybox",
  205. Image: "busybox",
  206. },
  207. },
  208. },
  209. },
  210. },
  211. },
  212. groupVersion: extensionsv1beta1.SchemeGroupVersion,
  213. path: "/namespaces/test/replicasets/nginx",
  214. args: []string{"replicaset", "nginx", "*=thingy"},
  215. },
  216. {
  217. name: "set image appsv1beta2 ReplicaSet",
  218. object: &appsv1beta2.ReplicaSet{
  219. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  220. Spec: appsv1beta2.ReplicaSetSpec{
  221. Template: corev1.PodTemplateSpec{
  222. Spec: corev1.PodSpec{
  223. Containers: []corev1.Container{
  224. {
  225. Name: "nginx",
  226. Image: "nginx",
  227. },
  228. },
  229. InitContainers: []corev1.Container{
  230. {
  231. Name: "busybox",
  232. Image: "busybox",
  233. },
  234. },
  235. },
  236. },
  237. },
  238. },
  239. groupVersion: appsv1beta2.SchemeGroupVersion,
  240. path: "/namespaces/test/replicasets/nginx",
  241. args: []string{"replicaset", "nginx", "*=thingy"},
  242. },
  243. {
  244. name: "set image appsv1 ReplicaSet",
  245. object: &appsv1.ReplicaSet{
  246. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  247. Spec: appsv1.ReplicaSetSpec{
  248. Template: corev1.PodTemplateSpec{
  249. Spec: corev1.PodSpec{
  250. Containers: []corev1.Container{
  251. {
  252. Name: "nginx",
  253. Image: "nginx",
  254. },
  255. },
  256. InitContainers: []corev1.Container{
  257. {
  258. Name: "busybox",
  259. Image: "busybox",
  260. },
  261. },
  262. },
  263. },
  264. },
  265. },
  266. groupVersion: appsv1.SchemeGroupVersion,
  267. path: "/namespaces/test/replicasets/nginx",
  268. args: []string{"replicaset", "nginx", "*=thingy"},
  269. },
  270. {
  271. name: "set image extensionsv1beta1 DaemonSet",
  272. object: &extensionsv1beta1.DaemonSet{
  273. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  274. Spec: extensionsv1beta1.DaemonSetSpec{
  275. Template: corev1.PodTemplateSpec{
  276. Spec: corev1.PodSpec{
  277. Containers: []corev1.Container{
  278. {
  279. Name: "nginx",
  280. Image: "nginx",
  281. },
  282. },
  283. InitContainers: []corev1.Container{
  284. {
  285. Name: "busybox",
  286. Image: "busybox",
  287. },
  288. },
  289. },
  290. },
  291. },
  292. },
  293. groupVersion: extensionsv1beta1.SchemeGroupVersion,
  294. path: "/namespaces/test/daemonsets/nginx",
  295. args: []string{"daemonset", "nginx", "*=thingy"},
  296. },
  297. {
  298. name: "set image appsv1beta2 DaemonSet",
  299. object: &appsv1beta2.DaemonSet{
  300. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  301. Spec: appsv1beta2.DaemonSetSpec{
  302. Template: corev1.PodTemplateSpec{
  303. Spec: corev1.PodSpec{
  304. Containers: []corev1.Container{
  305. {
  306. Name: "nginx",
  307. Image: "nginx",
  308. },
  309. },
  310. InitContainers: []corev1.Container{
  311. {
  312. Name: "busybox",
  313. Image: "busybox",
  314. },
  315. },
  316. },
  317. },
  318. },
  319. },
  320. groupVersion: appsv1beta2.SchemeGroupVersion,
  321. path: "/namespaces/test/daemonsets/nginx",
  322. args: []string{"daemonset", "nginx", "*=thingy"},
  323. },
  324. {
  325. name: "set image appsv1 DaemonSet",
  326. object: &appsv1.DaemonSet{
  327. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  328. Spec: appsv1.DaemonSetSpec{
  329. Template: corev1.PodTemplateSpec{
  330. Spec: corev1.PodSpec{
  331. Containers: []corev1.Container{
  332. {
  333. Name: "nginx",
  334. Image: "nginx",
  335. },
  336. },
  337. InitContainers: []corev1.Container{
  338. {
  339. Name: "busybox",
  340. Image: "busybox",
  341. },
  342. },
  343. },
  344. },
  345. },
  346. },
  347. groupVersion: appsv1.SchemeGroupVersion,
  348. path: "/namespaces/test/daemonsets/nginx",
  349. args: []string{"daemonset", "nginx", "*=thingy"},
  350. },
  351. {
  352. name: "set image extensionsv1beta1 Deployment",
  353. object: &extensionsv1beta1.Deployment{
  354. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  355. Spec: extensionsv1beta1.DeploymentSpec{
  356. Template: corev1.PodTemplateSpec{
  357. Spec: corev1.PodSpec{
  358. Containers: []corev1.Container{
  359. {
  360. Name: "nginx",
  361. Image: "nginx",
  362. },
  363. },
  364. InitContainers: []corev1.Container{
  365. {
  366. Name: "busybox",
  367. Image: "busybox",
  368. },
  369. },
  370. },
  371. },
  372. },
  373. },
  374. groupVersion: extensionsv1beta1.SchemeGroupVersion,
  375. path: "/namespaces/test/deployments/nginx",
  376. args: []string{"deployment", "nginx", "*=thingy"},
  377. },
  378. {
  379. name: "set image appsv1beta1 Deployment",
  380. object: &appsv1beta1.Deployment{
  381. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  382. Spec: appsv1beta1.DeploymentSpec{
  383. Template: corev1.PodTemplateSpec{
  384. Spec: corev1.PodSpec{
  385. Containers: []corev1.Container{
  386. {
  387. Name: "nginx",
  388. Image: "nginx",
  389. },
  390. },
  391. InitContainers: []corev1.Container{
  392. {
  393. Name: "busybox",
  394. Image: "busybox",
  395. },
  396. },
  397. },
  398. },
  399. },
  400. },
  401. groupVersion: appsv1beta1.SchemeGroupVersion,
  402. path: "/namespaces/test/deployments/nginx",
  403. args: []string{"deployment", "nginx", "*=thingy"},
  404. },
  405. {
  406. name: "set image appsv1beta2 Deployment",
  407. object: &appsv1beta2.Deployment{
  408. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  409. Spec: appsv1beta2.DeploymentSpec{
  410. Template: corev1.PodTemplateSpec{
  411. Spec: corev1.PodSpec{
  412. Containers: []corev1.Container{
  413. {
  414. Name: "nginx",
  415. Image: "nginx",
  416. },
  417. },
  418. InitContainers: []corev1.Container{
  419. {
  420. Name: "busybox",
  421. Image: "busybox",
  422. },
  423. },
  424. },
  425. },
  426. },
  427. },
  428. groupVersion: appsv1beta2.SchemeGroupVersion,
  429. path: "/namespaces/test/deployments/nginx",
  430. args: []string{"deployment", "nginx", "*=thingy"},
  431. },
  432. {
  433. name: "set image appsv1 Deployment",
  434. object: &appsv1.Deployment{
  435. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  436. Spec: appsv1.DeploymentSpec{
  437. Template: corev1.PodTemplateSpec{
  438. Spec: corev1.PodSpec{
  439. Containers: []corev1.Container{
  440. {
  441. Name: "nginx",
  442. Image: "nginx",
  443. },
  444. },
  445. InitContainers: []corev1.Container{
  446. {
  447. Name: "busybox",
  448. Image: "busybox",
  449. },
  450. },
  451. },
  452. },
  453. },
  454. },
  455. groupVersion: appsv1.SchemeGroupVersion,
  456. path: "/namespaces/test/deployments/nginx",
  457. args: []string{"deployment", "nginx", "*=thingy"},
  458. },
  459. {
  460. name: "set image appsv1beta1 StatefulSet",
  461. object: &appsv1beta1.StatefulSet{
  462. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  463. Spec: appsv1beta1.StatefulSetSpec{
  464. Template: corev1.PodTemplateSpec{
  465. Spec: corev1.PodSpec{
  466. Containers: []corev1.Container{
  467. {
  468. Name: "nginx",
  469. Image: "nginx",
  470. },
  471. },
  472. InitContainers: []corev1.Container{
  473. {
  474. Name: "busybox",
  475. Image: "busybox",
  476. },
  477. },
  478. },
  479. },
  480. },
  481. },
  482. groupVersion: appsv1beta1.SchemeGroupVersion,
  483. path: "/namespaces/test/statefulsets/nginx",
  484. args: []string{"statefulset", "nginx", "*=thingy"},
  485. },
  486. {
  487. name: "set image appsv1beta2 StatefulSet",
  488. object: &appsv1beta2.StatefulSet{
  489. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  490. Spec: appsv1beta2.StatefulSetSpec{
  491. Template: corev1.PodTemplateSpec{
  492. Spec: corev1.PodSpec{
  493. Containers: []corev1.Container{
  494. {
  495. Name: "nginx",
  496. Image: "nginx",
  497. },
  498. },
  499. InitContainers: []corev1.Container{
  500. {
  501. Name: "busybox",
  502. Image: "busybox",
  503. },
  504. },
  505. },
  506. },
  507. },
  508. },
  509. groupVersion: appsv1beta2.SchemeGroupVersion,
  510. path: "/namespaces/test/statefulsets/nginx",
  511. args: []string{"statefulset", "nginx", "*=thingy"},
  512. },
  513. {
  514. name: "set image appsv1 StatefulSet",
  515. object: &appsv1.StatefulSet{
  516. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  517. Spec: appsv1.StatefulSetSpec{
  518. Template: corev1.PodTemplateSpec{
  519. Spec: corev1.PodSpec{
  520. Containers: []corev1.Container{
  521. {
  522. Name: "nginx",
  523. Image: "nginx",
  524. },
  525. },
  526. InitContainers: []corev1.Container{
  527. {
  528. Name: "busybox",
  529. Image: "busybox",
  530. },
  531. },
  532. },
  533. },
  534. },
  535. },
  536. groupVersion: appsv1.SchemeGroupVersion,
  537. path: "/namespaces/test/statefulsets/nginx",
  538. args: []string{"statefulset", "nginx", "*=thingy"},
  539. },
  540. {
  541. name: "set image batchv1 Job",
  542. object: &batchv1.Job{
  543. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  544. Spec: batchv1.JobSpec{
  545. Template: corev1.PodTemplateSpec{
  546. Spec: corev1.PodSpec{
  547. Containers: []corev1.Container{
  548. {
  549. Name: "nginx",
  550. Image: "nginx",
  551. },
  552. },
  553. InitContainers: []corev1.Container{
  554. {
  555. Name: "busybox",
  556. Image: "busybox",
  557. },
  558. },
  559. },
  560. },
  561. },
  562. },
  563. groupVersion: batchv1.SchemeGroupVersion,
  564. path: "/namespaces/test/jobs/nginx",
  565. args: []string{"job", "nginx", "*=thingy"},
  566. },
  567. {
  568. name: "set image corev1.ReplicationController",
  569. object: &corev1.ReplicationController{
  570. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  571. Spec: corev1.ReplicationControllerSpec{
  572. Template: &corev1.PodTemplateSpec{
  573. Spec: corev1.PodSpec{
  574. Containers: []corev1.Container{
  575. {
  576. Name: "nginx",
  577. Image: "nginx",
  578. },
  579. },
  580. InitContainers: []corev1.Container{
  581. {
  582. Name: "busybox",
  583. Image: "busybox",
  584. },
  585. },
  586. },
  587. },
  588. },
  589. },
  590. groupVersion: corev1.SchemeGroupVersion,
  591. path: "/namespaces/test/replicationcontrollers/nginx",
  592. args: []string{"replicationcontroller", "nginx", "*=thingy"},
  593. },
  594. }
  595. for _, input := range inputs {
  596. t.Run(input.name, func(t *testing.T) {
  597. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  598. defer tf.Cleanup()
  599. tf.Client = &fake.RESTClient{
  600. GroupVersion: input.groupVersion,
  601. NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
  602. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  603. switch p, m := req.URL.Path, req.Method; {
  604. case p == input.path && m == http.MethodGet:
  605. return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
  606. case p == input.path && m == http.MethodPatch:
  607. stream, err := req.GetBody()
  608. if err != nil {
  609. return nil, err
  610. }
  611. bytes, err := ioutil.ReadAll(stream)
  612. if err != nil {
  613. return nil, err
  614. }
  615. assert.Contains(t, string(bytes), `"image":`+`"`+"thingy"+`"`, fmt.Sprintf("image not updated for %#v", input.object))
  616. return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
  617. default:
  618. t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req)
  619. return nil, fmt.Errorf("unexpected request")
  620. }
  621. }),
  622. }
  623. outputFormat := "yaml"
  624. streams := genericclioptions.NewTestIOStreamsDiscard()
  625. cmd := NewCmdImage(tf, streams)
  626. cmd.Flags().Set("output", outputFormat)
  627. opts := SetImageOptions{
  628. PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
  629. Local: false,
  630. IOStreams: streams,
  631. }
  632. err := opts.Complete(tf, cmd, input.args)
  633. assert.NoError(t, err)
  634. err = opts.Run()
  635. assert.NoError(t, err)
  636. })
  637. }
  638. }
  639. func TestSetImageRemoteWithSpecificContainers(t *testing.T) {
  640. inputs := []struct {
  641. name string
  642. object runtime.Object
  643. groupVersion schema.GroupVersion
  644. path string
  645. args []string
  646. }{
  647. {
  648. name: "set container image only",
  649. object: &extensionsv1beta1.ReplicaSet{
  650. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  651. Spec: extensionsv1beta1.ReplicaSetSpec{
  652. Template: corev1.PodTemplateSpec{
  653. Spec: corev1.PodSpec{
  654. Containers: []corev1.Container{
  655. {
  656. Name: "nginx",
  657. Image: "nginx",
  658. },
  659. },
  660. InitContainers: []corev1.Container{
  661. {
  662. Name: "busybox",
  663. Image: "busybox",
  664. },
  665. },
  666. },
  667. },
  668. },
  669. },
  670. groupVersion: extensionsv1beta1.SchemeGroupVersion,
  671. path: "/namespaces/test/replicasets/nginx",
  672. args: []string{"replicaset", "nginx", "nginx=thingy"},
  673. },
  674. {
  675. name: "set initContainer image only",
  676. object: &appsv1beta2.ReplicaSet{
  677. ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
  678. Spec: appsv1beta2.ReplicaSetSpec{
  679. Template: corev1.PodTemplateSpec{
  680. Spec: corev1.PodSpec{
  681. Containers: []corev1.Container{
  682. {
  683. Name: "busybox",
  684. Image: "busybox",
  685. },
  686. },
  687. InitContainers: []corev1.Container{
  688. {
  689. Name: "nginx",
  690. Image: "nginx",
  691. },
  692. },
  693. },
  694. },
  695. },
  696. },
  697. groupVersion: appsv1beta2.SchemeGroupVersion,
  698. path: "/namespaces/test/replicasets/nginx",
  699. args: []string{"replicaset", "nginx", "nginx=thingy"},
  700. },
  701. }
  702. for _, input := range inputs {
  703. t.Run(input.name, func(t *testing.T) {
  704. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  705. defer tf.Cleanup()
  706. tf.Client = &fake.RESTClient{
  707. GroupVersion: input.groupVersion,
  708. NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
  709. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  710. switch p, m := req.URL.Path, req.Method; {
  711. case p == input.path && m == http.MethodGet:
  712. return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
  713. case p == input.path && m == http.MethodPatch:
  714. stream, err := req.GetBody()
  715. if err != nil {
  716. return nil, err
  717. }
  718. bytes, err := ioutil.ReadAll(stream)
  719. if err != nil {
  720. return nil, err
  721. }
  722. assert.Contains(t, string(bytes), `"image":"`+"thingy"+`","name":`+`"nginx"`, fmt.Sprintf("image not updated for %#v", input.object))
  723. assert.NotContains(t, string(bytes), `"image":"`+"thingy"+`","name":`+`"busybox"`, fmt.Sprintf("image updated for %#v", input.object))
  724. return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
  725. default:
  726. t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req)
  727. return nil, fmt.Errorf("unexpected request")
  728. }
  729. }),
  730. }
  731. outputFormat := "yaml"
  732. streams := genericclioptions.NewTestIOStreamsDiscard()
  733. cmd := NewCmdImage(tf, streams)
  734. cmd.Flags().Set("output", outputFormat)
  735. opts := SetImageOptions{
  736. PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
  737. Local: false,
  738. IOStreams: streams,
  739. }
  740. err := opts.Complete(tf, cmd, input.args)
  741. assert.NoError(t, err)
  742. err = opts.Run()
  743. assert.NoError(t, err)
  744. })
  745. }
  746. }