1
2
3
4 package kio_test
5
6 import (
7 "bytes"
8 "strings"
9 "testing"
10
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13 "sigs.k8s.io/kustomize/kyaml/kio"
14 "sigs.k8s.io/kustomize/kyaml/yaml"
15 )
16
17 func TestByteReadWriter(t *testing.T) {
18 type testCase struct {
19 name string
20 err string
21 input string
22 expectedOutput string
23 instance kio.ByteReadWriter
24 }
25
26 testCases := []testCase{
27 {
28 name: "round_trip",
29 input: `
30 apiVersion: config.kubernetes.io/v1
31 kind: ResourceList
32 items:
33 - kind: Deployment
34 spec:
35 replicas: 1
36 - kind: Service
37 spec:
38 selectors:
39 foo: bar
40 `,
41 expectedOutput: `
42 apiVersion: config.kubernetes.io/v1
43 kind: ResourceList
44 items:
45 - kind: Deployment
46 spec:
47 replicas: 1
48 - kind: Service
49 spec:
50 selectors:
51 foo: bar
52 `,
53 },
54
55 {
56 name: "function_config",
57 input: `
58 apiVersion: config.kubernetes.io/v1
59 kind: ResourceList
60 items:
61 - kind: Deployment
62 spec:
63 replicas: 1
64 - kind: Service
65 spec:
66 selectors:
67 foo: bar
68 functionConfig:
69 a: b # something
70 `,
71 expectedOutput: `
72 apiVersion: config.kubernetes.io/v1
73 kind: ResourceList
74 items:
75 - kind: Deployment
76 spec:
77 replicas: 1
78 - kind: Service
79 spec:
80 selectors:
81 foo: bar
82 functionConfig:
83 a: b # something
84 `,
85 },
86
87 {
88 name: "results",
89 input: `
90 apiVersion: config.kubernetes.io/v1
91 kind: ResourceList
92 items:
93 - kind: Deployment
94 spec:
95 replicas: 1
96 - kind: Service
97 spec:
98 selectors:
99 foo: bar
100 results:
101 a: b # something
102 `,
103 expectedOutput: `
104 apiVersion: config.kubernetes.io/v1
105 kind: ResourceList
106 items:
107 - kind: Deployment
108 spec:
109 replicas: 1
110 - kind: Service
111 spec:
112 selectors:
113 foo: bar
114 results:
115 a: b # something
116 `,
117 },
118
119 {
120 name: "drop_invalid_resource_list_field",
121 input: `
122 apiVersion: config.kubernetes.io/v1
123 kind: ResourceList
124 items:
125 - kind: Deployment
126 spec:
127 replicas: 1
128 - kind: Service
129 spec:
130 selectors:
131 foo: bar
132 foo:
133 a: b # something
134 `,
135 expectedOutput: `
136 apiVersion: config.kubernetes.io/v1
137 kind: ResourceList
138 items:
139 - kind: Deployment
140 spec:
141 replicas: 1
142 - kind: Service
143 spec:
144 selectors:
145 foo: bar
146 `,
147 },
148
149 {
150 name: "list",
151 input: `
152 apiVersion: v1
153 kind: List
154 items:
155 - kind: Deployment
156 spec:
157 replicas: 1
158 - kind: Service
159 spec:
160 selectors:
161 foo: bar
162 `,
163 expectedOutput: `
164 apiVersion: v1
165 kind: List
166 items:
167 - kind: Deployment
168 spec:
169 replicas: 1
170 - kind: Service
171 spec:
172 selectors:
173 foo: bar
174 `,
175 },
176
177 {
178 name: "multiple_documents",
179 input: `
180 kind: Deployment
181 spec:
182 replicas: 1
183 ---
184 kind: Service
185 spec:
186 selectors:
187 foo: bar
188 `,
189 expectedOutput: `
190 kind: Deployment
191 spec:
192 replicas: 1
193 ---
194 kind: Service
195 spec:
196 selectors:
197 foo: bar
198 `,
199 },
200
201 {
202 name: "keep_annotations",
203 input: `
204 kind: Deployment
205 spec:
206 replicas: 1
207 ---
208 kind: Service
209 spec:
210 selectors:
211 foo: bar
212 `,
213 expectedOutput: `
214 kind: Deployment
215 spec:
216 replicas: 1
217 metadata:
218 annotations:
219 config.kubernetes.io/index: '0'
220 internal.config.kubernetes.io/index: '0'
221 ---
222 kind: Service
223 spec:
224 selectors:
225 foo: bar
226 metadata:
227 annotations:
228 config.kubernetes.io/index: '1'
229 internal.config.kubernetes.io/index: '1'
230 `,
231 instance: kio.ByteReadWriter{KeepReaderAnnotations: true},
232 },
233
234 {
235 name: "manual_override_wrap",
236 input: `
237 apiVersion: config.kubernetes.io/v1
238 kind: ResourceList
239 items:
240 - kind: Deployment
241 spec:
242 replicas: 1
243 - kind: Service
244 spec:
245 selectors:
246 foo: bar
247 functionConfig:
248 a: b # something
249 `,
250 expectedOutput: `
251 kind: Deployment
252 spec:
253 replicas: 1
254 ---
255 kind: Service
256 spec:
257 selectors:
258 foo: bar
259 `,
260 instance: kio.ByteReadWriter{NoWrap: true},
261 },
262
263 {
264 name: "manual_override_function_config",
265 input: `
266 apiVersion: config.kubernetes.io/v1
267 kind: ResourceList
268 items:
269 - kind: Deployment
270 spec:
271 replicas: 1
272 - kind: Service
273 spec:
274 selectors:
275 foo: bar
276 functionConfig:
277 a: b # something
278 `,
279 expectedOutput: `
280 apiVersion: config.kubernetes.io/v1
281 kind: ResourceList
282 items:
283 - kind: Deployment
284 spec:
285 replicas: 1
286 - kind: Service
287 spec:
288 selectors:
289 foo: bar
290 functionConfig:
291 c: d
292 `,
293 instance: kio.ByteReadWriter{FunctionConfig: yaml.MustParse(`c: d`)},
294 },
295 {
296 name: "anchors_not_inflated",
297 input: `
298 kind: ConfigMap
299 metadata:
300 name: foo
301 data:
302 color: &color-used blue
303 feeling: *color-used
304 `,
305
306
307
308
309
310
311
312
313
314 expectedOutput: `
315 kind: ConfigMap
316 metadata:
317 name: foo
318 data:
319 color: &color-used blue
320 feeling: *color-used
321 `,
322 },
323 }
324
325 for i := range testCases {
326 tc := testCases[i]
327 t.Run(tc.name, func(t *testing.T) {
328 var in, out bytes.Buffer
329 in.WriteString(tc.input)
330 w := tc.instance
331 w.Writer = &out
332 w.Reader = &in
333
334 nodes, err := w.Read()
335 if !assert.NoError(t, err) {
336 t.FailNow()
337 }
338
339 err = w.Write(nodes)
340 if !assert.NoError(t, err) {
341 t.FailNow()
342 }
343
344 if tc.err != "" {
345 if !assert.EqualError(t, err, tc.err) {
346 t.FailNow()
347 }
348 return
349 }
350
351 if !assert.Equal(t,
352 strings.TrimSpace(tc.expectedOutput), strings.TrimSpace(out.String())) {
353 t.FailNow()
354 }
355 })
356 }
357 }
358
359 func TestByteReadWriter_RetainSeqIndent(t *testing.T) {
360 type testCase struct {
361 name string
362 err string
363 input string
364 expectedOutput string
365 instance kio.ByteReadWriter
366 }
367
368 testCases := []testCase{
369 {
370 name: "round_trip with 2 space seq indent",
371 input: `
372 apiVersion: apps/v1
373 kind: Deployment
374 spec:
375 - foo
376 - bar
377 ---
378 apiVersion: v1
379 kind: Service
380 spec:
381 - foo
382 - bar
383 `,
384 expectedOutput: `
385 apiVersion: apps/v1
386 kind: Deployment
387 spec:
388 - foo
389 - bar
390 ---
391 apiVersion: v1
392 kind: Service
393 spec:
394 - foo
395 - bar
396 `,
397 },
398 {
399 name: "round_trip with 0 space seq indent",
400 input: `
401 apiVersion: apps/v1
402 kind: Deployment
403 spec:
404 - foo
405 - bar
406 ---
407 apiVersion: v1
408 kind: Service
409 spec:
410 - foo
411 - bar
412 `,
413 expectedOutput: `
414 apiVersion: apps/v1
415 kind: Deployment
416 spec:
417 - foo
418 - bar
419 ---
420 apiVersion: v1
421 kind: Service
422 spec:
423 - foo
424 - bar
425 `,
426 },
427 {
428 name: "round_trip with different indentations",
429 input: `
430 apiVersion: apps/v1
431 kind: Deployment
432 spec:
433 - foo
434 - bar
435 - baz
436 ---
437 apiVersion: v1
438 kind: Service
439 spec:
440 - foo
441 - bar
442 `,
443 expectedOutput: `
444 apiVersion: apps/v1
445 kind: Deployment
446 spec:
447 - foo
448 - bar
449 - baz
450 ---
451 apiVersion: v1
452 kind: Service
453 spec:
454 - foo
455 - bar
456 `,
457 },
458 {
459 name: "round_trip with mixed indentations in same resource, wide wins as it is first",
460 input: `
461 apiVersion: apps/v1
462 kind: Deployment
463 spec:
464 - foo
465 env:
466 - foo
467 - bar
468 `,
469 expectedOutput: `
470 apiVersion: apps/v1
471 kind: Deployment
472 spec:
473 - foo
474 env:
475 - foo
476 - bar
477 `,
478 },
479 {
480 name: "round_trip with mixed indentations in same resource, compact wins as it is first",
481 input: `
482 apiVersion: apps/v1
483 kind: Deployment
484 spec:
485 - foo
486 env:
487 - foo
488 - bar
489 `,
490 expectedOutput: `
491 apiVersion: apps/v1
492 kind: Deployment
493 spec:
494 - foo
495 env:
496 - foo
497 - bar
498 `,
499 },
500 {
501 name: "unwrap ResourceList with annotations",
502 input: `
503 apiVersion: config.kubernetes.io/v1
504 kind: ResourceList
505 items:
506 - kind: Deployment
507 metadata:
508 annotations:
509 internal.config.kubernetes.io/seqindent: "compact"
510 spec:
511 - foo
512 - bar
513 - kind: Service
514 metadata:
515 annotations:
516 internal.config.kubernetes.io/seqindent: "wide"
517 spec:
518 - foo
519 - bar
520 `,
521 expectedOutput: `
522 kind: Deployment
523 spec:
524 - foo
525 - bar
526 ---
527 kind: Service
528 spec:
529 - foo
530 - bar
531 `,
532 },
533 {
534 name: "round_trip with mixed indentations in same resource, wide wins as it is first",
535 input: `
536 apiVersion: apps/v1
537 kind: Deployment
538 spec:
539 - foo
540 - bar
541 env:
542 - foo
543 - bar
544 - baz
545 `,
546 expectedOutput: `
547 apiVersion: apps/v1
548 kind: Deployment
549 spec:
550 - foo
551 - bar
552 env:
553 - foo
554 - bar
555 - baz
556 `,
557 },
558 }
559
560 for i := range testCases {
561 tc := testCases[i]
562 t.Run(tc.name, func(t *testing.T) {
563 var in, out bytes.Buffer
564 in.WriteString(tc.input)
565 w := tc.instance
566 w.Writer = &out
567 w.Reader = &in
568 w.PreserveSeqIndent = true
569
570 nodes, err := w.Read()
571 if !assert.NoError(t, err) {
572 t.FailNow()
573 }
574
575 w.WrappingKind = ""
576 err = w.Write(nodes)
577 if !assert.NoError(t, err) {
578 t.FailNow()
579 }
580
581 if tc.err != "" {
582 if !assert.EqualError(t, err, tc.err) {
583 t.FailNow()
584 }
585 return
586 }
587
588 if !assert.Equal(t,
589 strings.TrimSpace(tc.expectedOutput), strings.TrimSpace(out.String())) {
590 t.FailNow()
591 }
592 })
593 }
594 }
595
596 func TestByteReadWriter_WrapBareSeqNode(t *testing.T) {
597 type testCase struct {
598 name string
599 readerErr string
600 writerErr string
601 input string
602 wrapBareSeqNode bool
603 expectedOutput string
604 instance kio.ByteReadWriter
605 }
606
607 testCases := []testCase{
608 {
609 name: "round_trip bare seq node simple",
610 wrapBareSeqNode: true,
611 input: `
612 - foo
613 - bar
614 `,
615 expectedOutput: `
616 - foo
617 - bar
618 `,
619 },
620 {
621 name: "round_trip bare seq node",
622 wrapBareSeqNode: true,
623 input: `# Use the old CRD because of the quantity validation issue:
624 # https://github.com/kubeflow/kubeflow/issues/5722
625 - op: replace
626 path: /spec
627 value:
628 group: kubeflow.org
629 names:
630 kind: Notebook
631 plural: notebooks
632 singular: notebook
633 scope: Namespaced
634 subresources:
635 status: {}
636 versions:
637 - name: v1alpha1
638 served: true
639 storage: false
640 `,
641 expectedOutput: `# Use the old CRD because of the quantity validation issue:
642 # https://github.com/kubeflow/kubeflow/issues/5722
643 - op: replace
644 path: /spec
645 value:
646 group: kubeflow.org
647 names:
648 kind: Notebook
649 plural: notebooks
650 singular: notebook
651 scope: Namespaced
652 subresources:
653 status: {}
654 versions:
655 - name: v1alpha1
656 served: true
657 storage: false
658 `,
659 },
660 {
661 name: "error round_trip bare seq node simple",
662 wrapBareSeqNode: false,
663 input: `
664 - foo
665 - bar
666 `,
667 readerErr: "wrong node kind: expected MappingNode but got SequenceNode",
668 },
669 {
670 name: "error round_trip bare seq node",
671 wrapBareSeqNode: false,
672 input: `# Use the old CRD because of the quantity validation issue:
673 # https://github.com/kubeflow/kubeflow/issues/5722
674 - op: replace
675 path: /spec
676 value:
677 group: kubeflow.org
678 names:
679 kind: Notebook
680 plural: notebooks
681 singular: notebook
682 scope: Namespaced
683 subresources:
684 status: {}
685 versions:
686 - name: v1alpha1
687 served: true
688 storage: false
689 `,
690 readerErr: "wrong node kind: expected MappingNode but got SequenceNode",
691 },
692 {
693 name: "round_trip bare seq node json",
694 wrapBareSeqNode: true,
695 input: `[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--namespaced"}]`,
696 expectedOutput: `[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--namespaced"}]`,
697 },
698 {
699 name: "error round_trip invalid yaml node",
700 wrapBareSeqNode: false,
701 input: "I am not valid",
702 readerErr: "wrong node kind: expected MappingNode but got ScalarNode",
703 },
704 }
705
706 for i := range testCases {
707 tc := testCases[i]
708 t.Run(tc.name, func(t *testing.T) {
709 var in, out bytes.Buffer
710 in.WriteString(tc.input)
711 w := tc.instance
712 w.Writer = &out
713 w.Reader = &in
714 w.PreserveSeqIndent = true
715 w.WrapBareSeqNode = tc.wrapBareSeqNode
716
717 nodes, err := w.Read()
718 if tc.readerErr != "" {
719 if !assert.Error(t, err) {
720 t.FailNow()
721 }
722 if !assert.Contains(t, err.Error(), tc.readerErr) {
723 t.FailNow()
724 }
725 return
726 }
727
728 w.WrappingKind = ""
729 err = w.Write(nodes)
730 if !assert.NoError(t, err) {
731 t.FailNow()
732 }
733
734 if tc.writerErr != "" {
735 if !assert.Error(t, err) {
736 t.FailNow()
737 }
738 if !assert.Contains(t, err.Error(), tc.writerErr) {
739 t.FailNow()
740 }
741 return
742 }
743
744 if !assert.Equal(t,
745 strings.TrimSpace(tc.expectedOutput), strings.TrimSpace(out.String())) {
746 t.FailNow()
747 }
748 })
749 }
750 }
751
752 func TestByteReadWriter_ResourceListWrapping(t *testing.T) {
753 singleDeployment := `kind: Deployment
754 apiVersion: v1
755 metadata:
756 name: tester
757 namespace: default
758 spec:
759 replicas: 0`
760 resourceList := `apiVersion: config.kubernetes.io/v1
761 kind: ResourceList
762 items:
763 - kind: Deployment
764 apiVersion: v1
765 metadata:
766 name: tester
767 namespace: default
768 spec:
769 replicas: 0`
770 resourceListWithError := `apiVersion: config.kubernetes.io/v1
771 kind: ResourceList
772 items:
773 - kind: Deployment
774 apiVersion: v1
775 metadata:
776 name: tester
777 namespace: default
778 spec:
779 replicas: 0
780 results:
781 - message: some error
782 severity: error`
783 resourceListDifferentWrapper := strings.NewReplacer(
784 "kind: ResourceList", "kind: SomethingElse",
785 "apiVersion: config.kubernetes.io/v1", "apiVersion: fakeVersion",
786 ).Replace(resourceList)
787
788 testCases := []struct {
789 desc string
790 noWrap bool
791 wrapKind string
792 wrapAPIVersion string
793 input string
794 want string
795 }{
796 {
797 desc: "resource list",
798 input: resourceList,
799 want: resourceList,
800 },
801 {
802 desc: "individual resources",
803 input: singleDeployment,
804 want: singleDeployment,
805 },
806 {
807 desc: "no nested wrapping",
808 wrapKind: kio.ResourceListKind,
809 wrapAPIVersion: kio.ResourceListAPIVersion,
810 input: resourceList,
811 want: resourceList,
812 },
813 {
814 desc: "unwrap resource list",
815 noWrap: true,
816 input: resourceList,
817 want: singleDeployment,
818 },
819 {
820 desc: "wrap individual resources",
821 wrapKind: kio.ResourceListKind,
822 wrapAPIVersion: kio.ResourceListAPIVersion,
823 input: singleDeployment,
824 want: resourceList,
825 },
826 {
827 desc: "NoWrap has precedence",
828 noWrap: true,
829 wrapKind: kio.ResourceListKind,
830 wrapAPIVersion: kio.ResourceListAPIVersion,
831 input: singleDeployment,
832 want: singleDeployment,
833 },
834 {
835 desc: "honor specified wrapping kind",
836 wrapKind: "SomethingElse",
837 wrapAPIVersion: "fakeVersion",
838 input: resourceList,
839 want: resourceListDifferentWrapper,
840 },
841 {
842 desc: "passthrough results",
843 input: resourceListWithError,
844 want: resourceListWithError,
845 },
846 }
847
848 for i := range testCases {
849 tc := testCases[i]
850 t.Run(tc.desc, func(t *testing.T) {
851 var got bytes.Buffer
852 rw := kio.ByteReadWriter{
853 Reader: strings.NewReader(tc.input),
854 Writer: &got,
855 NoWrap: tc.noWrap,
856 WrappingAPIVersion: tc.wrapAPIVersion,
857 WrappingKind: tc.wrapKind,
858 }
859
860 rnodes, err := rw.Read()
861 require.NoError(t, err)
862
863 err = rw.Write(rnodes)
864 require.NoError(t, err)
865
866 assert.Equal(t, tc.want, strings.TrimSpace(got.String()))
867 })
868 }
869 }
870
View as plain text