1
16
17 package set
18
19 import (
20 "fmt"
21 "io"
22 "net/http"
23 "strings"
24 "testing"
25
26 "github.com/stretchr/testify/assert"
27 appsv1 "k8s.io/api/apps/v1"
28 appsv1beta1 "k8s.io/api/apps/v1beta1"
29 appsv1beta2 "k8s.io/api/apps/v1beta2"
30 batchv1 "k8s.io/api/batch/v1"
31 corev1 "k8s.io/api/core/v1"
32 extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34 "k8s.io/apimachinery/pkg/runtime"
35 "k8s.io/apimachinery/pkg/runtime/schema"
36 "k8s.io/cli-runtime/pkg/genericclioptions"
37 "k8s.io/cli-runtime/pkg/genericiooptions"
38 "k8s.io/cli-runtime/pkg/resource"
39 restclient "k8s.io/client-go/rest"
40 "k8s.io/client-go/rest/fake"
41 cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
42 "k8s.io/kubectl/pkg/scheme"
43 )
44
45 func TestResourcesLocal(t *testing.T) {
46 tf := cmdtesting.NewTestFactory().WithNamespace("test")
47 defer tf.Cleanup()
48
49 tf.Client = &fake.RESTClient{
50 GroupVersion: schema.GroupVersion{Version: ""},
51 NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
52 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
53 t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
54 return nil, nil
55 }),
56 }
57 tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
58
59 outputFormat := "name"
60
61 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
62 cmd := NewCmdResources(tf, streams)
63 cmd.SetOut(buf)
64 cmd.SetErr(buf)
65 cmd.Flags().Set("output", outputFormat)
66 cmd.Flags().Set("local", "true")
67
68 opts := SetResourcesOptions{
69 PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
70 FilenameOptions: resource.FilenameOptions{
71 Filenames: []string{"../../../testdata/controller.yaml"}},
72 Local: true,
73 Limits: "cpu=200m,memory=512Mi",
74 Requests: "cpu=200m,memory=512Mi",
75 ContainerSelector: "*",
76 IOStreams: streams,
77 }
78
79 err := opts.Complete(tf, cmd, []string{})
80 if err == nil {
81 err = opts.Validate()
82 }
83 if err == nil {
84 err = opts.Run()
85 }
86 if err != nil {
87 t.Fatalf("unexpected error: %v", err)
88 }
89 if !strings.Contains(buf.String(), "replicationcontroller/cassandra") {
90 t.Errorf("did not set resources: %s", buf.String())
91 }
92 }
93
94 func TestSetMultiResourcesLimitsLocal(t *testing.T) {
95 tf := cmdtesting.NewTestFactory().WithNamespace("test")
96 defer tf.Cleanup()
97
98 tf.Client = &fake.RESTClient{
99 GroupVersion: schema.GroupVersion{Version: ""},
100 NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
101 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
102 t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
103 return nil, nil
104 }),
105 }
106 tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
107
108 outputFormat := "name"
109
110 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
111 cmd := NewCmdResources(tf, streams)
112 cmd.SetOut(buf)
113 cmd.SetErr(buf)
114 cmd.Flags().Set("output", outputFormat)
115 cmd.Flags().Set("local", "true")
116
117 opts := SetResourcesOptions{
118 PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
119 FilenameOptions: resource.FilenameOptions{
120 Filenames: []string{"../../../testdata/set/multi-resource-yaml.yaml"}},
121 Local: true,
122 Limits: "cpu=200m,memory=512Mi",
123 Requests: "cpu=200m,memory=512Mi",
124 ContainerSelector: "*",
125 IOStreams: streams,
126 }
127
128 err := opts.Complete(tf, cmd, []string{})
129 if err == nil {
130 err = opts.Validate()
131 }
132 if err == nil {
133 err = opts.Run()
134 }
135 if err != nil {
136 t.Fatalf("unexpected error: %v", err)
137 }
138 expectedOut := "replicationcontroller/first-rc\nreplicationcontroller/second-rc\n"
139 if buf.String() != expectedOut {
140 t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String())
141 }
142 }
143
144 func TestSetResourcesRemote(t *testing.T) {
145 inputs := []struct {
146 name string
147 object runtime.Object
148 groupVersion schema.GroupVersion
149 path string
150 args []string
151 }{
152 {
153 name: "set image extensionsv1beta1 ReplicaSet",
154 object: &extensionsv1beta1.ReplicaSet{
155 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
156 Spec: extensionsv1beta1.ReplicaSetSpec{
157 Template: corev1.PodTemplateSpec{
158 Spec: corev1.PodSpec{
159 Containers: []corev1.Container{
160 {
161 Name: "nginx",
162 Image: "nginx",
163 },
164 },
165 },
166 },
167 },
168 },
169 groupVersion: extensionsv1beta1.SchemeGroupVersion,
170 path: "/namespaces/test/replicasets/nginx",
171 args: []string{"replicaset", "nginx"},
172 },
173 {
174 name: "set image appsv1beta2 ReplicaSet",
175 object: &appsv1beta2.ReplicaSet{
176 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
177 Spec: appsv1beta2.ReplicaSetSpec{
178 Template: corev1.PodTemplateSpec{
179 Spec: corev1.PodSpec{
180 Containers: []corev1.Container{
181 {
182 Name: "nginx",
183 Image: "nginx",
184 },
185 },
186 },
187 },
188 },
189 },
190 groupVersion: appsv1beta2.SchemeGroupVersion,
191 path: "/namespaces/test/replicasets/nginx",
192 args: []string{"replicaset", "nginx"},
193 },
194 {
195 name: "set image appsv1 ReplicaSet",
196 object: &appsv1.ReplicaSet{
197 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
198 Spec: appsv1.ReplicaSetSpec{
199 Template: corev1.PodTemplateSpec{
200 Spec: corev1.PodSpec{
201 Containers: []corev1.Container{
202 {
203 Name: "nginx",
204 Image: "nginx",
205 },
206 },
207 },
208 },
209 },
210 },
211 groupVersion: appsv1.SchemeGroupVersion,
212 path: "/namespaces/test/replicasets/nginx",
213 args: []string{"replicaset", "nginx"},
214 },
215 {
216 name: "set image extensionsv1beta1 DaemonSet",
217 object: &extensionsv1beta1.DaemonSet{
218 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
219 Spec: extensionsv1beta1.DaemonSetSpec{
220 Template: corev1.PodTemplateSpec{
221 Spec: corev1.PodSpec{
222 Containers: []corev1.Container{
223 {
224 Name: "nginx",
225 Image: "nginx",
226 },
227 },
228 },
229 },
230 },
231 },
232 groupVersion: extensionsv1beta1.SchemeGroupVersion,
233 path: "/namespaces/test/daemonsets/nginx",
234 args: []string{"daemonset", "nginx"},
235 },
236 {
237 name: "set image appsv1beta2 DaemonSet",
238 object: &appsv1beta2.DaemonSet{
239 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
240 Spec: appsv1beta2.DaemonSetSpec{
241 Template: corev1.PodTemplateSpec{
242 Spec: corev1.PodSpec{
243 Containers: []corev1.Container{
244 {
245 Name: "nginx",
246 Image: "nginx",
247 },
248 },
249 },
250 },
251 },
252 },
253 groupVersion: appsv1beta2.SchemeGroupVersion,
254 path: "/namespaces/test/daemonsets/nginx",
255 args: []string{"daemonset", "nginx"},
256 },
257 {
258 name: "set image appsv1 DaemonSet",
259 object: &appsv1.DaemonSet{
260 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
261 Spec: appsv1.DaemonSetSpec{
262 Template: corev1.PodTemplateSpec{
263 Spec: corev1.PodSpec{
264 Containers: []corev1.Container{
265 {
266 Name: "nginx",
267 Image: "nginx",
268 },
269 },
270 },
271 },
272 },
273 },
274 groupVersion: appsv1.SchemeGroupVersion,
275 path: "/namespaces/test/daemonsets/nginx",
276 args: []string{"daemonset", "nginx"},
277 },
278 {
279 name: "set image extensionsv1beta1 Deployment",
280 object: &extensionsv1beta1.Deployment{
281 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
282 Spec: extensionsv1beta1.DeploymentSpec{
283 Template: corev1.PodTemplateSpec{
284 Spec: corev1.PodSpec{
285 Containers: []corev1.Container{
286 {
287 Name: "nginx",
288 Image: "nginx",
289 },
290 },
291 },
292 },
293 },
294 },
295 groupVersion: extensionsv1beta1.SchemeGroupVersion,
296 path: "/namespaces/test/deployments/nginx",
297 args: []string{"deployment", "nginx"},
298 },
299 {
300 name: "set image appsv1beta1 Deployment",
301 object: &appsv1beta1.Deployment{
302 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
303 Spec: appsv1beta1.DeploymentSpec{
304 Template: corev1.PodTemplateSpec{
305 Spec: corev1.PodSpec{
306 Containers: []corev1.Container{
307 {
308 Name: "nginx",
309 Image: "nginx",
310 },
311 },
312 },
313 },
314 },
315 },
316 groupVersion: appsv1beta1.SchemeGroupVersion,
317 path: "/namespaces/test/deployments/nginx",
318 args: []string{"deployment", "nginx"},
319 },
320 {
321 name: "set image appsv1beta2 Deployment",
322 object: &appsv1beta2.Deployment{
323 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
324 Spec: appsv1beta2.DeploymentSpec{
325 Template: corev1.PodTemplateSpec{
326 Spec: corev1.PodSpec{
327 Containers: []corev1.Container{
328 {
329 Name: "nginx",
330 Image: "nginx",
331 },
332 },
333 },
334 },
335 },
336 },
337 groupVersion: appsv1beta2.SchemeGroupVersion,
338 path: "/namespaces/test/deployments/nginx",
339 args: []string{"deployment", "nginx"},
340 },
341 {
342 name: "set image appsv1 Deployment",
343 object: &appsv1.Deployment{
344 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
345 Spec: appsv1.DeploymentSpec{
346 Template: corev1.PodTemplateSpec{
347 Spec: corev1.PodSpec{
348 Containers: []corev1.Container{
349 {
350 Name: "nginx",
351 Image: "nginx",
352 },
353 },
354 },
355 },
356 },
357 },
358 groupVersion: appsv1.SchemeGroupVersion,
359 path: "/namespaces/test/deployments/nginx",
360 args: []string{"deployment", "nginx"},
361 },
362 {
363 name: "set image appsv1beta1 StatefulSet",
364 object: &appsv1beta1.StatefulSet{
365 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
366 Spec: appsv1beta1.StatefulSetSpec{
367 Template: corev1.PodTemplateSpec{
368 Spec: corev1.PodSpec{
369 Containers: []corev1.Container{
370 {
371 Name: "nginx",
372 Image: "nginx",
373 },
374 },
375 },
376 },
377 },
378 },
379 groupVersion: appsv1beta1.SchemeGroupVersion,
380 path: "/namespaces/test/statefulsets/nginx",
381 args: []string{"statefulset", "nginx"},
382 },
383 {
384 name: "set image appsv1beta2 StatefulSet",
385 object: &appsv1beta2.StatefulSet{
386 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
387 Spec: appsv1beta2.StatefulSetSpec{
388 Template: corev1.PodTemplateSpec{
389 Spec: corev1.PodSpec{
390 Containers: []corev1.Container{
391 {
392 Name: "nginx",
393 Image: "nginx",
394 },
395 },
396 },
397 },
398 },
399 },
400 groupVersion: appsv1beta2.SchemeGroupVersion,
401 path: "/namespaces/test/statefulsets/nginx",
402 args: []string{"statefulset", "nginx"},
403 },
404 {
405 name: "set image appsv1 StatefulSet",
406 object: &appsv1.StatefulSet{
407 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
408 Spec: appsv1.StatefulSetSpec{
409 Template: corev1.PodTemplateSpec{
410 Spec: corev1.PodSpec{
411 Containers: []corev1.Container{
412 {
413 Name: "nginx",
414 Image: "nginx",
415 },
416 },
417 },
418 },
419 },
420 },
421 groupVersion: appsv1.SchemeGroupVersion,
422 path: "/namespaces/test/statefulsets/nginx",
423 args: []string{"statefulset", "nginx"},
424 },
425 {
426 name: "set image batchv1 Job",
427 object: &batchv1.Job{
428 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
429 Spec: batchv1.JobSpec{
430 Template: corev1.PodTemplateSpec{
431 Spec: corev1.PodSpec{
432 Containers: []corev1.Container{
433 {
434 Name: "nginx",
435 Image: "nginx",
436 },
437 },
438 },
439 },
440 },
441 },
442 groupVersion: batchv1.SchemeGroupVersion,
443 path: "/namespaces/test/jobs/nginx",
444 args: []string{"job", "nginx"},
445 },
446 {
447 name: "set image corev1.ReplicationController",
448 object: &corev1.ReplicationController{
449 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
450 Spec: corev1.ReplicationControllerSpec{
451 Template: &corev1.PodTemplateSpec{
452 Spec: corev1.PodSpec{
453 Containers: []corev1.Container{
454 {
455 Name: "nginx",
456 Image: "nginx",
457 },
458 },
459 },
460 },
461 },
462 },
463 groupVersion: corev1.SchemeGroupVersion,
464 path: "/namespaces/test/replicationcontrollers/nginx",
465 args: []string{"replicationcontroller", "nginx"},
466 },
467 }
468
469 for i, input := range inputs {
470 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
471 tf := cmdtesting.NewTestFactory().WithNamespace("test")
472 defer tf.Cleanup()
473
474 tf.Client = &fake.RESTClient{
475 GroupVersion: input.groupVersion,
476 NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
477 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
478 switch p, m := req.URL.Path, req.Method; {
479 case p == input.path && m == http.MethodGet:
480 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
481 case p == input.path && m == http.MethodPatch:
482 stream, err := req.GetBody()
483 if err != nil {
484 return nil, err
485 }
486 bytes, err := io.ReadAll(stream)
487 if err != nil {
488 return nil, err
489 }
490 assert.Contains(t, string(bytes), "200m", fmt.Sprintf("resources not updated for %#v", input.object))
491 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
492 default:
493 t.Errorf("%s: unexpected request: %s %#v\n%#v", "resources", req.Method, req.URL, req)
494 return nil, fmt.Errorf("unexpected request")
495 }
496 }),
497 }
498
499 outputFormat := "yaml"
500
501 streams := genericiooptions.NewTestIOStreamsDiscard()
502 cmd := NewCmdResources(tf, streams)
503 cmd.Flags().Set("output", outputFormat)
504 opts := SetResourcesOptions{
505 PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
506
507 Limits: "cpu=200m,memory=512Mi",
508 ContainerSelector: "*",
509 IOStreams: streams,
510 }
511 err := opts.Complete(tf, cmd, input.args)
512 if err == nil {
513 err = opts.Validate()
514 }
515 if err == nil {
516 err = opts.Run()
517 }
518 assert.NoError(t, err)
519 })
520 }
521 }
522
523 func TestSetResourcesRemoteWithSpecificContainers(t *testing.T) {
524 inputs := []struct {
525 name string
526 selector string
527 args []string
528
529 expectedContainers int
530 }{
531 {
532 name: "all containers",
533 args: []string{"deployments", "redis"},
534 selector: "*",
535 expectedContainers: 2,
536 },
537 {
538 name: "use wildcards to select some containers",
539 args: []string{"deployments", "redis"},
540 selector: "red*",
541 expectedContainers: 1,
542 },
543 {
544 name: "single container",
545 args: []string{"deployments", "redis"},
546 selector: "redis",
547 expectedContainers: 1,
548 },
549 }
550
551 for _, input := range inputs {
552 mockDeployment := &appsv1.Deployment{
553 ObjectMeta: metav1.ObjectMeta{
554 Name: "redis",
555 Namespace: "test",
556 },
557 Spec: appsv1.DeploymentSpec{
558 Template: corev1.PodTemplateSpec{
559 Spec: corev1.PodSpec{
560 InitContainers: []corev1.Container{
561 {
562 Name: "init",
563 Image: "redis",
564 },
565 },
566 Containers: []corev1.Container{
567 {
568 Name: "redis",
569 Image: "redis",
570 },
571 },
572 },
573 },
574 },
575 }
576 t.Run(input.name, func(t *testing.T) {
577 tf := cmdtesting.NewTestFactory().WithNamespace("test")
578 defer tf.Cleanup()
579 tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
580 tf.Client = &fake.RESTClient{
581 GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
582 NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
583 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
584 switch p, m := req.URL.Path, req.Method; {
585 case p == "/namespaces/test/deployments/redis" && m == http.MethodGet:
586 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(mockDeployment)}, nil
587 case p == "/namespaces/test/deployments/redis" && m == http.MethodPatch:
588 stream, err := req.GetBody()
589 if err != nil {
590 return nil, err
591 }
592 bytes, err := io.ReadAll(stream)
593 if err != nil {
594 return nil, err
595 }
596 updated := strings.Count(string(bytes), "200m")
597 if updated != input.expectedContainers {
598 t.Errorf("expected %d containers to be selected but got %d \n", input.expectedContainers, updated)
599 }
600 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(mockDeployment)}, nil
601 default:
602 t.Errorf("%s: unexpected request: %#v\n%#v", input.name, req.URL, req)
603 return nil, nil
604 }
605 }),
606 }
607
608 outputFormat := "yaml"
609
610 streams := genericiooptions.NewTestIOStreamsDiscard()
611 cmd := NewCmdResources(tf, streams)
612 cmd.Flags().Set("output", outputFormat)
613 opts := SetResourcesOptions{
614 PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
615
616 Limits: "cpu=200m,memory=512Mi",
617 ContainerSelector: input.selector,
618 IOStreams: streams,
619 }
620 err := opts.Complete(tf, cmd, input.args)
621 if err == nil {
622 err = opts.Validate()
623 }
624 if err == nil {
625 err = opts.Run()
626 }
627 assert.NoError(t, err)
628 })
629 }
630 }
631
View as plain text