1
2
3
4 package yaml
5
6 import (
7 "fmt"
8 "reflect"
9 "strings"
10 "testing"
11
12 "github.com/google/go-cmp/cmp"
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15 yaml "sigs.k8s.io/yaml/goyaml.v3"
16 )
17
18 func TestRNodeHasNilEntryInList(t *testing.T) {
19 testConfigMap := map[string]interface{}{
20 "apiVersion": "v1",
21 "kind": "ConfigMap",
22 "metadata": map[string]interface{}{
23 "name": "winnie",
24 },
25 }
26 type resultExpected struct {
27 hasNil bool
28 path string
29 }
30
31 testCases := map[string]struct {
32 theMap map[string]interface{}
33 rsExp resultExpected
34 }{
35 "actuallyNil": {
36 theMap: nil,
37 rsExp: resultExpected{},
38 },
39 "empty": {
40 theMap: map[string]interface{}{},
41 rsExp: resultExpected{},
42 },
43 "list": {
44 theMap: map[string]interface{}{
45 "apiVersion": "v1",
46 "kind": "List",
47 "items": []interface{}{
48 testConfigMap,
49 testConfigMap,
50 },
51 },
52 rsExp: resultExpected{},
53 },
54 "listWithNil": {
55 theMap: map[string]interface{}{
56 "apiVersion": "v1",
57 "kind": "List",
58 "items": []interface{}{
59 testConfigMap,
60 nil,
61 },
62 },
63 rsExp: resultExpected{
64 hasNil: false,
65 path: "this/should/be/non-empty",
66 },
67 },
68 }
69
70 for n := range testCases {
71 tc := testCases[n]
72 t.Run(n, func(t *testing.T) {
73 rn, err := FromMap(tc.theMap)
74 if !assert.NoError(t, err) {
75 t.FailNow()
76 }
77 hasNil, path := rn.HasNilEntryInList()
78 if tc.rsExp.hasNil {
79 if !assert.True(t, hasNil) {
80 t.FailNow()
81 }
82 if !assert.Equal(t, tc.rsExp.path, path) {
83 t.FailNow()
84 }
85 } else {
86 if !assert.False(t, hasNil) {
87 t.FailNow()
88 }
89 if !assert.Empty(t, path) {
90 t.FailNow()
91 }
92 }
93 })
94 }
95 }
96
97 func TestRNodeNewStringRNodeText(t *testing.T) {
98 rn := NewStringRNode("cat")
99 if !assert.Equal(t, `cat
100 `,
101 rn.MustString()) {
102 t.FailNow()
103 }
104 }
105
106 func TestRNodeNewStringRNodeBinary(t *testing.T) {
107 rn := NewStringRNode(string([]byte{
108 0xff,
109 0x68,
110 0x65,
111 0x6c,
112 0x6c,
113 0x6f,
114 }))
115 if !assert.Equal(t, `!!binary /2hlbGxv
116 `,
117 rn.MustString()) {
118 t.FailNow()
119 }
120 }
121
122 func TestRNodeGetDataMap(t *testing.T) {
123 emptyMap := map[string]string{}
124 testCases := map[string]struct {
125 theMap map[string]interface{}
126 expected map[string]string
127 }{
128 "actuallyNil": {
129 theMap: nil,
130 expected: emptyMap,
131 },
132 "empty": {
133 theMap: map[string]interface{}{},
134 expected: emptyMap,
135 },
136 "mostlyEmpty": {
137 theMap: map[string]interface{}{
138 "hey": "there",
139 },
140 expected: emptyMap,
141 },
142 "noNameConfigMap": {
143 theMap: map[string]interface{}{
144 "apiVersion": "v1",
145 "kind": "ConfigMap",
146 },
147 expected: emptyMap,
148 },
149 "configmap": {
150 theMap: map[string]interface{}{
151 "apiVersion": "v1",
152 "kind": "ConfigMap",
153 "metadata": map[string]interface{}{
154 "name": "winnie",
155 },
156 "data": map[string]string{
157 "wine": "cabernet",
158 "truck": "ford",
159 "rocket": "falcon9",
160 "planet": "mars",
161 "city": "brownsville",
162 },
163 },
164
165 expected: map[string]string{
166 "city": "brownsville",
167 "wine": "cabernet",
168 "planet": "mars",
169 "rocket": "falcon9",
170 "truck": "ford",
171 },
172 },
173 }
174
175 for n := range testCases {
176 tc := testCases[n]
177 t.Run(n, func(t *testing.T) {
178 rn, err := FromMap(tc.theMap)
179 if !assert.NoError(t, err) {
180 t.FailNow()
181 }
182 m := rn.GetDataMap()
183 if !assert.Equal(t, tc.expected, m) {
184 t.FailNow()
185 }
186 })
187 }
188 }
189
190 func TestRNodeGetValidatedDataMap(t *testing.T) {
191 emptyMap := map[string]string{}
192 testCases := map[string]struct {
193 theMap map[string]interface{}
194 theAllowedKeys []string
195 expected map[string]string
196 expectedError error
197 }{
198 "nilResultEmptyKeys": {
199 theMap: nil,
200 theAllowedKeys: []string{},
201 expected: emptyMap,
202 expectedError: nil,
203 },
204 "empty": {
205 theMap: map[string]interface{}{},
206 theAllowedKeys: []string{},
207 expected: emptyMap,
208 expectedError: nil,
209 },
210 "expectedKeysMatch": {
211 theMap: map[string]interface{}{
212 "apiVersion": "v1",
213 "kind": "ConfigMap",
214 "metadata": map[string]interface{}{
215 "name": "winnie",
216 },
217 "data": map[string]string{
218 "wine": "cabernet",
219 "truck": "ford",
220 "rocket": "falcon9",
221 "planet": "mars",
222 "city": "brownsville",
223 },
224 },
225 theAllowedKeys: []string{
226 "wine",
227 "truck",
228 "rocket",
229 "planet",
230 "city",
231 "plane",
232 "country",
233 },
234
235 expected: map[string]string{
236 "city": "brownsville",
237 "wine": "cabernet",
238 "planet": "mars",
239 "rocket": "falcon9",
240 "truck": "ford",
241 },
242 expectedError: nil,
243 },
244 "unexpectedKeyInConfigMap": {
245 theMap: map[string]interface{}{
246 "apiVersion": "v1",
247 "kind": "ConfigMap",
248 "metadata": map[string]interface{}{
249 "name": "winnie",
250 },
251 "data": map[string]string{
252 "wine": "cabernet",
253 "truck": "ford",
254 "rocket": "falcon9",
255 },
256 },
257 theAllowedKeys: []string{
258 "wine",
259 "truck",
260 },
261
262 expected: map[string]string{
263 "wine": "cabernet",
264 "rocket": "falcon9",
265 "truck": "ford",
266 },
267 expectedError: fmt.Errorf("an unexpected key (rocket) was found"),
268 },
269 }
270
271 for n := range testCases {
272 tc := testCases[n]
273 t.Run(n, func(t *testing.T) {
274 rn, err := FromMap(tc.theMap)
275 if !assert.NoError(t, err) {
276 t.FailNow()
277 }
278 m, err := rn.GetValidatedDataMap(tc.theAllowedKeys)
279 if !assert.Equal(t, tc.expected, m) {
280 t.FailNow()
281 }
282 if !assert.Equal(t, tc.expectedError, err) {
283 t.FailNow()
284 }
285 })
286 }
287 }
288
289 func TestRNodeSetDataMap(t *testing.T) {
290 testCases := map[string]struct {
291 theMap map[string]interface{}
292 input map[string]string
293 expected map[string]string
294 }{
295 "empty": {
296 theMap: map[string]interface{}{},
297 input: map[string]string{
298 "wine": "cabernet",
299 "truck": "ford",
300 },
301 expected: map[string]string{
302 "wine": "cabernet",
303 "truck": "ford",
304 },
305 },
306 "replace": {
307 theMap: map[string]interface{}{
308 "foo": 3,
309 "data": map[string]string{
310 "rocket": "falcon9",
311 "planet": "mars",
312 },
313 },
314 input: map[string]string{
315 "wine": "cabernet",
316 "truck": "ford",
317 },
318 expected: map[string]string{
319 "wine": "cabernet",
320 "truck": "ford",
321 },
322 },
323 "clear1": {
324 theMap: map[string]interface{}{
325 "foo": 3,
326 "data": map[string]string{
327 "rocket": "falcon9",
328 "planet": "mars",
329 },
330 },
331 input: map[string]string{},
332 expected: map[string]string{},
333 },
334 "clear2": {
335 theMap: map[string]interface{}{
336 "foo": 3,
337 "data": map[string]string{
338 "rocket": "falcon9",
339 "planet": "mars",
340 },
341 },
342 input: nil,
343 expected: map[string]string{},
344 },
345 }
346
347 for n := range testCases {
348 tc := testCases[n]
349 t.Run(n, func(t *testing.T) {
350 rn, err := FromMap(tc.theMap)
351 if !assert.NoError(t, err) {
352 t.FailNow()
353 }
354 rn.SetDataMap(tc.input)
355 m := rn.GetDataMap()
356 if !assert.Equal(t, tc.expected, m) {
357 t.FailNow()
358 }
359 })
360 }
361 }
362
363 func TestRNodeGetValidatedMetadata(t *testing.T) {
364 testConfigMap := map[string]interface{}{
365 "apiVersion": "v1",
366 "kind": "ConfigMap",
367 "metadata": map[string]interface{}{
368 "name": "winnie",
369 },
370 }
371 type resultExpected struct {
372 out ResourceMeta
373 errMsg string
374 }
375
376 testCases := map[string]struct {
377 theMap map[string]interface{}
378 rsExp resultExpected
379 }{
380 "actuallyNil": {
381 theMap: nil,
382 rsExp: resultExpected{
383 errMsg: "missing Resource metadata",
384 },
385 },
386 "empty": {
387 theMap: map[string]interface{}{},
388 rsExp: resultExpected{
389 errMsg: "missing Resource metadata",
390 },
391 },
392 "mostlyEmpty": {
393 theMap: map[string]interface{}{
394 "hey": "there",
395 },
396 rsExp: resultExpected{
397 errMsg: "missing Resource metadata",
398 },
399 },
400 "noNameConfigMap": {
401 theMap: map[string]interface{}{
402 "apiVersion": "v1",
403 "kind": "ConfigMap",
404 },
405 rsExp: resultExpected{
406 errMsg: "missing metadata.name",
407 },
408 },
409 "configmap": {
410 theMap: testConfigMap,
411 rsExp: resultExpected{
412 out: ResourceMeta{
413 TypeMeta: TypeMeta{
414 APIVersion: "v1",
415 Kind: "ConfigMap",
416 },
417 ObjectMeta: ObjectMeta{
418 NameMeta: NameMeta{
419 Name: "winnie",
420 },
421 },
422 },
423 },
424 },
425 "list": {
426 theMap: map[string]interface{}{
427 "apiVersion": "v1",
428 "kind": "List",
429 "items": []interface{}{
430 testConfigMap,
431 testConfigMap,
432 },
433 },
434 rsExp: resultExpected{
435 out: ResourceMeta{
436 TypeMeta: TypeMeta{
437 APIVersion: "v1",
438 Kind: "List",
439 },
440 },
441 },
442 },
443 "configmaplist": {
444 theMap: map[string]interface{}{
445 "apiVersion": "v1",
446 "kind": "ConfigMapList",
447 "items": []interface{}{
448 testConfigMap,
449 testConfigMap,
450 },
451 },
452 rsExp: resultExpected{
453 out: ResourceMeta{
454 TypeMeta: TypeMeta{
455 APIVersion: "v1",
456 Kind: "ConfigMapList",
457 },
458 },
459 },
460 },
461 }
462
463 for n := range testCases {
464 tc := testCases[n]
465 t.Run(n, func(t *testing.T) {
466 rn, err := FromMap(tc.theMap)
467 if !assert.NoError(t, err) {
468 t.FailNow()
469 }
470 m, err := rn.GetValidatedMetadata()
471 if tc.rsExp.errMsg == "" {
472 if !assert.NoError(t, err) {
473 t.FailNow()
474 }
475 if !assert.Equal(t, tc.rsExp.out, m) {
476 t.FailNow()
477 }
478 } else {
479 if !assert.Error(t, err) {
480 t.FailNow()
481 }
482 if !assert.Contains(t, err.Error(), tc.rsExp.errMsg) {
483 t.FailNow()
484 }
485 }
486 })
487 }
488 }
489
490 func TestRNodeMapEmpty(t *testing.T) {
491 newRNodeMap, err := NewRNode(nil).Map()
492 require.NoError(t, err)
493 assert.Equal(t, 0, len(newRNodeMap))
494 }
495
496 func TestRNodeMap(t *testing.T) {
497 wn := NewRNode(nil)
498 if err := wn.UnmarshalJSON([]byte(`{
499 "apiVersion": "apps/v1",
500 "kind": "Deployment",
501 "metadata": {
502 "name": "homer",
503 "namespace": "simpsons"
504 }
505 }`)); err != nil {
506 t.Fatalf("unexpected unmarshaljson err: %v", err)
507 }
508
509 expected := map[string]interface{}{
510 "apiVersion": "apps/v1",
511 "kind": "Deployment",
512 "metadata": map[string]interface{}{
513 "name": "homer",
514 "namespace": "simpsons",
515 },
516 }
517
518 actual, err := wn.Map()
519 require.NoError(t, err)
520 if diff := cmp.Diff(expected, actual); diff != "" {
521 t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
522 }
523 }
524
525 func TestRNodeFromMap(t *testing.T) {
526 testConfigMap := map[string]interface{}{
527 "apiVersion": "v1",
528 "kind": "ConfigMap",
529 "metadata": map[string]interface{}{
530 "name": "winnie",
531 },
532 }
533 type resultExpected struct {
534 out string
535 err error
536 }
537
538 testCases := map[string]struct {
539 theMap map[string]interface{}
540 rsExp resultExpected
541 }{
542 "actuallyNil": {
543 theMap: nil,
544 rsExp: resultExpected{
545 out: `{}`,
546 err: nil,
547 },
548 },
549 "empty": {
550 theMap: map[string]interface{}{},
551 rsExp: resultExpected{
552 out: `{}`,
553 err: nil,
554 },
555 },
556 "mostlyEmpty": {
557 theMap: map[string]interface{}{
558 "hey": "there",
559 },
560 rsExp: resultExpected{
561 out: `hey: there`,
562 err: nil,
563 },
564 },
565 "configmap": {
566 theMap: testConfigMap,
567 rsExp: resultExpected{
568 out: `
569 apiVersion: v1
570 kind: ConfigMap
571 metadata:
572 name: winnie
573 `,
574 err: nil,
575 },
576 },
577 "list": {
578 theMap: map[string]interface{}{
579 "apiVersion": "v1",
580 "kind": "List",
581 "items": []interface{}{
582 testConfigMap,
583 testConfigMap,
584 },
585 },
586 rsExp: resultExpected{
587 out: `
588 apiVersion: v1
589 items:
590 - apiVersion: v1
591 kind: ConfigMap
592 metadata:
593 name: winnie
594 - apiVersion: v1
595 kind: ConfigMap
596 metadata:
597 name: winnie
598 kind: List
599 `,
600 err: nil,
601 },
602 },
603 "configmaplist": {
604 theMap: map[string]interface{}{
605 "apiVersion": "v1",
606 "kind": "ConfigMapList",
607 "items": []interface{}{
608 testConfigMap,
609 testConfigMap,
610 },
611 },
612 rsExp: resultExpected{
613 out: `
614 apiVersion: v1
615 items:
616 - apiVersion: v1
617 kind: ConfigMap
618 metadata:
619 name: winnie
620 - apiVersion: v1
621 kind: ConfigMap
622 metadata:
623 name: winnie
624 kind: ConfigMapList
625 `,
626 err: nil,
627 },
628 },
629 }
630
631 for n := range testCases {
632 tc := testCases[n]
633 t.Run(n, func(t *testing.T) {
634 rn, err := FromMap(tc.theMap)
635 if tc.rsExp.err == nil {
636 if !assert.NoError(t, err) {
637 t.FailNow()
638 }
639 if !assert.Equal(t,
640 strings.TrimSpace(tc.rsExp.out),
641 strings.TrimSpace(rn.MustString())) {
642 t.FailNow()
643 }
644 } else {
645 if !assert.Error(t, err) {
646 t.FailNow()
647 }
648 if !assert.Equal(t, tc.rsExp.err, err) {
649 t.FailNow()
650 }
651 }
652 })
653 }
654 }
655
656
657 func TestRNode_GetMeta_UTF16(t *testing.T) {
658 sr, err := Parse(`apiVersion: rbac.istio.io/v1alpha1
659 kind: ServiceRole
660 metadata:
661 name: wildcard
662 namespace: default
663 # If set to [“*”], it refers to all services in the namespace
664 annotations:
665 foo: bar
666 spec:
667 rules:
668 # There is one service in default namespace, should not result in a validation error
669 # If set to [“*”], it refers to all services in the namespace
670 - services: ["*"]
671 methods: ["GET", "HEAD"]
672 `)
673 if !assert.NoError(t, err) {
674 t.FailNow()
675 }
676 actual, err := sr.GetMeta()
677 if !assert.NoError(t, err) {
678 t.FailNow()
679 }
680
681 expected := ResourceMeta{
682 TypeMeta: TypeMeta{
683 APIVersion: "rbac.istio.io/v1alpha1",
684 Kind: "ServiceRole",
685 },
686 ObjectMeta: ObjectMeta{
687 NameMeta: NameMeta{
688 Name: "wildcard",
689 Namespace: "default",
690 },
691 Annotations: map[string]string{"foo": "bar"},
692 },
693 }
694 if !assert.Equal(t, expected, actual) {
695 t.FailNow()
696 }
697 }
698
699 func TestDeAnchor(t *testing.T) {
700 rn, err := Parse(`
701 apiVersion: v1
702 kind: ConfigMap
703 metadata:
704 name: wildcard
705 data:
706 color: &color-used blue
707 feeling: *color-used
708 `)
709 require.NoError(t, err)
710 require.NoError(t, rn.DeAnchor())
711 actual, err := rn.String()
712 require.NoError(t, err)
713 assert.Equal(t, strings.TrimSpace(`
714 apiVersion: v1
715 kind: ConfigMap
716 metadata:
717 name: wildcard
718 data:
719 color: blue
720 feeling: blue
721 `), strings.TrimSpace(actual))
722 }
723
724 func TestDeAnchorMerge(t *testing.T) {
725 testCases := []struct {
726 description string
727 input string
728 expected string
729 expectedErr error
730 }{
731
732
733
734 {
735 description: "simplest merge tag",
736 input: `
737 apiVersion: v1
738 kind: MergeTagTest
739 metadata:
740 name: test
741 data:
742 color: &color-used
743 foo: bar
744 primaryColor:
745 <<: *color-used
746 `,
747 expected: `
748 apiVersion: v1
749 kind: MergeTagTest
750 metadata:
751 name: test
752 data:
753 color:
754 foo: bar
755 primaryColor:
756 foo: bar
757 `,
758 },
759
760
761
762 {
763 description: "keep duplicated keys",
764 input: `
765 apiVersion: v1
766 kind: MergeTagTest
767 metadata:
768 name: test
769 data:
770 color: "#FF0000"
771 color: "#FF00FF"
772 `,
773 expected: `
774 apiVersion: v1
775 kind: MergeTagTest
776 metadata:
777 name: test
778 data:
779 color: "#FF0000"
780 color: "#FF00FF"
781 `,
782 },
783
784
785
786 {
787 description: "keep json",
788 input: `{"apiVersion": "v1", "kind": "MergeTagTest", "spec": {"color": {"rgb": "#FF0000"}}}`,
789 expected: `{"apiVersion": "v1", "kind": "MergeTagTest", "spec": {"color": {"rgb": "#FF0000"}}}`,
790 },
791
792
793
794 {
795 description: "keep comments",
796 input: `
797 apiVersion: v1
798 kind: MergeTagTest
799 metadata:
800 name: test
801 data:
802 color: &color-used
803 foo: bar
804 primaryColor:
805 # use same color because is pretty
806 rgb: "#FF0000"
807 `,
808 expected: `
809 apiVersion: v1
810 kind: MergeTagTest
811 metadata:
812 name: test
813 data:
814 color:
815 foo: bar
816 primaryColor:
817 # use same color because is pretty
818 rgb: "#FF0000"
819 `,
820 },
821
822
823
824 {
825 description: "works with explicit merge tag",
826 input: `
827 apiVersion: v1
828 kind: MergeTagTest
829 metadata:
830 name: test
831 data:
832 color: &color-used
833 foo: bar
834 primaryColor:
835 !!merge <<: *color-used
836 `,
837 expected: `
838 apiVersion: v1
839 kind: MergeTagTest
840 metadata:
841 name: test
842 data:
843 color:
844 foo: bar
845 primaryColor:
846 foo: bar
847 `,
848 },
849
850
851
852 {
853 description: "works with explicit long merge tag",
854 input: `
855 apiVersion: v1
856 kind: MergeTagTest
857 metadata:
858 name: test
859 data:
860 color: &color-used
861 foo: bar
862 primaryColor:
863 !<tag:yaml.org,2002:merge> "<<" : *color-used
864 `,
865 expected: `
866 apiVersion: v1
867 kind: MergeTagTest
868 metadata:
869 name: test
870 data:
871 color:
872 foo: bar
873 primaryColor:
874 foo: bar
875 `,
876 },
877
878
879
880 {
881 description: "merging properties",
882 input: `
883 apiVersion: v1
884 kind: MergeTagTest
885 metadata:
886 name: test
887 data:
888 color: &color-used
889 rgb: "#FF0000"
890 primaryColor:
891 <<: *color-used
892 pretty: true
893 `,
894 expected: `
895 apiVersion: v1
896 kind: MergeTagTest
897 metadata:
898 name: test
899 data:
900 color:
901 rgb: "#FF0000"
902 primaryColor:
903 pretty: true
904 rgb: "#FF0000"
905 `,
906 },
907
908
909
910 {
911 description: "overriding value",
912 input: `
913 apiVersion: v1
914 kind: MergeTagTest
915 metadata:
916 name: test
917 data:
918 color: &color-used
919 rgb: "#FF0000"
920 pretty: false
921 primaryColor:
922 <<: *color-used
923 pretty: true
924 `,
925 expected: `
926 apiVersion: v1
927 kind: MergeTagTest
928 metadata:
929 name: test
930 data:
931 color:
932 rgb: "#FF0000"
933 pretty: false
934 primaryColor:
935 pretty: true
936 rgb: "#FF0000"
937 `,
938 },
939
940
941
942 {
943 description: "returns error when defining multiple merge keys",
944 input: `
945 apiVersion: v1
946 kind: MergeTagTest
947 metadata:
948 name: test
949 data:
950 color: &color
951 rgb: "#FF0000"
952 pretty: false
953 primaryColor: &primary
954 rgb: "#0000FF"
955 alpha: 50
956 secondaryColor:
957 <<: *color
958 <<: *primary
959 secondary: true
960 `,
961 expectedErr: fmt.Errorf("duplicate merge key"),
962 },
963
964
965
966 {
967 description: "merging multiple anchors with sequence node",
968 input: `
969 apiVersion: v1
970 kind: MergeTagTest
971 metadata:
972 name: test
973 data:
974 color: &color
975 rgb: "#FF0000"
976 pretty: false
977 primaryColor: &primary
978 rgb: "#0000FF"
979 alpha: 50
980 secondaryColor:
981 <<: [ *color, *primary ]
982 secondary: true
983 `,
984 expected: `
985 apiVersion: v1
986 kind: MergeTagTest
987 metadata:
988 name: test
989 data:
990 color:
991 rgb: "#FF0000"
992 pretty: false
993 primaryColor:
994 rgb: "#0000FF"
995 alpha: 50
996 secondaryColor:
997 secondary: true
998 rgb: "#FF0000"
999 alpha: 50
1000 pretty: false
1001 `,
1002 },
1003
1004
1005
1006 {
1007 description: "merging inline map",
1008 input: `
1009 apiVersion: v1
1010 kind: MergeTagTest
1011 metadata:
1012 name: test
1013 data:
1014 color: &color
1015 rgb: "#FF0000"
1016 pretty: false
1017 primaryColor:
1018 <<: {"pretty": true}
1019 rgb: "#0000FF"
1020 alpha: 50
1021 `,
1022 expected: `
1023 apiVersion: v1
1024 kind: MergeTagTest
1025 metadata:
1026 name: test
1027 data:
1028 color:
1029 rgb: "#FF0000"
1030 pretty: false
1031 primaryColor:
1032 rgb: "#0000FF"
1033 alpha: 50
1034 "pretty": true
1035 `,
1036 },
1037
1038
1039
1040 {
1041 description: "merging inline sequence map",
1042 input: `
1043 apiVersion: v1
1044 kind: MergeTagTest
1045 metadata:
1046 name: test
1047 data:
1048 color: &color
1049 rgb: "#FF0000"
1050 pretty: false
1051 primaryColor:
1052 <<: [ *color, {"name": "ugly blue"}]
1053 rgb: "#0000FF"
1054 alpha: 50
1055 `,
1056 expected: `
1057 apiVersion: v1
1058 kind: MergeTagTest
1059 metadata:
1060 name: test
1061 data:
1062 color:
1063 rgb: "#FF0000"
1064 pretty: false
1065 primaryColor:
1066 rgb: "#0000FF"
1067 alpha: 50
1068 "name": "ugly blue"
1069 pretty: false
1070 `,
1071 },
1072
1073
1074
1075 {
1076 description: "error on nested lists on merges",
1077 input: `
1078 apiVersion: v1
1079 kind: MergeTagTest
1080 metadata:
1081 name: test
1082 data:
1083 color: &color
1084 rgb: "#FF0000"
1085 pretty: false
1086 primaryColor:
1087 <<: [ *color, [{"name": "ugly blue"}]]
1088 rgb: "#0000FF"
1089 alpha: 50
1090 `,
1091 expectedErr: fmt.Errorf("invalid map merge: received a nested sequence"),
1092 },
1093
1094
1095
1096 {
1097 description: "error on non-map references on merges",
1098 input: `
1099 apiVersion: v1
1100 kind: MergeTagTest
1101 metadata:
1102 name: test
1103 data:
1104 color: &color
1105 - rgb: "#FF0000"
1106 pretty: false
1107 primaryColor:
1108 <<: [ *color, [{"name": "ugly blue"}]]
1109 rgb: "#0000FF"
1110 alpha: 50
1111 `,
1112 expectedErr: fmt.Errorf("invalid map merge: received alias for a non-map value"),
1113 },
1114
1115
1116
1117 {
1118 description: "merging on a list",
1119 input: `
1120 apiVersion: v1
1121 kind: MergeTagTestList
1122 items:
1123 - apiVersion: v1
1124 kind: MergeTagTest
1125 metadata:
1126 name: test
1127 spec: &merge-spec
1128 something: true
1129 - apiVersion: v1
1130 kind: MergeTagTest
1131 metadata:
1132 name: test
1133 spec:
1134 <<: *merge-spec
1135 `,
1136 expected: `
1137 apiVersion: v1
1138 kind: MergeTagTestList
1139 items:
1140 - apiVersion: v1
1141 kind: MergeTagTest
1142 metadata:
1143 name: test
1144 spec:
1145 something: true
1146 - apiVersion: v1
1147 kind: MergeTagTest
1148 metadata:
1149 name: test
1150 spec:
1151 something: true
1152 `,
1153 },
1154 }
1155
1156 for _, tc := range testCases {
1157 t.Run(tc.description, func(t *testing.T) {
1158 rn, err := Parse(tc.input)
1159
1160 require.NoError(t, err)
1161 err = rn.DeAnchor()
1162 if tc.expectedErr == nil {
1163 require.NoError(t, err)
1164 actual, err := rn.String()
1165 require.NoError(t, err)
1166 assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(actual))
1167 } else {
1168 assert.NotNil(t, err)
1169 assert.Equal(t, tc.expectedErr.Error(), err.Error())
1170 }
1171 })
1172 }
1173 }
1174 func TestRNode_UnmarshalJSON(t *testing.T) {
1175 testCases := []struct {
1176 testName string
1177 input string
1178 output string
1179 }{
1180 {
1181 testName: "simple document",
1182 input: `{"hello":"world"}`,
1183 output: `hello: world`,
1184 },
1185 {
1186 testName: "nested structure",
1187 input: `
1188 {
1189 "apiVersion": "apps/v1",
1190 "kind": "Deployment",
1191 "metadata": {
1192 "name": "my-deployment",
1193 "namespace": "default"
1194 }
1195 }
1196 `,
1197 output: `
1198 apiVersion: apps/v1
1199 kind: Deployment
1200 metadata:
1201 name: my-deployment
1202 namespace: default
1203 `,
1204 },
1205 }
1206
1207 for i := range testCases {
1208 tc := testCases[i]
1209 t.Run(tc.testName, func(t *testing.T) {
1210 instance := &RNode{}
1211 err := instance.UnmarshalJSON([]byte(tc.input))
1212 if !assert.NoError(t, err) {
1213 t.FailNow()
1214 }
1215
1216 actual, err := instance.String()
1217 if !assert.NoError(t, err) {
1218 t.FailNow()
1219 }
1220
1221 if !assert.Equal(t,
1222 strings.TrimSpace(tc.output), strings.TrimSpace(actual)) {
1223 t.FailNow()
1224 }
1225 })
1226 }
1227 }
1228
1229 func TestRNode_MarshalJSON(t *testing.T) {
1230 tests := []struct {
1231 name string
1232 ydoc string
1233 want string
1234 }{
1235 {
1236 name: "object",
1237 ydoc: `
1238 hello: world
1239 `,
1240 want: `{"hello":"world"}`,
1241 },
1242 {
1243 name: "array",
1244 ydoc: `
1245 - name: s1
1246 - name: s2
1247 `,
1248 want: `[{"name":"s1"},{"name":"s2"}]`,
1249 },
1250 }
1251 for idx := range tests {
1252 tt := tests[idx]
1253 t.Run(tt.name, func(t *testing.T) {
1254 instance, err := Parse(tt.ydoc)
1255 if !assert.NoError(t, err) {
1256 t.FailNow()
1257 }
1258
1259 actual, err := instance.MarshalJSON()
1260 if !assert.NoError(t, err) {
1261 t.FailNow()
1262 }
1263
1264 if !assert.Equal(t,
1265 strings.TrimSpace(tt.want), strings.TrimSpace(string(actual))) {
1266 t.FailNow()
1267 }
1268 })
1269 }
1270 }
1271
1272 func TestConvertJSONToYamlNode(t *testing.T) {
1273 inputJSON := `{"type": "string", "maxLength": 15, "enum": ["allowedValue1", "allowedValue2"]}`
1274 expected := `enum:
1275 - allowedValue1
1276 - allowedValue2
1277 maxLength: 15
1278 type: string
1279 `
1280
1281 node, err := ConvertJSONToYamlNode(inputJSON)
1282 if !assert.NoError(t, err) {
1283 t.FailNow()
1284 }
1285 actual, err := node.String()
1286 if !assert.NoError(t, err) {
1287 t.FailNow()
1288 }
1289 assert.Equal(t, expected, actual)
1290 }
1291
1292 func TestIsMissingOrNull(t *testing.T) {
1293 if !IsMissingOrNull(nil) {
1294 t.Fatalf("input: nil")
1295 }
1296
1297 if !IsMissingOrNull(NewRNode(nil)) {
1298 t.Fatalf("input: nil value")
1299 }
1300
1301 if IsMissingOrNull(NewScalarRNode("foo")) {
1302 t.Fatalf("input: valid node")
1303 }
1304
1305 if !IsMissingOrNull(MakeNullNode()) {
1306 t.Fatalf("input: with NullNodeTag")
1307 }
1308
1309
1310 if IsMissingOrNull(NewListRNode()) {
1311 t.Fatalf("input: empty array")
1312 }
1313
1314
1315 node := NewListRNode("foo")
1316 if IsMissingOrNull(node) {
1317 t.Fatalf("input: array with 1 item")
1318 }
1319
1320
1321 node.value.Content = nil
1322 if IsMissingOrNull(node) {
1323 t.Fatalf("input: empty array")
1324 }
1325 }
1326
1327 func TestCopy(t *testing.T) {
1328 rn := RNode{
1329 fieldPath: []string{"fp1", "fp2"},
1330 value: &Node{
1331 Kind: 200,
1332 },
1333 Match: []string{"m1", "m2"},
1334 }
1335 rnC := rn.Copy()
1336 if !reflect.DeepEqual(&rn, rnC) {
1337 t.Fatalf("copy %v is not deep equal to %v", rnC, rn)
1338 }
1339 tmp := rn.value.Kind
1340 rn.value.Kind = 666
1341 if reflect.DeepEqual(rn, rnC) {
1342 t.Fatalf("changing component should break equality")
1343 }
1344 rn.value.Kind = tmp
1345 if !reflect.DeepEqual(&rn, rnC) {
1346 t.Fatalf("should be back to normal")
1347 }
1348 rn.fieldPath[0] = "Different"
1349 if reflect.DeepEqual(rn, rnC) {
1350 t.Fatalf("changing fieldpath should break equality")
1351 }
1352 }
1353
1354 func TestFieldRNodes(t *testing.T) {
1355 testCases := []struct {
1356 testName string
1357 input string
1358 output []string
1359 err string
1360 }{
1361 {
1362 testName: "simple document",
1363 input: `apiVersion: example.com/v1beta1
1364 kind: Example1
1365 spec:
1366 list:
1367 - "a"
1368 - "b"
1369 - "c"`,
1370 output: []string{"apiVersion", "kind", "spec", "list"},
1371 },
1372 {
1373 testName: "non mapping node error",
1374 input: `apiVersion`,
1375 err: `wrong node kind: expected MappingNode but got ScalarNode: node contents:
1376 apiVersion
1377 `,
1378 },
1379 }
1380
1381 for i := range testCases {
1382 tc := testCases[i]
1383 t.Run(tc.testName, func(t *testing.T) {
1384 rNode, err := Parse(tc.input)
1385 if !assert.NoError(t, err) {
1386 t.FailNow()
1387 }
1388
1389 fieldRNodes, err := rNode.FieldRNodes()
1390 if tc.err == "" {
1391 if !assert.NoError(t, err) {
1392 t.FailNow()
1393 }
1394 } else {
1395 if !assert.Equal(t, tc.err, err.Error()) {
1396 t.FailNow()
1397 }
1398 }
1399 for i := range fieldRNodes {
1400 actual, err := fieldRNodes[i].String()
1401 if !assert.NoError(t, err) {
1402 t.FailNow()
1403 }
1404 if !assert.Equal(t, tc.output[i], strings.TrimSpace(actual)) {
1405 t.FailNow()
1406 }
1407 }
1408 })
1409 }
1410 }
1411
1412 func TestIsEmptyMap(t *testing.T) {
1413 node := NewMapRNode(nil)
1414
1415 if !IsEmptyMap(node) {
1416 t.Fatalf("input: empty map")
1417 }
1418
1419 node = NewMapRNode(&map[string]string{
1420 "foo": "bar",
1421 })
1422 if IsEmptyMap(node) {
1423 t.Fatalf("input: map with 1 item")
1424 }
1425
1426 node.value.Content = nil
1427 if !IsEmptyMap(node) {
1428 t.Fatalf("input: empty map")
1429 }
1430 }
1431
1432 func TestIsNil(t *testing.T) {
1433 var rn *RNode
1434
1435 if !rn.IsNil() {
1436 t.Fatalf("uninitialized RNode should be nil")
1437 }
1438
1439 if !NewRNode(nil).IsNil() {
1440 t.Fatalf("missing value YNode should be nil")
1441 }
1442
1443 if MakeNullNode().IsNil() {
1444 t.Fatalf("value tagged null is not nil")
1445 }
1446
1447 if NewMapRNode(nil).IsNil() {
1448 t.Fatalf("empty map not nil")
1449 }
1450
1451 if NewListRNode().IsNil() {
1452 t.Fatalf("empty list not nil")
1453 }
1454 }
1455
1456 func TestIsTaggedNull(t *testing.T) {
1457 var rn *RNode
1458
1459 if rn.IsTaggedNull() {
1460 t.Fatalf("nil RNode cannot be tagged")
1461 }
1462
1463 if NewRNode(nil).IsTaggedNull() {
1464 t.Fatalf("bare RNode should not be tagged")
1465 }
1466
1467 if !MakeNullNode().IsTaggedNull() {
1468 t.Fatalf("a null node is tagged null by definition")
1469 }
1470
1471 if NewMapRNode(nil).IsTaggedNull() {
1472 t.Fatalf("empty map should not be tagged null")
1473 }
1474
1475 if NewListRNode().IsTaggedNull() {
1476 t.Fatalf("empty list should not be tagged null")
1477 }
1478 }
1479
1480 func TestRNodeIsNilOrEmpty(t *testing.T) {
1481 var rn *RNode
1482
1483 if !rn.IsNilOrEmpty() {
1484 t.Fatalf("uninitialized RNode should be empty")
1485 }
1486
1487 if !NewRNode(nil).IsNilOrEmpty() {
1488 t.Fatalf("missing value YNode should be empty")
1489 }
1490
1491 if !MakeNullNode().IsNilOrEmpty() {
1492 t.Fatalf("value tagged null should be empty")
1493 }
1494
1495 if !NewMapRNode(nil).IsNilOrEmpty() {
1496 t.Fatalf("empty map should be empty")
1497 }
1498
1499 if NewMapRNode(&map[string]string{"foo": "bar"}).IsNilOrEmpty() {
1500 t.Fatalf("non-empty map should not be empty")
1501 }
1502
1503 if !NewListRNode().IsNilOrEmpty() {
1504 t.Fatalf("empty list should be empty")
1505 }
1506
1507 if NewListRNode("foo").IsNilOrEmpty() {
1508 t.Fatalf("non-empty list should not be empty")
1509 }
1510
1511 if !NewRNode(&Node{}).IsNilOrEmpty() {
1512 t.Fatalf("zero YNode should be empty")
1513 }
1514 }
1515
1516 const deploymentJSON = `
1517 {
1518 "apiVersion": "apps/v1",
1519 "kind": "Deployment",
1520 "metadata": {
1521 "name": "homer",
1522 "namespace": "simpsons",
1523 "labels": {
1524 "fruit": "apple",
1525 "veggie": "carrot"
1526 },
1527 "annotations": {
1528 "area": "51",
1529 "greeting": "Take me to your leader."
1530 }
1531 }
1532 }
1533 `
1534
1535 func TestRNodeSetNamespace(t *testing.T) {
1536 n := NewRNode(nil)
1537 assert.Equal(t, "", n.GetNamespace())
1538 require.NoError(t, n.SetNamespace(""))
1539 assert.Equal(t, "", n.GetNamespace())
1540 if err := n.UnmarshalJSON([]byte(deploymentJSON)); err != nil {
1541 t.Fatalf("unexpected unmarshaljson err: %v", err)
1542 }
1543 require.NoError(t, n.SetNamespace("flanders"))
1544 assert.Equal(t, "flanders", n.GetNamespace())
1545 }
1546
1547 func TestRNodeSetLabels(t *testing.T) {
1548 n := NewRNode(nil)
1549 assert.Equal(t, 0, len(n.GetLabels()))
1550 require.NoError(t, n.SetLabels(map[string]string{}))
1551 assert.Equal(t, 0, len(n.GetLabels()))
1552
1553 n = NewRNode(nil)
1554 if err := n.UnmarshalJSON([]byte(deploymentJSON)); err != nil {
1555 t.Fatalf("unexpected unmarshaljson err: %v", err)
1556 }
1557 labels := n.GetLabels()
1558 assert.Equal(t, 2, len(labels))
1559 assert.Equal(t, "apple", labels["fruit"])
1560 assert.Equal(t, "carrot", labels["veggie"])
1561 require.NoError(t, n.SetLabels(map[string]string{
1562 "label1": "foo",
1563 "label2": "bar",
1564 }))
1565 labels = n.GetLabels()
1566 assert.Equal(t, 2, len(labels))
1567 assert.Equal(t, "foo", labels["label1"])
1568 assert.Equal(t, "bar", labels["label2"])
1569 require.NoError(t, n.SetLabels(map[string]string{}))
1570 assert.Equal(t, 0, len(n.GetLabels()))
1571 }
1572
1573 func TestRNodeGetAnnotations(t *testing.T) {
1574 n := NewRNode(nil)
1575 assert.Equal(t, 0, len(n.GetAnnotations()))
1576 require.NoError(t, n.SetAnnotations(map[string]string{}))
1577 assert.Equal(t, 0, len(n.GetAnnotations()))
1578
1579 n = NewRNode(nil)
1580 if err := n.UnmarshalJSON([]byte(deploymentJSON)); err != nil {
1581 t.Fatalf("unexpected unmarshaljson err: %v", err)
1582 }
1583 annotations := n.GetAnnotations()
1584 assert.Equal(t, 2, len(annotations))
1585 assert.Equal(t, "51", annotations["area"])
1586 assert.Equal(t, "Take me to your leader.", annotations["greeting"])
1587 require.NoError(t, n.SetAnnotations(map[string]string{
1588 "annotation1": "foo",
1589 "annotation2": "bar",
1590 }))
1591 annotations = n.GetAnnotations()
1592 assert.Equal(t, 2, len(annotations))
1593 assert.Equal(t, "foo", annotations["annotation1"])
1594 assert.Equal(t, "bar", annotations["annotation2"])
1595 require.NoError(t, n.SetAnnotations(map[string]string{}))
1596 assert.Equal(t, 0, len(n.GetAnnotations()))
1597 }
1598
1599 func TestRNodeMatchesAnnotationSelector(t *testing.T) {
1600 rn := NewRNode(nil)
1601 require.NoError(t, rn.UnmarshalJSON([]byte(deploymentJSON)))
1602 testcases := map[string]struct {
1603 selector string
1604 matches bool
1605 errMsg string
1606 }{
1607 "select_01": {
1608 selector: ".*",
1609 matches: false,
1610 errMsg: "name part must consist of alphanumeric character",
1611 },
1612 "select_02": {
1613 selector: "area=51",
1614 matches: true,
1615 },
1616 "select_03": {
1617 selector: "area=florida",
1618 matches: false,
1619 },
1620 "select_04": {
1621 selector: "area in (disneyland, 51, iowa)",
1622 matches: true,
1623 },
1624 "select_05": {
1625 selector: "area in (disneyland, iowa)",
1626 matches: false,
1627 },
1628 "select_06": {
1629 selector: "area notin (disneyland, 51, iowa)",
1630 matches: false,
1631 },
1632 }
1633 for n, tc := range testcases {
1634 gotMatch, err := rn.MatchesAnnotationSelector(tc.selector)
1635 if tc.errMsg == "" {
1636 require.NoError(t, err)
1637 assert.Equalf(
1638 t, tc.matches, gotMatch, "test=%s selector=%v", n, tc.selector)
1639 } else {
1640 assert.Truef(
1641 t, strings.Contains(err.Error(), tc.errMsg),
1642 "test=%s selector=%v", n, tc.selector)
1643 }
1644 }
1645 }
1646
1647 func TestRNodeMatchesLabelSelector(t *testing.T) {
1648 rn := NewRNode(nil)
1649 require.NoError(t, rn.UnmarshalJSON([]byte(deploymentJSON)))
1650 testcases := map[string]struct {
1651 selector string
1652 matches bool
1653 errMsg string
1654 }{
1655 "select_01": {
1656 selector: ".*",
1657 matches: false,
1658 errMsg: "name part must consist of alphanumeric character",
1659 },
1660 "select_02": {
1661 selector: "fruit=apple",
1662 matches: true,
1663 },
1664 "select_03": {
1665 selector: "fruit=banana",
1666 matches: false,
1667 },
1668 "select_04": {
1669 selector: "fruit in (orange, banana, apple)",
1670 matches: true,
1671 },
1672 "select_05": {
1673 selector: "fruit in (orange, banana)",
1674 matches: false,
1675 },
1676 "select_06": {
1677 selector: "fruit notin (orange, banana, apple)",
1678 matches: false,
1679 },
1680 }
1681 for n, tc := range testcases {
1682 gotMatch, err := rn.MatchesLabelSelector(tc.selector)
1683 if tc.errMsg == "" {
1684 require.NoError(t, err)
1685 assert.Equalf(
1686 t, tc.matches, gotMatch, "test=%s selector=%v", n, tc.selector)
1687 } else {
1688 assert.Truef(
1689 t, strings.Contains(err.Error(), tc.errMsg),
1690 "test=%s selector=%v", n, tc.selector)
1691 }
1692 }
1693 }
1694
1695 const (
1696 deploymentLittleJson = `{"apiVersion":"apps/v1","kind":"Deployment",` +
1697 `"metadata":{"name":"homer","namespace":"simpsons"}}`
1698
1699 deploymentBiggerJson = `
1700 {
1701 "apiVersion": "apps/v1",
1702 "kind": "Deployment",
1703 "metadata": {
1704 "name": "homer",
1705 "namespace": "simpsons",
1706 "labels": {
1707 "fruit": "apple",
1708 "veggie": "carrot"
1709 },
1710 "annotations": {
1711 "area": "51",
1712 "greeting": "Take me to your leader."
1713 }
1714 },
1715 "spec": {
1716 "template": {
1717 "spec": {
1718 "containers": [
1719 {
1720 "env": [
1721 {
1722 "name": "CM_FOO",
1723 "valueFrom": {
1724 "configMapKeyRef": {
1725 "key": "somekey",
1726 "name": "myCm"
1727 }
1728 }
1729 },
1730 {
1731 "name": "SECRET_FOO",
1732 "valueFrom": {
1733 "secretKeyRef": {
1734 "key": "someKey",
1735 "name": "mySecret"
1736 }
1737 }
1738 }
1739 ],
1740 "image": "nginx:1.7.9",
1741 "name": "nginx"
1742 }
1743 ]
1744 }
1745 }
1746 }
1747 }
1748 `
1749 bigMapYaml = `Kind: Service
1750 complextree:
1751 - field1:
1752 - boolfield: true
1753 floatsubfield: 1.01
1754 intsubfield: 1010
1755 stringsubfield: idx1010
1756 - boolfield: false
1757 floatsubfield: 1.011
1758 intsubfield: 1011
1759 stringsubfield: idx1011
1760 field2:
1761 - boolfield: true
1762 floatsubfield: 1.02
1763 intsubfield: 1020
1764 stringsubfield: idx1020
1765 - boolfield: false
1766 floatsubfield: 1.021
1767 intsubfield: 1021
1768 stringsubfield: idx1021
1769 - field1:
1770 - boolfield: true
1771 floatsubfield: 1.11
1772 intsubfield: 1110
1773 stringsubfield: idx1110
1774 - boolfield: false
1775 floatsubfield: 1.111
1776 intsubfield: 1111
1777 stringsubfield: idx1111
1778 field2:
1779 - boolfield: true
1780 floatsubfield: 1.112
1781 intsubfield: 1120
1782 stringsubfield: idx1120
1783 - boolfield: false
1784 floatsubfield: 1.1121
1785 intsubfield: 1121
1786 stringsubfield: idx1121
1787 metadata:
1788 labels:
1789 app: application-name
1790 name: service-name
1791 spec:
1792 ports:
1793 port: 80
1794 that:
1795 - idx0
1796 - idx1
1797 - idx2
1798 - idx3
1799 these:
1800 - field1:
1801 - idx010
1802 - idx011
1803 field2:
1804 - idx020
1805 - idx021
1806 - field1:
1807 - idx110
1808 - idx111
1809 field2:
1810 - idx120
1811 - idx121
1812 - field1:
1813 - idx210
1814 - idx211
1815 field2:
1816 - idx220
1817 - idx221
1818 this:
1819 is:
1820 aBool: true
1821 aFloat: 1.001
1822 aNilValue: null
1823 aNumber: 1000
1824 anEmptyMap: {}
1825 anEmptySlice: []
1826 those:
1827 - field1: idx0foo
1828 field2: idx0bar
1829 - field1: idx1foo
1830 field2: idx1bar
1831 - field1: idx2foo
1832 field2: idx2bar
1833 `
1834 )
1835
1836 func TestGetFieldValueReturnsMap(t *testing.T) {
1837 rn := NewRNode(nil)
1838 if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
1839 t.Fatalf("unexpected unmarshaljson err: %v", err)
1840 }
1841 expected := map[string]interface{}{
1842 "fruit": "apple",
1843 "veggie": "carrot",
1844 }
1845 actual, err := rn.GetFieldValue("metadata.labels")
1846 if err != nil {
1847 t.Fatalf("error getting field value: %v", err)
1848 }
1849 if diff := cmp.Diff(expected, actual); diff != "" {
1850 t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
1851 }
1852 }
1853
1854 func TestGetFieldValueReturnsStuff(t *testing.T) {
1855 wn := NewRNode(nil)
1856 if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
1857 t.Fatalf("unexpected unmarshaljson err: %v", err)
1858 }
1859 expected := []interface{}{
1860 map[string]interface{}{
1861 "env": []interface{}{
1862 map[string]interface{}{
1863 "name": "CM_FOO",
1864 "valueFrom": map[string]interface{}{
1865 "configMapKeyRef": map[string]interface{}{
1866 "key": "somekey",
1867 "name": "myCm",
1868 },
1869 },
1870 },
1871 map[string]interface{}{
1872 "name": "SECRET_FOO",
1873 "valueFrom": map[string]interface{}{
1874 "secretKeyRef": map[string]interface{}{
1875 "key": "someKey",
1876 "name": "mySecret",
1877 },
1878 },
1879 },
1880 },
1881 "image": string("nginx:1.7.9"),
1882 "name": string("nginx"),
1883 },
1884 }
1885 actual, err := wn.GetFieldValue("spec.template.spec.containers")
1886 if err != nil {
1887 t.Fatalf("error getting field value: %v", err)
1888 }
1889 if diff := cmp.Diff(expected, actual); diff != "" {
1890 t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
1891 }
1892
1893 _, err = wn.GetFieldValue("spec.template.spec.containers.env")
1894 if err == nil {
1895 t.Fatalf("expected err %v", err)
1896 }
1897 }
1898
1899 func makeBigMap() map[string]interface{} {
1900 return map[string]interface{}{
1901 "Kind": "Service",
1902 "metadata": map[string]interface{}{
1903 "labels": map[string]interface{}{
1904 "app": "application-name",
1905 },
1906 "name": "service-name",
1907 },
1908 "spec": map[string]interface{}{
1909 "ports": map[string]interface{}{
1910 "port": int64(80),
1911 },
1912 },
1913 "this": map[string]interface{}{
1914 "is": map[string]interface{}{
1915 "aNumber": int64(1000),
1916 "aFloat": float64(1.001),
1917 "aNilValue": nil,
1918 "aBool": true,
1919 "anEmptyMap": map[string]interface{}{},
1920 "anEmptySlice": []interface{}{},
1921
1927 },
1928 },
1929 "that": []interface{}{
1930 "idx0",
1931 "idx1",
1932 "idx2",
1933 "idx3",
1934 },
1935 "those": []interface{}{
1936 map[string]interface{}{
1937 "field1": "idx0foo",
1938 "field2": "idx0bar",
1939 },
1940 map[string]interface{}{
1941 "field1": "idx1foo",
1942 "field2": "idx1bar",
1943 },
1944 map[string]interface{}{
1945 "field1": "idx2foo",
1946 "field2": "idx2bar",
1947 },
1948 },
1949 "these": []interface{}{
1950 map[string]interface{}{
1951 "field1": []interface{}{"idx010", "idx011"},
1952 "field2": []interface{}{"idx020", "idx021"},
1953 },
1954 map[string]interface{}{
1955 "field1": []interface{}{"idx110", "idx111"},
1956 "field2": []interface{}{"idx120", "idx121"},
1957 },
1958 map[string]interface{}{
1959 "field1": []interface{}{"idx210", "idx211"},
1960 "field2": []interface{}{"idx220", "idx221"},
1961 },
1962 },
1963 "complextree": []interface{}{
1964 map[string]interface{}{
1965 "field1": []interface{}{
1966 map[string]interface{}{
1967 "stringsubfield": "idx1010",
1968 "intsubfield": int64(1010),
1969 "floatsubfield": float64(1.010),
1970 "boolfield": true,
1971 },
1972 map[string]interface{}{
1973 "stringsubfield": "idx1011",
1974 "intsubfield": int64(1011),
1975 "floatsubfield": float64(1.011),
1976 "boolfield": false,
1977 },
1978 },
1979 "field2": []interface{}{
1980 map[string]interface{}{
1981 "stringsubfield": "idx1020",
1982 "intsubfield": int64(1020),
1983 "floatsubfield": float64(1.020),
1984 "boolfield": true,
1985 },
1986 map[string]interface{}{
1987 "stringsubfield": "idx1021",
1988 "intsubfield": int64(1021),
1989 "floatsubfield": float64(1.021),
1990 "boolfield": false,
1991 },
1992 },
1993 },
1994 map[string]interface{}{
1995 "field1": []interface{}{
1996 map[string]interface{}{
1997 "stringsubfield": "idx1110",
1998 "intsubfield": int64(1110),
1999 "floatsubfield": float64(1.110),
2000 "boolfield": true,
2001 },
2002 map[string]interface{}{
2003 "stringsubfield": "idx1111",
2004 "intsubfield": int64(1111),
2005 "floatsubfield": float64(1.111),
2006 "boolfield": false,
2007 },
2008 },
2009 "field2": []interface{}{
2010 map[string]interface{}{
2011 "stringsubfield": "idx1120",
2012 "intsubfield": int64(1120),
2013 "floatsubfield": float64(1.1120),
2014 "boolfield": true,
2015 },
2016 map[string]interface{}{
2017 "stringsubfield": "idx1121",
2018 "intsubfield": int64(1121),
2019 "floatsubfield": float64(1.1121),
2020 "boolfield": false,
2021 },
2022 },
2023 },
2024 },
2025 }
2026 }
2027 func TestBasicYamlOperationFromMap(t *testing.T) {
2028 bytes, err := Marshal(makeBigMap())
2029 if err != nil {
2030 t.Fatalf("unexpected yaml.Marshal err: %v", err)
2031 }
2032 if string(bytes) != bigMapYaml {
2033 t.Fatalf("unexpected string equality")
2034 }
2035 rNode, err := Parse(string(bytes))
2036 if err != nil {
2037 t.Fatalf("unexpected yaml.Marshal err: %v", err)
2038 }
2039 rNodeString := rNode.MustString()
2040
2041
2042 rNodeStrings := strings.Split(rNodeString, "\n")
2043 bigMapStrings := strings.Split(bigMapYaml, "\n")
2044 if len(rNodeStrings) != len(bigMapStrings) {
2045 t.Fatalf("line count mismatch")
2046 }
2047 for i := range rNodeStrings {
2048 s1 := strings.TrimSpace(rNodeStrings[i])
2049 s2 := strings.TrimSpace(bigMapStrings[i])
2050 if s1 != s2 {
2051 t.Fatalf("expected '%s'=='%s'", s1, s2)
2052 }
2053 }
2054 }
2055
2056 func TestGetFieldValueReturnsSlice(t *testing.T) {
2057 bytes, err := yaml.Marshal(makeBigMap())
2058 if err != nil {
2059 t.Fatalf("unexpected yaml.Marshal err: %v", err)
2060 }
2061 rNode, err := Parse(string(bytes))
2062 if err != nil {
2063 t.Fatalf("unexpected yaml.Marshal err: %v", err)
2064 }
2065 expected := []interface{}{"idx0", "idx1", "idx2", "idx3"}
2066 actual, err := rNode.GetFieldValue("that")
2067 if err != nil {
2068 t.Fatalf("error getting slice: %v", err)
2069 }
2070 if diff := cmp.Diff(expected, actual); diff != "" {
2071 t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
2072 }
2073 }
2074
2075 func TestGetFieldValueReturnsSliceOfMappings(t *testing.T) {
2076 bytes, err := yaml.Marshal(makeBigMap())
2077 if err != nil {
2078 t.Fatalf("unexpected yaml.Marshal err: %v", err)
2079 }
2080 rn, err := Parse(string(bytes))
2081 if err != nil {
2082 t.Fatalf("unexpected yaml.Marshal err: %v", err)
2083 }
2084 expected := []interface{}{
2085 map[string]interface{}{
2086 "field1": "idx0foo",
2087 "field2": "idx0bar",
2088 },
2089 map[string]interface{}{
2090 "field1": "idx1foo",
2091 "field2": "idx1bar",
2092 },
2093 map[string]interface{}{
2094 "field1": "idx2foo",
2095 "field2": "idx2bar",
2096 },
2097 }
2098 actual, err := rn.GetFieldValue("those")
2099 if err != nil {
2100 t.Fatalf("error getting slice: %v", err)
2101 }
2102 if diff := cmp.Diff(expected, actual); diff != "" {
2103 t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
2104 }
2105 }
2106
2107 func TestGetFieldValueReturnsString(t *testing.T) {
2108 rn := NewRNode(nil)
2109 if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
2110 t.Fatalf("unexpected unmarshaljson err: %v", err)
2111 }
2112 actual, err := rn.GetFieldValue("metadata.labels.fruit")
2113 if err != nil {
2114 t.Fatalf("error getting field value: %v", err)
2115 }
2116 v, ok := actual.(string)
2117 if !ok || v != "apple" {
2118 t.Fatalf("unexpected value '%v'", actual)
2119 }
2120 }
2121
2122 func TestGetFieldValueResolvesAlias(t *testing.T) {
2123 yamlWithAlias := `
2124 foo: &a theValue
2125 bar: *a
2126 `
2127 rn, err := Parse(yamlWithAlias)
2128 if err != nil {
2129 t.Fatalf("unexpected yaml parse error: %v", err)
2130 }
2131 actual, err := rn.GetFieldValue("bar")
2132 if err != nil {
2133 t.Fatalf("error getting field value: %v", err)
2134 }
2135 v, ok := actual.(string)
2136 if !ok || v != "theValue" {
2137 t.Fatalf("unexpected value '%v'", actual)
2138 }
2139 }
2140
2141 func TestGetString(t *testing.T) {
2142 rn := NewRNode(nil)
2143 if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
2144 t.Fatalf("unexpected unmarshaljson err: %v", err)
2145 }
2146 expected := "carrot"
2147 actual, err := rn.GetString("metadata.labels.veggie")
2148 if err != nil {
2149 t.Fatalf("error getting string: %v", err)
2150 }
2151 if expected != actual {
2152 t.Fatalf("expected '%s', got '%s'", expected, actual)
2153 }
2154 }
2155
2156 func TestGetSlice(t *testing.T) {
2157 bytes, err := yaml.Marshal(makeBigMap())
2158 if err != nil {
2159 t.Fatalf("unexpected yaml.Marshal err: %v", err)
2160 }
2161 rn, err := Parse(string(bytes))
2162 if err != nil {
2163 t.Fatalf("unexpected yaml.Marshal err: %v", err)
2164 }
2165 expected := []interface{}{"idx0", "idx1", "idx2", "idx3"}
2166 actual, err := rn.GetSlice("that")
2167 if err != nil {
2168 t.Fatalf("error getting slice: %v", err)
2169 }
2170 if diff := cmp.Diff(expected, actual); diff != "" {
2171 t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
2172 }
2173 }
2174
2175 func TestRoundTripJSON(t *testing.T) {
2176 rn := NewRNode(nil)
2177 err := rn.UnmarshalJSON([]byte(deploymentLittleJson))
2178 if err != nil {
2179 t.Fatalf("unexpected UnmarshalJSON err: %v", err)
2180 }
2181 data, err := rn.MarshalJSON()
2182 if err != nil {
2183 t.Fatalf("unexpected MarshalJSON err: %v", err)
2184 }
2185 if actual := string(data); actual != deploymentLittleJson {
2186 t.Fatalf("expected %s, got %s", deploymentLittleJson, actual)
2187 }
2188 }
2189
2190 func TestGettingFields(t *testing.T) {
2191 rn := NewRNode(nil)
2192 err := rn.UnmarshalJSON([]byte(deploymentBiggerJson))
2193 if err != nil {
2194 t.Fatalf("unexpected unmarshaljson err: %v", err)
2195 }
2196 expected := "Deployment"
2197 actual := rn.GetKind()
2198 if expected != actual {
2199 t.Fatalf("expected '%s', got '%s'", expected, actual)
2200 }
2201 expected = "homer"
2202 actual = rn.GetName()
2203 if expected != actual {
2204 t.Fatalf("expected '%s', got '%s'", expected, actual)
2205 }
2206 actualMap := rn.GetLabels()
2207 v, ok := actualMap["fruit"]
2208 if !ok || v != "apple" {
2209 t.Fatalf("unexpected labels '%v'", actualMap)
2210 }
2211 actualMap = rn.GetAnnotations()
2212 v, ok = actualMap["greeting"]
2213 if !ok || v != "Take me to your leader." {
2214 t.Fatalf("unexpected annotations '%v'", actualMap)
2215 }
2216 }
2217
2218 func TestMapEmpty(t *testing.T) {
2219 newNodeMap, err := NewRNode(nil).Map()
2220 require.NoError(t, err)
2221 assert.Equal(t, 0, len(newNodeMap))
2222 }
2223
2224 func TestMap(t *testing.T) {
2225 rn := NewRNode(nil)
2226 if err := rn.UnmarshalJSON([]byte(deploymentLittleJson)); err != nil {
2227 t.Fatalf("unexpected unmarshaljson err: %v", err)
2228 }
2229
2230 expected := map[string]interface{}{
2231 "apiVersion": "apps/v1",
2232 "kind": "Deployment",
2233 "metadata": map[string]interface{}{
2234 "name": "homer",
2235 "namespace": "simpsons",
2236 },
2237 }
2238
2239 actual, err := rn.Map()
2240 require.NoError(t, err)
2241 if diff := cmp.Diff(expected, actual); diff != "" {
2242 t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
2243 }
2244 }
2245
2246 func TestSetName(t *testing.T) {
2247 rn := NewRNode(nil)
2248 if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
2249 t.Fatalf("unexpected unmarshaljson err: %v", err)
2250 }
2251 err := rn.SetName("marge")
2252 require.NoError(t, err)
2253 if expected, actual := "marge", rn.GetName(); expected != actual {
2254 t.Fatalf("expected '%s', got '%s'", expected, actual)
2255 }
2256 }
2257
2258 func TestSetNamespace(t *testing.T) {
2259 rn := NewRNode(nil)
2260 if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
2261 t.Fatalf("unexpected unmarshaljson err: %v", err)
2262 }
2263 err := rn.SetNamespace("flanders")
2264 require.NoError(t, err)
2265 meta, err := rn.GetMeta()
2266 require.NoError(t, err)
2267 if expected, actual := "flanders", meta.Namespace; expected != actual {
2268 t.Fatalf("expected '%s', got '%s'", expected, actual)
2269 }
2270 }
2271
2272 func TestSetLabels(t *testing.T) {
2273 rn := NewRNode(nil)
2274 if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
2275 t.Fatalf("unexpected unmarshaljson err: %v", err)
2276 }
2277 require.NoError(t, rn.SetLabels(map[string]string{
2278 "label1": "foo",
2279 "label2": "bar",
2280 }))
2281 labels := rn.GetLabels()
2282 if expected, actual := 2, len(labels); expected != actual {
2283 t.Fatalf("expected '%d', got '%d'", expected, actual)
2284 }
2285 if expected, actual := "foo", labels["label1"]; expected != actual {
2286 t.Fatalf("expected '%s', got '%s'", expected, actual)
2287 }
2288 if expected, actual := "bar", labels["label2"]; expected != actual {
2289 t.Fatalf("expected '%s', got '%s'", expected, actual)
2290 }
2291 }
2292
2293 func TestGetAnnotations(t *testing.T) {
2294 rn := NewRNode(nil)
2295 if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
2296 t.Fatalf("unexpected unmarshaljson err: %v", err)
2297 }
2298 require.NoError(t, rn.SetAnnotations(map[string]string{
2299 "annotation1": "foo",
2300 "annotation2": "bar",
2301 }))
2302 annotations := rn.GetAnnotations()
2303 if expected, actual := 2, len(annotations); expected != actual {
2304 t.Fatalf("expected '%d', got '%d'", expected, actual)
2305 }
2306 if expected, actual := "foo", annotations["annotation1"]; expected != actual {
2307 t.Fatalf("expected '%s', got '%s'", expected, actual)
2308 }
2309 if expected, actual := "bar", annotations["annotation2"]; expected != actual {
2310 t.Fatalf("expected '%s', got '%s'", expected, actual)
2311 }
2312 }
2313
2314 func BenchmarkGetAnnotations(b *testing.B) {
2315 counts := []int{0, 2, 5, 8}
2316 for _, count := range counts {
2317 appliedAnnotations := make(map[string]string, count)
2318 for i := 1; i <= count; i++ {
2319 key := fmt.Sprintf("annotation-key-%d", i)
2320 value := fmt.Sprintf("annotation-value-%d", i)
2321 appliedAnnotations[key] = value
2322 }
2323 rn := NewRNode(nil)
2324 if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
2325 b.Fatalf("unexpected unmarshaljson err: %v", err)
2326 }
2327 require.NoError(b, rn.SetAnnotations(appliedAnnotations))
2328 b.Run(fmt.Sprintf("%02d", count), func(b *testing.B) {
2329 for i := 0; i < b.N; i++ {
2330 _ = rn.GetAnnotations()
2331 }
2332 })
2333 }
2334 }
2335
2336 func TestGetFieldValueWithDot(t *testing.T) {
2337 const input = `
2338 kind: Pod
2339 metadata:
2340 name: hello-world
2341 labels:
2342 app: hello-world-app
2343 foo.appname: hello-world-foo
2344 `
2345 data, err := Parse(input)
2346 require.NoError(t, err)
2347
2348 labelRNode, err := data.Pipe(Lookup("metadata", "labels"))
2349 require.NoError(t, err)
2350
2351 app, err := labelRNode.GetFieldValue("app")
2352 require.NoError(t, err)
2353 require.Equal(t, "hello-world-app", app)
2354
2355 fooAppName, err := labelRNode.GetFieldValue(`foo\.appname`)
2356 require.NoError(t, err)
2357 require.Equal(t, "hello-world-foo", fooAppName)
2358 }
2359
View as plain text