1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package resourceoverrides
16
17 import (
18 "reflect"
19 "testing"
20
21 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
22 testk8s "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/k8s"
23
24 "github.com/google/go-cmp/cmp"
25 apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
26 v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 )
28
29 var (
30 fooKind = "Foo"
31 emptyObject = make(map[string]interface{})
32 )
33
34 func TestPreserveUserSpecifiedLegacyField(t *testing.T) {
35 t.Parallel()
36 tests := []struct {
37 name string
38 original *k8s.Resource
39 reconciled *k8s.Resource
40 fieldPath []string
41 expected *k8s.Resource
42 }{
43 {
44 name: "no legacy field is set",
45 original: &k8s.Resource{
46 TypeMeta: v1.TypeMeta{
47 Kind: fooKind,
48 },
49 Spec: map[string]interface{}{
50 "field1": "value",
51 },
52 },
53 reconciled: &k8s.Resource{
54 TypeMeta: v1.TypeMeta{
55 Kind: fooKind,
56 },
57 Spec: map[string]interface{}{
58 "field1": "value",
59 "field2": "defaultValue",
60 },
61 },
62 fieldPath: []string{"legacyField"},
63 expected: &k8s.Resource{
64 TypeMeta: v1.TypeMeta{
65 Kind: fooKind,
66 },
67 Spec: map[string]interface{}{
68 "field1": "value",
69 "field2": "defaultValue",
70 },
71 },
72 },
73 {
74 name: "user-specified legacy field is preserved",
75 original: &k8s.Resource{
76 TypeMeta: v1.TypeMeta{
77 Kind: fooKind,
78 },
79 Spec: map[string]interface{}{
80 "field1": "value",
81 "legacyField": "value",
82 },
83 },
84 reconciled: &k8s.Resource{
85 TypeMeta: v1.TypeMeta{
86 Kind: fooKind,
87 },
88 Spec: map[string]interface{}{
89 "field1": "value",
90 "field2": "defaultValue",
91 },
92 },
93 fieldPath: []string{"legacyField"},
94 expected: &k8s.Resource{
95 TypeMeta: v1.TypeMeta{
96 Kind: fooKind,
97 },
98 Spec: map[string]interface{}{
99 "field1": "value",
100 "legacyField": "value",
101 "field2": "defaultValue",
102 },
103 },
104 },
105 {
106 name: "user-specified nested legacy field is preserved",
107 original: &k8s.Resource{
108 TypeMeta: v1.TypeMeta{
109 Kind: fooKind,
110 },
111 Spec: map[string]interface{}{
112 "field1": "value",
113 "topField": map[string]interface{}{
114 "legacyField": "value",
115 },
116 },
117 },
118 reconciled: &k8s.Resource{
119 TypeMeta: v1.TypeMeta{
120 Kind: fooKind,
121 },
122 Spec: map[string]interface{}{
123 "field1": "value",
124 "topField": map[string]interface{}{
125 "newField": "value",
126 },
127 },
128 },
129 fieldPath: []string{"topField", "legacyField"},
130 expected: &k8s.Resource{
131 TypeMeta: v1.TypeMeta{
132 Kind: fooKind,
133 },
134 Spec: map[string]interface{}{
135 "field1": "value",
136 "topField": map[string]interface{}{
137 "legacyField": "value",
138 "newField": "value",
139 },
140 },
141 },
142 },
143 }
144
145 for _, tc := range tests {
146 t.Run(tc.name, func(t *testing.T) {
147 if err := PreserveUserSpecifiedLegacyField(tc.original, tc.reconciled, tc.fieldPath...); err != nil {
148 t.Fatalf("unexpected error: %v", err)
149 }
150 if !reflect.DeepEqual(tc.reconciled, tc.expected) {
151 t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.reconciled))
152 }
153 })
154 }
155 }
156
157 func TestPreserveUserSpecifiedLegacyFieldUnderSlice(t *testing.T) {
158 t.Parallel()
159 tests := []struct {
160 name string
161 original *k8s.Resource
162 reconciled *k8s.Resource
163 pathUpToSlice []string
164 fieldPath []string
165 expected *k8s.Resource
166 }{
167 {
168 name: "no legacy field is set",
169 original: &k8s.Resource{
170 TypeMeta: v1.TypeMeta{
171 Kind: fooKind,
172 },
173 Spec: map[string]interface{}{
174 "field1": "value",
175 },
176 },
177 reconciled: &k8s.Resource{
178 TypeMeta: v1.TypeMeta{
179 Kind: fooKind,
180 },
181 Spec: map[string]interface{}{
182 "field1": "value",
183 "field2": "defaultValue",
184 },
185 },
186 pathUpToSlice: []string{"pathUpToSlice"},
187 fieldPath: []string{"legacyField"},
188 expected: &k8s.Resource{
189 TypeMeta: v1.TypeMeta{
190 Kind: fooKind,
191 },
192 Spec: map[string]interface{}{
193 "field1": "value",
194 "field2": "defaultValue",
195 },
196 },
197 },
198 {
199 name: "user-specified legacy field is preserved for the correct slice element",
200 original: &k8s.Resource{
201 TypeMeta: v1.TypeMeta{
202 Kind: fooKind,
203 },
204 Spec: map[string]interface{}{
205 "field1": "value",
206 "pathUpToSlice": []interface{}{
207 map[string]interface{}{
208 "subfield1": "value1",
209 },
210 map[string]interface{}{
211 "legacyField": "value",
212 },
213 },
214 },
215 },
216 reconciled: &k8s.Resource{
217 TypeMeta: v1.TypeMeta{
218 Kind: fooKind,
219 },
220 Spec: map[string]interface{}{
221 "field1": "value",
222 "pathUpToSlice": []interface{}{
223 map[string]interface{}{
224 "subfield1": "value1",
225 },
226 map[string]interface{}{},
227 },
228 "field2": "defaultValue",
229 },
230 },
231 pathUpToSlice: []string{"pathUpToSlice"},
232 fieldPath: []string{"legacyField"},
233 expected: &k8s.Resource{
234 TypeMeta: v1.TypeMeta{
235 Kind: fooKind,
236 },
237 Spec: map[string]interface{}{
238 "field1": "value",
239 "pathUpToSlice": []interface{}{
240 map[string]interface{}{
241 "subfield1": "value1",
242 },
243 map[string]interface{}{
244 "legacyField": "value",
245 },
246 },
247 "field2": "defaultValue",
248 },
249 },
250 },
251 {
252 name: "user-specified nested legacy field is preserved",
253 original: &k8s.Resource{
254 TypeMeta: v1.TypeMeta{
255 Kind: fooKind,
256 },
257 Spec: map[string]interface{}{
258 "field1": "value",
259 "topField1": map[string]interface{}{
260 "pathUpToSlice": []interface{}{
261 map[string]interface{}{
262 "topField2": map[string]interface{}{
263 "legacyField": "value",
264 },
265 },
266 },
267 },
268 },
269 },
270 reconciled: &k8s.Resource{
271 TypeMeta: v1.TypeMeta{
272 Kind: fooKind,
273 },
274 Spec: map[string]interface{}{
275 "field1": "value",
276 "topField1": map[string]interface{}{
277 "pathUpToSlice": []interface{}{
278 map[string]interface{}{
279 "topField2": map[string]interface{}{
280 "newField": "value1",
281 },
282 },
283 },
284 },
285 },
286 },
287 pathUpToSlice: []string{"topField1", "pathUpToSlice"},
288 fieldPath: []string{"topField2", "legacyField"},
289 expected: &k8s.Resource{
290 TypeMeta: v1.TypeMeta{
291 Kind: fooKind,
292 },
293 Spec: map[string]interface{}{
294 "field1": "value",
295 "topField1": map[string]interface{}{
296 "pathUpToSlice": []interface{}{
297 map[string]interface{}{
298 "topField2": map[string]interface{}{
299 "newField": "value1",
300 "legacyField": "value",
301 },
302 },
303 },
304 },
305 },
306 },
307 },
308 }
309
310 for _, tc := range tests {
311 t.Run(tc.name, func(t *testing.T) {
312 if err := PreserveUserSpecifiedLegacyFieldUnderSlice(tc.original, tc.reconciled, tc.pathUpToSlice, tc.fieldPath); err != nil {
313 t.Fatalf("unexpected error: %v", err)
314 }
315 if !reflect.DeepEqual(tc.reconciled, tc.expected) {
316 t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.reconciled))
317 }
318 })
319 }
320 }
321
322 func TestPreserveUserSpecifiedLegacyArrayField(t *testing.T) {
323 t.Parallel()
324 tests := []struct {
325 name string
326 original *k8s.Resource
327 reconciled *k8s.Resource
328 fieldPath []string
329 expected *k8s.Resource
330 hasError bool
331 }{
332 {
333 name: "no legacy array field is set",
334 original: &k8s.Resource{
335 TypeMeta: v1.TypeMeta{
336 Kind: fooKind,
337 },
338 Spec: map[string]interface{}{
339 "field1": "value",
340 },
341 },
342 reconciled: &k8s.Resource{
343 TypeMeta: v1.TypeMeta{
344 Kind: fooKind,
345 },
346 Spec: map[string]interface{}{
347 "field1": "value",
348 "field2": "defaultValue",
349 },
350 },
351 fieldPath: []string{"legacyArray"},
352 expected: &k8s.Resource{
353 TypeMeta: v1.TypeMeta{
354 Kind: fooKind,
355 },
356 Spec: map[string]interface{}{
357 "field1": "value",
358 "field2": "defaultValue",
359 },
360 },
361 },
362 {
363 name: "user-specified legacy array field is preserved",
364 original: &k8s.Resource{
365 TypeMeta: v1.TypeMeta{
366 Kind: fooKind,
367 },
368 Spec: map[string]interface{}{
369 "field1": "value",
370 "legacyArray": []interface{}{
371 "testValue1",
372 "testValue2",
373 },
374 },
375 },
376 reconciled: &k8s.Resource{
377 TypeMeta: v1.TypeMeta{
378 Kind: fooKind,
379 },
380 Spec: map[string]interface{}{
381 "field1": "value",
382 "field2": "defaultValue",
383 },
384 },
385 fieldPath: []string{"legacyArray"},
386 expected: &k8s.Resource{
387 TypeMeta: v1.TypeMeta{
388 Kind: fooKind,
389 },
390 Spec: map[string]interface{}{
391 "field1": "value",
392 "field2": "defaultValue",
393 "legacyArray": []interface{}{
394 "testValue1",
395 "testValue2",
396 },
397 },
398 },
399 },
400 {
401 name: "user-specified nested legacy array field is preserved",
402 original: &k8s.Resource{
403 TypeMeta: v1.TypeMeta{
404 Kind: fooKind,
405 },
406 Spec: map[string]interface{}{
407 "field1": "value",
408 "topField": map[string]interface{}{
409 "legacyArray": []interface{}{
410 "testValue1",
411 "testValue2",
412 },
413 },
414 },
415 },
416 reconciled: &k8s.Resource{
417 TypeMeta: v1.TypeMeta{
418 Kind: fooKind,
419 },
420 Spec: map[string]interface{}{
421 "field1": "value",
422 "topField": map[string]interface{}{
423 "newField": "value",
424 },
425 },
426 },
427 fieldPath: []string{"topField", "legacyArray"},
428 expected: &k8s.Resource{
429 TypeMeta: v1.TypeMeta{
430 Kind: fooKind,
431 },
432 Spec: map[string]interface{}{
433 "field1": "value",
434 "topField": map[string]interface{}{
435 "legacyArray": []interface{}{
436 "testValue1",
437 "testValue2",
438 },
439 "newField": "value",
440 },
441 },
442 },
443 },
444 {
445 name: "user-specified legacy field is not preserved because it's " +
446 "not an array",
447 original: &k8s.Resource{
448 TypeMeta: v1.TypeMeta{
449 Kind: fooKind,
450 },
451 Spec: map[string]interface{}{
452 "field1": "value",
453 "legacyArray": "testValue",
454 },
455 },
456 reconciled: &k8s.Resource{
457 TypeMeta: v1.TypeMeta{
458 Kind: fooKind,
459 },
460 Spec: map[string]interface{}{
461 "field1": "value",
462 "field2": "defaultValue",
463 },
464 },
465 fieldPath: []string{"legacyArray"},
466 hasError: true,
467 },
468 }
469
470 for _, tc := range tests {
471 t.Run(tc.name, func(t *testing.T) {
472 err := PreserveUserSpecifiedLegacyArrayField(tc.original, tc.reconciled, tc.fieldPath...)
473 if tc.hasError {
474 if err == nil {
475 t.Fatalf("got nil, but expect to have error")
476 }
477 return
478 }
479 if err != nil {
480 t.Fatalf("unexpected error: %v", err)
481 }
482 if got, want := tc.reconciled, tc.expected; !reflect.DeepEqual(got, want) {
483 t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(want, got))
484 }
485 })
486 }
487 }
488
489 func TestPruneDefaultedAuthoritativeFieldIfOnlyLegacyFieldSpecified(t *testing.T) {
490 t.Parallel()
491 tests := []struct {
492 name string
493 original *k8s.Resource
494 reconciled *k8s.Resource
495 legacyFieldPath []string
496 fieldPath []string
497 expected *k8s.Resource
498 }{
499 {
500 name: "neither the legacy field nor the authoritative field is set; the authoritative field is not defaulted",
501 original: &k8s.Resource{
502 TypeMeta: v1.TypeMeta{
503 Kind: fooKind,
504 },
505 Spec: map[string]interface{}{
506 "field1": "value",
507 },
508 },
509 reconciled: &k8s.Resource{
510 TypeMeta: v1.TypeMeta{
511 Kind: fooKind,
512 },
513 Spec: map[string]interface{}{
514 "field1": "value",
515 "field2": "defaultValue",
516 },
517 },
518 legacyFieldPath: []string{"legacyField"},
519 fieldPath: []string{"authoritativeField"},
520 expected: &k8s.Resource{
521 TypeMeta: v1.TypeMeta{
522 Kind: fooKind,
523 },
524 Spec: map[string]interface{}{
525 "field1": "value",
526 "field2": "defaultValue",
527 },
528 },
529 },
530 {
531 name: "neither the legacy field nor the authoritative field is set; the authoritative field is defaulted",
532 original: &k8s.Resource{
533 TypeMeta: v1.TypeMeta{
534 Kind: fooKind,
535 },
536 Spec: map[string]interface{}{
537 "field1": "value",
538 },
539 },
540 reconciled: &k8s.Resource{
541 TypeMeta: v1.TypeMeta{
542 Kind: fooKind,
543 },
544 Spec: map[string]interface{}{
545 "field1": "value",
546 "authoritativeField": "defaultValue",
547 },
548 },
549 legacyFieldPath: []string{"legacyField"},
550 fieldPath: []string{"authoritativeField"},
551 expected: &k8s.Resource{
552 TypeMeta: v1.TypeMeta{
553 Kind: fooKind,
554 },
555 Spec: map[string]interface{}{
556 "field1": "value",
557 "authoritativeField": "defaultValue",
558 },
559 },
560 },
561 {
562 name: "prune the authoritative field if users only specify the legacy field",
563 original: &k8s.Resource{
564 TypeMeta: v1.TypeMeta{
565 Kind: fooKind,
566 },
567 Spec: map[string]interface{}{
568 "field1": "value",
569 "legacyField": "value",
570 },
571 },
572 reconciled: &k8s.Resource{
573 TypeMeta: v1.TypeMeta{
574 Kind: fooKind,
575 },
576 Spec: map[string]interface{}{
577 "field1": "value",
578 "authoritativeField": "value",
579 },
580 },
581 legacyFieldPath: []string{"legacyField"},
582 fieldPath: []string{"authoritativeField"},
583 expected: &k8s.Resource{
584 TypeMeta: v1.TypeMeta{
585 Kind: fooKind,
586 },
587 Spec: map[string]interface{}{
588 "field1": "value",
589 },
590 },
591 },
592 {
593 name: "prune the nested authoritative field if users only specify the legacy field",
594 original: &k8s.Resource{
595 TypeMeta: v1.TypeMeta{
596 Kind: fooKind,
597 },
598 Spec: map[string]interface{}{
599 "field1": "value",
600 "topField": map[string]interface{}{
601 "field2": "value",
602 "legacyField": "value",
603 },
604 },
605 },
606 reconciled: &k8s.Resource{
607 TypeMeta: v1.TypeMeta{
608 Kind: fooKind,
609 },
610 Spec: map[string]interface{}{
611 "field1": "value",
612 "topField": map[string]interface{}{
613 "field2": "value",
614 "authoritativeField": "value",
615 },
616 },
617 },
618 legacyFieldPath: []string{"topField", "legacyField"},
619 fieldPath: []string{"topField", "authoritativeField"},
620 expected: &k8s.Resource{
621 TypeMeta: v1.TypeMeta{
622 Kind: fooKind,
623 },
624 Spec: map[string]interface{}{
625 "field1": "value",
626 "topField": map[string]interface{}{
627 "field2": "value",
628 },
629 },
630 },
631 },
632 {
633 name: "users only specify the authoritative field",
634 original: &k8s.Resource{
635 TypeMeta: v1.TypeMeta{
636 Kind: fooKind,
637 },
638 Spec: map[string]interface{}{
639 "field1": "value",
640 "authoritativeField": "value",
641 },
642 },
643 reconciled: &k8s.Resource{
644 TypeMeta: v1.TypeMeta{
645 Kind: fooKind,
646 },
647 Spec: map[string]interface{}{
648 "field1": "value",
649 "authoritativeField": "value",
650 },
651 },
652 legacyFieldPath: []string{"legacyField"},
653 fieldPath: []string{"authoritativeField"},
654 expected: &k8s.Resource{
655 TypeMeta: v1.TypeMeta{
656 Kind: fooKind,
657 },
658 Spec: map[string]interface{}{
659 "field1": "value",
660 "authoritativeField": "value",
661 },
662 },
663 },
664 {
665 name: "users specify both the legacy and the authoritative fields",
666 original: &k8s.Resource{
667 TypeMeta: v1.TypeMeta{
668 Kind: fooKind,
669 },
670 Spec: map[string]interface{}{
671 "field1": "value",
672 "legacyField": "value",
673 "authoritativeField": "value",
674 },
675 },
676 reconciled: &k8s.Resource{
677 TypeMeta: v1.TypeMeta{
678 Kind: fooKind,
679 },
680 Spec: map[string]interface{}{
681 "field1": "value",
682 "legacyField": "value",
683 "authoritativeField": "value",
684 },
685 },
686 legacyFieldPath: []string{"legacyField"},
687 fieldPath: []string{"authoritativeField"},
688 expected: &k8s.Resource{
689 TypeMeta: v1.TypeMeta{
690 Kind: fooKind,
691 },
692 Spec: map[string]interface{}{
693 "field1": "value",
694 "legacyField": "value",
695 "authoritativeField": "value",
696 },
697 },
698 },
699 }
700
701 for _, tc := range tests {
702 t.Run(tc.name, func(t *testing.T) {
703 if err := PruneDefaultedAuthoritativeFieldIfOnlyLegacyFieldSpecified(tc.original, tc.reconciled, tc.legacyFieldPath, tc.fieldPath); err != nil {
704 t.Fatalf("unexpected error: %v", err)
705 }
706 if !reflect.DeepEqual(tc.reconciled, tc.expected) {
707 t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.reconciled))
708 }
709 })
710 }
711 }
712
713 func TestPruneDefaultedAuthoritativeFieldIfOnlyLegacyFieldSpecifiedUnderSlice(t *testing.T) {
714 t.Parallel()
715 tests := []struct {
716 name string
717 original *k8s.Resource
718 reconciled *k8s.Resource
719 pathUpToSlice []string
720 legacyFieldPath []string
721 authoritativeFieldPath []string
722 expected *k8s.Resource
723 }{
724 {
725 name: "neither the legacy field nor the authoritative field is set; the authoritative field is not defaulted",
726 original: &k8s.Resource{
727 TypeMeta: v1.TypeMeta{
728 Kind: fooKind,
729 },
730 Spec: map[string]interface{}{
731 "field1": "value",
732 },
733 },
734 reconciled: &k8s.Resource{
735 TypeMeta: v1.TypeMeta{
736 Kind: fooKind,
737 },
738 Spec: map[string]interface{}{
739 "field1": "value",
740 "field2": "defaultValue",
741 },
742 },
743 pathUpToSlice: []string{"pathUpToSlice"},
744 legacyFieldPath: []string{"legacyField"},
745 authoritativeFieldPath: []string{"authoritativeField"},
746 expected: &k8s.Resource{
747 TypeMeta: v1.TypeMeta{
748 Kind: fooKind,
749 },
750 Spec: map[string]interface{}{
751 "field1": "value",
752 "field2": "defaultValue",
753 },
754 },
755 },
756 {
757 name: "neither the legacy field nor the authoritative field is set; the authoritative field is defaulted",
758 original: &k8s.Resource{
759 TypeMeta: v1.TypeMeta{
760 Kind: fooKind,
761 },
762 Spec: map[string]interface{}{
763 "field1": "value",
764 },
765 },
766 reconciled: &k8s.Resource{
767 TypeMeta: v1.TypeMeta{
768 Kind: fooKind,
769 },
770 Spec: map[string]interface{}{
771 "field1": "value",
772 "pathUpToSlice": []interface{}{
773 map[string]interface{}{
774 "authoritativeField": "defaultValue",
775 },
776 },
777 },
778 },
779 pathUpToSlice: []string{"pathUpToSlice"},
780 legacyFieldPath: []string{"legacyField"},
781 authoritativeFieldPath: []string{"authoritativeField"},
782 expected: &k8s.Resource{
783 TypeMeta: v1.TypeMeta{
784 Kind: fooKind,
785 },
786 Spec: map[string]interface{}{
787 "field1": "value",
788 "pathUpToSlice": []interface{}{
789 map[string]interface{}{
790 "authoritativeField": "defaultValue",
791 },
792 },
793 },
794 },
795 },
796 {
797 name: "prune the authoritative field if users only specify the legacy field",
798 original: &k8s.Resource{
799 TypeMeta: v1.TypeMeta{
800 Kind: fooKind,
801 },
802 Spec: map[string]interface{}{
803 "field1": "value",
804 "pathUpToSlice": []interface{}{
805 map[string]interface{}{
806 "legacyField": "value",
807 },
808 },
809 },
810 },
811 reconciled: &k8s.Resource{
812 TypeMeta: v1.TypeMeta{
813 Kind: fooKind,
814 },
815 Spec: map[string]interface{}{
816 "field1": "value",
817 "pathUpToSlice": []interface{}{
818 map[string]interface{}{
819 "authoritativeField": "value",
820 },
821 },
822 },
823 },
824 pathUpToSlice: []string{"pathUpToSlice"},
825 legacyFieldPath: []string{"legacyField"},
826 authoritativeFieldPath: []string{"authoritativeField"},
827 expected: &k8s.Resource{
828 TypeMeta: v1.TypeMeta{
829 Kind: fooKind,
830 },
831 Spec: map[string]interface{}{
832 "field1": "value",
833 "pathUpToSlice": []interface{}{
834 map[string]interface{}{},
835 },
836 },
837 },
838 },
839 {
840 name: "prune the nested authoritative field if users only specify the legacy field",
841 original: &k8s.Resource{
842 TypeMeta: v1.TypeMeta{
843 Kind: fooKind,
844 },
845 Spec: map[string]interface{}{
846 "field1": "value",
847 "topField1": map[string]interface{}{
848 "pathUpToSlice": []interface{}{
849 map[string]interface{}{
850 "topField2": map[string]interface{}{
851 "legacyField": "value",
852 "field2": "value",
853 },
854 },
855 },
856 },
857 },
858 },
859 reconciled: &k8s.Resource{
860 TypeMeta: v1.TypeMeta{
861 Kind: fooKind,
862 },
863 Spec: map[string]interface{}{
864 "field1": "value",
865 "topField1": map[string]interface{}{
866 "pathUpToSlice": []interface{}{
867 map[string]interface{}{
868 "topField2": map[string]interface{}{
869 "authoritativeField": "value",
870 "field2": "value",
871 },
872 },
873 },
874 },
875 },
876 },
877 pathUpToSlice: []string{"topField1", "pathUpToSlice"},
878 legacyFieldPath: []string{"topField2", "legacyField"},
879 authoritativeFieldPath: []string{"topField2", "authoritativeField"},
880 expected: &k8s.Resource{
881 TypeMeta: v1.TypeMeta{
882 Kind: fooKind,
883 },
884 Spec: map[string]interface{}{
885 "field1": "value",
886 "topField1": map[string]interface{}{
887 "pathUpToSlice": []interface{}{
888 map[string]interface{}{
889 "topField2": map[string]interface{}{
890 "field2": "value",
891 },
892 },
893 },
894 },
895 },
896 },
897 },
898 {
899 name: "users only specify the authoritative field",
900 original: &k8s.Resource{
901 TypeMeta: v1.TypeMeta{
902 Kind: fooKind,
903 },
904 Spec: map[string]interface{}{
905 "field1": "value",
906 "pathUpToSlice": []interface{}{
907 map[string]interface{}{
908 "authoritativeField": "value",
909 },
910 },
911 },
912 },
913 reconciled: &k8s.Resource{
914 TypeMeta: v1.TypeMeta{
915 Kind: fooKind,
916 },
917 Spec: map[string]interface{}{
918 "field1": "value",
919 "pathUpToSlice": []interface{}{
920 map[string]interface{}{
921 "authoritativeField": "value",
922 },
923 },
924 },
925 },
926 pathUpToSlice: []string{"pathUpToSlice"},
927 legacyFieldPath: []string{"legacyField"},
928 authoritativeFieldPath: []string{"authoritativeField"},
929 expected: &k8s.Resource{
930 TypeMeta: v1.TypeMeta{
931 Kind: fooKind,
932 },
933 Spec: map[string]interface{}{
934 "field1": "value",
935 "pathUpToSlice": []interface{}{
936 map[string]interface{}{
937 "authoritativeField": "value",
938 },
939 },
940 },
941 },
942 },
943 {
944 name: "handle multiple elements with different use-cases",
945 original: &k8s.Resource{
946 TypeMeta: v1.TypeMeta{
947 Kind: fooKind,
948 },
949 Spec: map[string]interface{}{
950 "field1": "value",
951 "pathUpToSlice": []interface{}{
952 map[string]interface{}{
953 "legacyField": "value",
954 },
955 map[string]interface{}{
956 "authoritativeField": "value1",
957 },
958 },
959 },
960 },
961 reconciled: &k8s.Resource{
962 TypeMeta: v1.TypeMeta{
963 Kind: fooKind,
964 },
965 Spec: map[string]interface{}{
966 "field1": "value",
967 "pathUpToSlice": []interface{}{
968 map[string]interface{}{
969 "authoritativeField": "value",
970 },
971 map[string]interface{}{
972 "authoritativeField": "value1",
973 },
974 map[string]interface{}{
975 "authoritativeField": "defaultValue",
976 },
977 },
978 },
979 },
980 pathUpToSlice: []string{"pathUpToSlice"},
981 legacyFieldPath: []string{"legacyField"},
982 authoritativeFieldPath: []string{"authoritativeField"},
983 expected: &k8s.Resource{
984 TypeMeta: v1.TypeMeta{
985 Kind: fooKind,
986 },
987 Spec: map[string]interface{}{
988 "field1": "value",
989 "pathUpToSlice": []interface{}{
990 map[string]interface{}{},
991 map[string]interface{}{
992 "authoritativeField": "value1",
993 },
994 map[string]interface{}{
995 "authoritativeField": "defaultValue",
996 },
997 },
998 },
999 },
1000 },
1001 }
1002
1003 for _, tc := range tests {
1004 t.Run(tc.name, func(t *testing.T) {
1005 if err := PruneDefaultedAuthoritativeFieldIfOnlyLegacyFieldSpecifiedUnderSlice(tc.original, tc.reconciled, tc.pathUpToSlice, tc.legacyFieldPath, tc.authoritativeFieldPath); err != nil {
1006 t.Fatalf("unexpected error: %v", err)
1007 }
1008 if !reflect.DeepEqual(tc.reconciled, tc.expected) {
1009 t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.reconciled))
1010 }
1011 })
1012 }
1013 }
1014
1015 func TestPruneDefaultedAuthoritativeArrayFieldIfOnlyLegacyArrayFieldSpecified(t *testing.T) {
1016 t.Parallel()
1017 tests := []struct {
1018 name string
1019 original *k8s.Resource
1020 reconciled *k8s.Resource
1021 legacyFieldPath []string
1022 fieldPath []string
1023 expected *k8s.Resource
1024 hasError bool
1025 }{
1026 {
1027 name: "neither the legacy array field nor the authoritative array " +
1028 "field is set; the authoritative array field is not defaulted",
1029 original: &k8s.Resource{
1030 TypeMeta: v1.TypeMeta{
1031 Kind: fooKind,
1032 },
1033 Spec: map[string]interface{}{
1034 "field1": "value",
1035 },
1036 },
1037 reconciled: &k8s.Resource{
1038 TypeMeta: v1.TypeMeta{
1039 Kind: fooKind,
1040 },
1041 Spec: map[string]interface{}{
1042 "field1": "value",
1043 "field2": "defaultValue",
1044 },
1045 },
1046 legacyFieldPath: []string{"legacyArray"},
1047 fieldPath: []string{"authoritativeArray"},
1048 expected: &k8s.Resource{
1049 TypeMeta: v1.TypeMeta{
1050 Kind: fooKind,
1051 },
1052 Spec: map[string]interface{}{
1053 "field1": "value",
1054 "field2": "defaultValue",
1055 },
1056 },
1057 },
1058 {
1059 name: "neither the legacy array field nor the authoritative array " +
1060 "field is set; the authoritative array field is defaulted",
1061 original: &k8s.Resource{
1062 TypeMeta: v1.TypeMeta{
1063 Kind: fooKind,
1064 },
1065 Spec: map[string]interface{}{
1066 "field1": "value",
1067 },
1068 },
1069 reconciled: &k8s.Resource{
1070 TypeMeta: v1.TypeMeta{
1071 Kind: fooKind,
1072 },
1073 Spec: map[string]interface{}{
1074 "field1": "value",
1075 "authoritativeArray": []interface{}{
1076 "defaultedValue1",
1077 "defaultedValue2",
1078 },
1079 },
1080 },
1081 legacyFieldPath: []string{"legacyArray"},
1082 fieldPath: []string{"authoritativeArray"},
1083 expected: &k8s.Resource{
1084 TypeMeta: v1.TypeMeta{
1085 Kind: fooKind,
1086 },
1087 Spec: map[string]interface{}{
1088 "field1": "value",
1089 "authoritativeArray": []interface{}{
1090 "defaultedValue1",
1091 "defaultedValue2",
1092 },
1093 },
1094 },
1095 },
1096 {
1097 name: "prune the authoritative array field if users only specify " +
1098 "the legacy array field",
1099 original: &k8s.Resource{
1100 TypeMeta: v1.TypeMeta{
1101 Kind: fooKind,
1102 },
1103 Spec: map[string]interface{}{
1104 "field1": "value",
1105 "legacyArray": []interface{}{
1106 "testValue1",
1107 "testValue2",
1108 },
1109 },
1110 },
1111 reconciled: &k8s.Resource{
1112 TypeMeta: v1.TypeMeta{
1113 Kind: fooKind,
1114 },
1115 Spec: map[string]interface{}{
1116 "field1": "value",
1117 "authoritativeArray": []interface{}{
1118 "testValue1",
1119 "testValue2",
1120 },
1121 },
1122 },
1123 legacyFieldPath: []string{"legacyArray"},
1124 fieldPath: []string{"authoritativeArray"},
1125 expected: &k8s.Resource{
1126 TypeMeta: v1.TypeMeta{
1127 Kind: fooKind,
1128 },
1129 Spec: map[string]interface{}{
1130 "field1": "value",
1131 },
1132 },
1133 },
1134 {
1135 name: "prune the authoritative array field of objects if users " +
1136 "only specify the legacy array field of strings",
1137 original: &k8s.Resource{
1138 TypeMeta: v1.TypeMeta{
1139 Kind: fooKind,
1140 },
1141 Spec: map[string]interface{}{
1142 "field1": "value",
1143 "legacyArray": []interface{}{
1144 "testValue1",
1145 "testValue2",
1146 },
1147 },
1148 },
1149 reconciled: &k8s.Resource{
1150 TypeMeta: v1.TypeMeta{
1151 Kind: fooKind,
1152 },
1153 Spec: map[string]interface{}{
1154 "field1": "value",
1155 "authoritativeArray": []interface{}{
1156 map[string]interface{}{
1157 "external": "testValue1",
1158 },
1159 map[string]interface{}{
1160 "external": "testValue2",
1161 },
1162 },
1163 },
1164 },
1165 legacyFieldPath: []string{"legacyArray"},
1166 fieldPath: []string{"authoritativeArray"},
1167 expected: &k8s.Resource{
1168 TypeMeta: v1.TypeMeta{
1169 Kind: fooKind,
1170 },
1171 Spec: map[string]interface{}{
1172 "field1": "value",
1173 },
1174 },
1175 },
1176 {
1177 name: "prune the nested authoritative array field if users only " +
1178 "specify the legacy array field",
1179 original: &k8s.Resource{
1180 TypeMeta: v1.TypeMeta{
1181 Kind: fooKind,
1182 },
1183 Spec: map[string]interface{}{
1184 "field1": "value",
1185 "topField": map[string]interface{}{
1186 "field2": "value",
1187 "legacyArray": []interface{}{
1188 "testValue1",
1189 "testValue2",
1190 },
1191 },
1192 },
1193 },
1194 reconciled: &k8s.Resource{
1195 TypeMeta: v1.TypeMeta{
1196 Kind: fooKind,
1197 },
1198 Spec: map[string]interface{}{
1199 "field1": "value",
1200 "topField": map[string]interface{}{
1201 "field2": "value",
1202 "authoritativeArray": []interface{}{
1203 "testValue1",
1204 "testValue2",
1205 },
1206 },
1207 },
1208 },
1209 legacyFieldPath: []string{"topField", "legacyArray"},
1210 fieldPath: []string{"topField", "authoritativeArray"},
1211 expected: &k8s.Resource{
1212 TypeMeta: v1.TypeMeta{
1213 Kind: fooKind,
1214 },
1215 Spec: map[string]interface{}{
1216 "field1": "value",
1217 "topField": map[string]interface{}{
1218 "field2": "value",
1219 },
1220 },
1221 },
1222 },
1223 {
1224 name: "users only specify the authoritative array field",
1225 original: &k8s.Resource{
1226 TypeMeta: v1.TypeMeta{
1227 Kind: fooKind,
1228 },
1229 Spec: map[string]interface{}{
1230 "field1": "value",
1231 "authoritativeArray": []interface{}{
1232 "testValue1",
1233 "testValue2",
1234 },
1235 },
1236 },
1237 reconciled: &k8s.Resource{
1238 TypeMeta: v1.TypeMeta{
1239 Kind: fooKind,
1240 },
1241 Spec: map[string]interface{}{
1242 "field1": "value",
1243 "authoritativeArray": []interface{}{
1244 "testValue1",
1245 "testValue2",
1246 },
1247 },
1248 },
1249 legacyFieldPath: []string{"legacyArray"},
1250 fieldPath: []string{"authoritativeArray"},
1251 expected: &k8s.Resource{
1252 TypeMeta: v1.TypeMeta{
1253 Kind: fooKind,
1254 },
1255 Spec: map[string]interface{}{
1256 "field1": "value",
1257 "authoritativeArray": []interface{}{
1258 "testValue1",
1259 "testValue2",
1260 },
1261 },
1262 },
1263 },
1264 {
1265 name: "users specify both the legacy and the authoritative fields",
1266 original: &k8s.Resource{
1267 TypeMeta: v1.TypeMeta{
1268 Kind: fooKind,
1269 },
1270 Spec: map[string]interface{}{
1271 "field1": "value",
1272 "legacyArray": []interface{}{
1273 "testValue1",
1274 "testValue2",
1275 },
1276 "authoritativeArray": []interface{}{
1277 "testValue1",
1278 "testValue2",
1279 },
1280 },
1281 },
1282 reconciled: &k8s.Resource{
1283 TypeMeta: v1.TypeMeta{
1284 Kind: fooKind,
1285 },
1286 Spec: map[string]interface{}{
1287 "field1": "value",
1288 "legacyArray": []interface{}{
1289 "testValue1",
1290 "testValue2",
1291 },
1292 "authoritativeArray": []interface{}{
1293 "testValue1",
1294 "testValue2",
1295 },
1296 },
1297 },
1298 legacyFieldPath: []string{"legacyArray"},
1299 fieldPath: []string{"authoritativeArray"},
1300 expected: &k8s.Resource{
1301 TypeMeta: v1.TypeMeta{
1302 Kind: fooKind,
1303 },
1304 Spec: map[string]interface{}{
1305 "field1": "value",
1306 "legacyArray": []interface{}{
1307 "testValue1",
1308 "testValue2",
1309 },
1310 "authoritativeArray": []interface{}{
1311 "testValue1",
1312 "testValue2",
1313 },
1314 },
1315 },
1316 },
1317 {
1318 name: "error pruning the authoritative array field if the legacy " +
1319 "field is not an array",
1320 original: &k8s.Resource{
1321 TypeMeta: v1.TypeMeta{
1322 Kind: fooKind,
1323 },
1324 Spec: map[string]interface{}{
1325 "field1": "value",
1326 "legacyField": "testValue1",
1327 },
1328 },
1329 reconciled: &k8s.Resource{
1330 TypeMeta: v1.TypeMeta{
1331 Kind: fooKind,
1332 },
1333 Spec: map[string]interface{}{
1334 "field1": "value",
1335 "authoritativeArray": []interface{}{
1336 "testValue1",
1337 "testValue2",
1338 },
1339 },
1340 },
1341 legacyFieldPath: []string{"legacyField"},
1342 fieldPath: []string{"authoritativeArray"},
1343 hasError: true,
1344 },
1345 {
1346 name: "error when users only specify the non-array authoritative " +
1347 "field",
1348 original: &k8s.Resource{
1349 TypeMeta: v1.TypeMeta{
1350 Kind: fooKind,
1351 },
1352 Spec: map[string]interface{}{
1353 "field1": "value",
1354 "authoritativeField": "testValue1",
1355 },
1356 },
1357 reconciled: &k8s.Resource{
1358 TypeMeta: v1.TypeMeta{
1359 Kind: fooKind,
1360 },
1361 Spec: map[string]interface{}{
1362 "field1": "value",
1363 "authoritativeField": "testValue1",
1364 },
1365 },
1366 legacyFieldPath: []string{"legacyArray"},
1367 fieldPath: []string{"authoritativeField"},
1368 hasError: true,
1369 },
1370 }
1371
1372 for _, tc := range tests {
1373 t.Run(tc.name, func(t *testing.T) {
1374 err := PruneDefaultedAuthoritativeArrayFieldIfOnlyLegacyArrayFieldSpecified(tc.original, tc.reconciled, tc.legacyFieldPath, tc.fieldPath)
1375 if tc.hasError {
1376 if err == nil {
1377 t.Fatalf("got nil, but expect to have error")
1378 }
1379 return
1380 }
1381 if err != nil {
1382 t.Fatalf("unexpected error: %v", err)
1383 }
1384 if got, want := tc.reconciled, tc.expected; !reflect.DeepEqual(got, want) {
1385 t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(want, got))
1386 }
1387 })
1388 }
1389 }
1390
1391 func TestFavorAuthoritativeFieldOverLegacyField(t *testing.T) {
1392 t.Parallel()
1393 tests := []struct {
1394 name string
1395 original *k8s.Resource
1396 legacyFieldPath []string
1397 fieldPath []string
1398 expected *k8s.Resource
1399 hasError bool
1400 }{
1401 {
1402 name: "neither the legacy field nor the authoritative field is set",
1403 original: &k8s.Resource{
1404 TypeMeta: v1.TypeMeta{
1405 Kind: fooKind,
1406 },
1407 Spec: map[string]interface{}{
1408 "field1": "value",
1409 },
1410 },
1411 legacyFieldPath: []string{"legacyField"},
1412 fieldPath: []string{"authoritativeField"},
1413 expected: &k8s.Resource{
1414 TypeMeta: v1.TypeMeta{
1415 Kind: fooKind,
1416 },
1417 Spec: map[string]interface{}{
1418 "field1": "value",
1419 },
1420 },
1421 },
1422 {
1423 name: "only the legacy field is set",
1424 original: &k8s.Resource{
1425 TypeMeta: v1.TypeMeta{
1426 Kind: fooKind,
1427 },
1428 Spec: map[string]interface{}{
1429 "field1": "value",
1430 "legacyField": "value",
1431 },
1432 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1433 "f:field1": emptyObject,
1434 "f:legacyField": emptyObject,
1435 }),
1436 },
1437 legacyFieldPath: []string{"legacyField"},
1438 fieldPath: []string{"authoritativeField"},
1439 expected: &k8s.Resource{
1440 TypeMeta: v1.TypeMeta{
1441 Kind: fooKind,
1442 },
1443 Spec: map[string]interface{}{
1444 "field1": "value",
1445 "authoritativeField": "value",
1446 },
1447 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1448 "f:field1": emptyObject,
1449 "f:legacyField": emptyObject,
1450 "f:authoritativeField": emptyObject,
1451 }),
1452 },
1453 },
1454 {
1455 name: "only the nested legacy field is set",
1456 original: &k8s.Resource{
1457 TypeMeta: v1.TypeMeta{
1458 Kind: fooKind,
1459 },
1460 Spec: map[string]interface{}{
1461 "field1": "value",
1462 "topField": map[string]interface{}{
1463 "field2": "value",
1464 "legacyField": "value",
1465 },
1466 },
1467 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1468 "f:field1": emptyObject,
1469 "f:topField": map[string]interface{}{
1470 ".": emptyObject,
1471 "f:legacyField": emptyObject,
1472 },
1473 }),
1474 },
1475 legacyFieldPath: []string{"topField", "legacyField"},
1476 fieldPath: []string{"topField", "authoritativeField"},
1477 expected: &k8s.Resource{
1478 TypeMeta: v1.TypeMeta{
1479 Kind: fooKind,
1480 },
1481 Spec: map[string]interface{}{
1482 "field1": "value",
1483 "topField": map[string]interface{}{
1484 "field2": "value",
1485 "authoritativeField": "value",
1486 },
1487 },
1488 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1489 "f:field1": emptyObject,
1490 "f:topField": map[string]interface{}{
1491 ".": emptyObject,
1492 "f:legacyField": emptyObject,
1493 "f:authoritativeField": emptyObject,
1494 },
1495 }),
1496 },
1497 },
1498 {
1499 name: "only the nested legacy field is set and the authoritative field is a reference field",
1500 original: &k8s.Resource{
1501 TypeMeta: v1.TypeMeta{
1502 Kind: fooKind,
1503 },
1504 Spec: map[string]interface{}{
1505 "field1": "value",
1506 "topField": map[string]interface{}{
1507 "field2": "value",
1508 "legacyField": "value",
1509 },
1510 },
1511 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1512 "f:field1": emptyObject,
1513 "f:topField": map[string]interface{}{
1514 ".": emptyObject,
1515 "f:legacyField": emptyObject,
1516 },
1517 }),
1518 },
1519 legacyFieldPath: []string{"topField", "legacyField"},
1520 fieldPath: []string{"topField", "authoritativeFieldRef"},
1521 expected: &k8s.Resource{
1522 TypeMeta: v1.TypeMeta{
1523 Kind: fooKind,
1524 },
1525 Spec: map[string]interface{}{
1526 "field1": "value",
1527 "topField": map[string]interface{}{
1528 "field2": "value",
1529 "authoritativeFieldRef": map[string]interface{}{
1530 "external": "value",
1531 },
1532 },
1533 },
1534 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1535 "f:field1": emptyObject,
1536 "f:topField": map[string]interface{}{
1537 ".": emptyObject,
1538 "f:legacyField": emptyObject,
1539 "f:authoritativeFieldRef": map[string]interface{}{
1540 ".": emptyObject,
1541 "f:external": emptyObject,
1542 },
1543 },
1544 }),
1545 },
1546 },
1547 {
1548 name: "only the authoritative field is set",
1549 original: &k8s.Resource{
1550 TypeMeta: v1.TypeMeta{
1551 Kind: fooKind,
1552 },
1553 Spec: map[string]interface{}{
1554 "field1": "value",
1555 "authoritativeField": "value",
1556 },
1557 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1558 "f:field1": emptyObject,
1559 "f:authoritativeField": emptyObject,
1560 }),
1561 },
1562 legacyFieldPath: []string{"legacyField"},
1563 fieldPath: []string{"authoritativeField"},
1564 expected: &k8s.Resource{
1565 TypeMeta: v1.TypeMeta{
1566 Kind: fooKind,
1567 },
1568 Spec: map[string]interface{}{
1569 "field1": "value",
1570 "authoritativeField": "value",
1571 },
1572 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1573 "f:field1": emptyObject,
1574 "f:authoritativeField": emptyObject,
1575 }),
1576 },
1577 },
1578 {
1579 name: "only the nested authoritative field is set",
1580 original: &k8s.Resource{
1581 TypeMeta: v1.TypeMeta{
1582 Kind: fooKind,
1583 },
1584 Spec: map[string]interface{}{
1585 "field1": "value",
1586 "topField": map[string]interface{}{
1587 "field2": "value",
1588 "authoritativeField": "value",
1589 },
1590 },
1591 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1592 "f:field1": emptyObject,
1593 "f:topField": map[string]interface{}{
1594 ".": emptyObject,
1595 "f:authoritativeField": emptyObject,
1596 },
1597 }),
1598 },
1599 legacyFieldPath: []string{"topField", "legacyField"},
1600 fieldPath: []string{"topField", "authoritativeField"},
1601 expected: &k8s.Resource{
1602 TypeMeta: v1.TypeMeta{
1603 Kind: fooKind,
1604 },
1605 Spec: map[string]interface{}{
1606 "field1": "value",
1607 "topField": map[string]interface{}{
1608 "field2": "value",
1609 "authoritativeField": "value",
1610 },
1611 },
1612 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1613 "f:field1": emptyObject,
1614 "f:topField": map[string]interface{}{
1615 ".": emptyObject,
1616 "f:authoritativeField": emptyObject,
1617 },
1618 }),
1619 },
1620 },
1621 {
1622 name: "both the legacy field and the authoritative field are set with the same value",
1623 original: &k8s.Resource{
1624 TypeMeta: v1.TypeMeta{
1625 Kind: fooKind,
1626 },
1627 Spec: map[string]interface{}{
1628 "field1": "value",
1629 "legacyField": "value1",
1630 "authoritativeField": "value1",
1631 },
1632 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1633 "f:field1": emptyObject,
1634 "f:legacyField": emptyObject,
1635 "f:authoritativeField": emptyObject,
1636 }),
1637 },
1638 legacyFieldPath: []string{"legacyField"},
1639 fieldPath: []string{"authoritativeField"},
1640 expected: &k8s.Resource{
1641 TypeMeta: v1.TypeMeta{
1642 Kind: fooKind,
1643 },
1644 Spec: map[string]interface{}{
1645 "field1": "value",
1646 "authoritativeField": "value1",
1647 },
1648 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1649 "f:field1": emptyObject,
1650 "f:legacyField": emptyObject,
1651 "f:authoritativeField": emptyObject,
1652 }),
1653 },
1654 },
1655 {
1656 name: "both the legacy field and the authoritative field are set with the different values",
1657 original: &k8s.Resource{
1658 TypeMeta: v1.TypeMeta{
1659 Kind: fooKind,
1660 },
1661 Spec: map[string]interface{}{
1662 "field1": "value",
1663 "legacyField": "value1",
1664 "authoritativeField": "value2",
1665 },
1666 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1667 "f:field1": emptyObject,
1668 "f:legacyField": emptyObject,
1669 "f:authoritativeField": emptyObject,
1670 }),
1671 },
1672 legacyFieldPath: []string{"legacyField"},
1673 fieldPath: []string{"authoritativeField"},
1674 hasError: true,
1675 },
1676 {
1677 name: "both the nested legacy field and the nested authoritative field are set with the same value",
1678 original: &k8s.Resource{
1679 TypeMeta: v1.TypeMeta{
1680 Kind: fooKind,
1681 },
1682 Spec: map[string]interface{}{
1683 "field1": "value",
1684 "topField": map[string]interface{}{
1685 "legacyField": "value1",
1686 "authoritativeField": "value1",
1687 },
1688 },
1689 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1690 "f:field1": emptyObject,
1691 "f:topField": map[string]interface{}{
1692 ".": emptyObject,
1693 "f:authoritativeField": emptyObject,
1694 "f:legacyField": emptyObject,
1695 },
1696 }),
1697 },
1698 legacyFieldPath: []string{"topField", "legacyField"},
1699 fieldPath: []string{"topField", "authoritativeField"},
1700 expected: &k8s.Resource{
1701 TypeMeta: v1.TypeMeta{
1702 Kind: fooKind,
1703 },
1704 Spec: map[string]interface{}{
1705 "field1": "value",
1706 "topField": map[string]interface{}{
1707 "authoritativeField": "value1",
1708 },
1709 },
1710 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1711 "f:field1": emptyObject,
1712 "f:topField": map[string]interface{}{
1713 ".": emptyObject,
1714 "f:authoritativeField": emptyObject,
1715 "f:legacyField": emptyObject,
1716 },
1717 }),
1718 },
1719 },
1720 {
1721 name: "both the nested legacy field and the nested authoritative field are set with different values",
1722 original: &k8s.Resource{
1723 TypeMeta: v1.TypeMeta{
1724 Kind: fooKind,
1725 },
1726 Spec: map[string]interface{}{
1727 "field1": "value",
1728 "topField": map[string]interface{}{
1729 "legacyField": "value1",
1730 "authoritativeField": "value2",
1731 },
1732 },
1733 ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1734 "f:field1": emptyObject,
1735 "f:topField": map[string]interface{}{
1736 ".": emptyObject,
1737 "f:authoritativeField": emptyObject,
1738 "f:legacyField": emptyObject,
1739 },
1740 }),
1741 },
1742 legacyFieldPath: []string{"topField", "legacyField"},
1743 fieldPath: []string{"topField", "authoritativeField"},
1744 hasError: true,
1745 },
1746 {
1747 name: "the nested legacy field and the nested authoritative field are structurally different",
1748 original: &k8s.Resource{
1749 TypeMeta: v1.TypeMeta{
1750 Kind: fooKind,
1751 },
1752 Spec: map[string]interface{}{
1753 "field1": "value",
1754 "topField": map[string]interface{}{
1755 "legacyField": "value1",
1756 "authoritativeField": map[string]interface{}{
1757 "subfield": "value1",
1758 },
1759 },
1760 },
1761 },
1762 legacyFieldPath: []string{"topField", "legacyField"},
1763 fieldPath: []string{"topField", "authoritativeField"},
1764 hasError: true,
1765 },
1766 {
1767 name: "the nested authoritative field has a slice somewhere in the path",
1768 original: &k8s.Resource{
1769 TypeMeta: v1.TypeMeta{
1770 Kind: fooKind,
1771 },
1772 Spec: map[string]interface{}{
1773 "field1": "value",
1774 "legacyField": "value1",
1775 "topField": []interface{}{
1776 map[string]interface{}{
1777 "authoritativeField": "value2",
1778 },
1779 },
1780 },
1781 },
1782 legacyFieldPath: []string{"legacyField"},
1783 fieldPath: []string{"topField", "authoritativeField"},
1784 hasError: true,
1785 },
1786 {
1787 name: "the nested legacy field has a slice somewhere in the path",
1788 original: &k8s.Resource{
1789 TypeMeta: v1.TypeMeta{
1790 Kind: fooKind,
1791 },
1792 Spec: map[string]interface{}{
1793 "field1": "value",
1794 "authoritativeField": "value1",
1795 "topField": []interface{}{
1796 map[string]interface{}{
1797 "legacyField": "value2",
1798 },
1799 },
1800 },
1801 },
1802 legacyFieldPath: []string{"topField", "legacyField"},
1803 fieldPath: []string{"authoritativeField"},
1804 hasError: true,
1805 },
1806 }
1807
1808 for _, tc := range tests {
1809 t.Run(tc.name, func(t *testing.T) {
1810 err := FavorAuthoritativeFieldOverLegacyField(tc.original, tc.legacyFieldPath, tc.fieldPath)
1811 if tc.hasError {
1812 if err == nil {
1813 t.Fatalf("got nil, but expect to have error")
1814 }
1815 return
1816 }
1817 if err != nil {
1818 t.Fatalf("unexpected error: %v", err)
1819 }
1820
1821
1822 if tc.original.ManagedFields != nil && !tc.original.ManagedFields.Equals(tc.expected.ManagedFields) {
1823 t.Fatalf("got %v, want %v", tc.original.ManagedFields, tc.expected.ManagedFields)
1824 }
1825 tc.original.ManagedFields = nil
1826 tc.expected.ManagedFields = nil
1827 if !reflect.DeepEqual(tc.original, tc.expected) {
1828 t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.original))
1829 }
1830 })
1831 }
1832 }
1833
1834 func TestFavorReferenceFieldOverNonReferenceFieldUnderSlice(t *testing.T) {
1835 t.Parallel()
1836 tests := []struct {
1837 name string
1838 original *k8s.Resource
1839 pathUpToSlice []string
1840 nonReferenceFieldPath []string
1841 referenceFieldPath []string
1842 expected *k8s.Resource
1843 hasError bool
1844 }{
1845 {
1846 name: "neither the legacy field nor the authoritative field is set without path up to slice",
1847 original: &k8s.Resource{
1848 TypeMeta: v1.TypeMeta{
1849 Kind: fooKind,
1850 },
1851 Spec: map[string]interface{}{
1852 "field1": "value",
1853 },
1854 },
1855 pathUpToSlice: []string{"pathUpToSlice"},
1856 nonReferenceFieldPath: []string{"legacyField"},
1857 referenceFieldPath: []string{"authoritativeField"},
1858 expected: &k8s.Resource{
1859 TypeMeta: v1.TypeMeta{
1860 Kind: fooKind,
1861 },
1862 Spec: map[string]interface{}{
1863 "field1": "value",
1864 },
1865 },
1866 },
1867 {
1868 name: "neither the legacy field nor the authoritative field is set with path up to slice",
1869 original: &k8s.Resource{
1870 TypeMeta: v1.TypeMeta{
1871 Kind: fooKind,
1872 },
1873 Spec: map[string]interface{}{
1874 "field1": "value",
1875 "pathUpToSlice": []interface{}{},
1876 },
1877 },
1878 pathUpToSlice: []string{"pathUpToSlice"},
1879 nonReferenceFieldPath: []string{"legacyField"},
1880 referenceFieldPath: []string{"authoritativeField"},
1881 expected: &k8s.Resource{
1882 TypeMeta: v1.TypeMeta{
1883 Kind: fooKind,
1884 },
1885 Spec: map[string]interface{}{
1886 "field1": "value",
1887 "pathUpToSlice": []interface{}{},
1888 },
1889 },
1890 },
1891 {
1892 name: "only the legacy field is set",
1893 original: &k8s.Resource{
1894 TypeMeta: v1.TypeMeta{
1895 Kind: fooKind,
1896 },
1897 Spec: map[string]interface{}{
1898 "pathUpToSlice": []interface{}{
1899 map[string]interface{}{
1900 "field1": "value",
1901 "legacyField": "value",
1902 },
1903 },
1904 },
1905 },
1906 pathUpToSlice: []string{"pathUpToSlice"},
1907 nonReferenceFieldPath: []string{"legacyField"},
1908 referenceFieldPath: []string{"authoritativeField"},
1909 expected: &k8s.Resource{
1910 TypeMeta: v1.TypeMeta{
1911 Kind: fooKind,
1912 },
1913 Spec: map[string]interface{}{
1914 "pathUpToSlice": []interface{}{
1915 map[string]interface{}{
1916 "field1": "value",
1917 "authoritativeField": map[string]interface{}{
1918 "external": "value",
1919 },
1920 },
1921 },
1922 },
1923 },
1924 },
1925 {
1926 name: "only the authoritative field is set",
1927 original: &k8s.Resource{
1928 TypeMeta: v1.TypeMeta{
1929 Kind: fooKind,
1930 },
1931 Spec: map[string]interface{}{
1932 "field1": "value",
1933 "pathUpToSlice": []interface{}{
1934 map[string]interface{}{
1935 "authoritativeField": "value",
1936 },
1937 },
1938 },
1939 },
1940 pathUpToSlice: []string{"pathUpToSlice"},
1941 nonReferenceFieldPath: []string{"legacyField"},
1942 referenceFieldPath: []string{"authoritativeField"},
1943 expected: &k8s.Resource{
1944 TypeMeta: v1.TypeMeta{
1945 Kind: fooKind,
1946 },
1947 Spec: map[string]interface{}{
1948 "field1": "value",
1949 "pathUpToSlice": []interface{}{
1950 map[string]interface{}{
1951 "authoritativeField": "value",
1952 },
1953 },
1954 },
1955 },
1956 },
1957 {
1958 name: "only the legacy field within the nested slice field is set",
1959 original: &k8s.Resource{
1960 TypeMeta: v1.TypeMeta{
1961 Kind: fooKind,
1962 },
1963 Spec: map[string]interface{}{
1964 "field1": "value",
1965 "topField": map[string]interface{}{
1966 "field2": "value",
1967 "pathUpToSlice": []interface{}{
1968 map[string]interface{}{
1969 "legacyField": "value",
1970 },
1971 },
1972 },
1973 },
1974 },
1975 pathUpToSlice: []string{"topField", "pathUpToSlice"},
1976 nonReferenceFieldPath: []string{"legacyField"},
1977 referenceFieldPath: []string{"authoritativeField"},
1978 expected: &k8s.Resource{
1979 TypeMeta: v1.TypeMeta{
1980 Kind: fooKind,
1981 },
1982 Spec: map[string]interface{}{
1983 "field1": "value",
1984 "topField": map[string]interface{}{
1985 "field2": "value",
1986 "pathUpToSlice": []interface{}{
1987 map[string]interface{}{
1988 "authoritativeField": map[string]interface{}{
1989 "external": "value",
1990 },
1991 },
1992 },
1993 },
1994 },
1995 },
1996 },
1997 {
1998 name: "only the nested legacy field within the slice field is set",
1999 original: &k8s.Resource{
2000 TypeMeta: v1.TypeMeta{
2001 Kind: fooKind,
2002 },
2003 Spec: map[string]interface{}{
2004 "field1": "value",
2005 "pathUpToSlice": []interface{}{
2006 map[string]interface{}{
2007 "topField": map[string]interface{}{
2008 "legacyField": "value",
2009 },
2010 },
2011 },
2012 },
2013 },
2014 pathUpToSlice: []string{"pathUpToSlice"},
2015 nonReferenceFieldPath: []string{"topField", "legacyField"},
2016 referenceFieldPath: []string{"topField", "authoritativeField"},
2017 expected: &k8s.Resource{
2018 TypeMeta: v1.TypeMeta{
2019 Kind: fooKind,
2020 },
2021 Spec: map[string]interface{}{
2022 "field1": "value",
2023 "pathUpToSlice": []interface{}{
2024 map[string]interface{}{
2025 "topField": map[string]interface{}{
2026 "authoritativeField": map[string]interface{}{
2027 "external": "value",
2028 },
2029 },
2030 },
2031 },
2032 },
2033 },
2034 },
2035 {
2036 name: "slice has one authoritative field and one legacy field set on different elements",
2037 original: &k8s.Resource{
2038 TypeMeta: v1.TypeMeta{
2039 Kind: fooKind,
2040 },
2041 Spec: map[string]interface{}{
2042 "field1": "value",
2043 "pathUpToSlice": []interface{}{
2044 map[string]interface{}{
2045 "authoritativeField": map[string]interface{}{
2046 "name": "resourcename",
2047 "kind": "resourcekind",
2048 },
2049 },
2050 map[string]interface{}{
2051 "legacyField": "value1",
2052 },
2053 },
2054 },
2055 },
2056 pathUpToSlice: []string{"pathUpToSlice"},
2057 nonReferenceFieldPath: []string{"legacyField"},
2058 referenceFieldPath: []string{"authoritativeField"},
2059 expected: &k8s.Resource{
2060 TypeMeta: v1.TypeMeta{
2061 Kind: fooKind,
2062 },
2063 Spec: map[string]interface{}{
2064 "field1": "value",
2065 "pathUpToSlice": []interface{}{
2066 map[string]interface{}{
2067 "authoritativeField": map[string]interface{}{
2068 "name": "resourcename",
2069 "kind": "resourcekind",
2070 },
2071 },
2072 map[string]interface{}{
2073 "authoritativeField": map[string]interface{}{
2074 "external": "value1",
2075 },
2076 },
2077 },
2078 },
2079 },
2080 },
2081 {
2082 name: "both legacy field and authoritative field are set",
2083 original: &k8s.Resource{
2084 TypeMeta: v1.TypeMeta{
2085 Kind: fooKind,
2086 },
2087 Spec: map[string]interface{}{
2088 "pathUpToSlice": []interface{}{
2089 map[string]interface{}{
2090 "legacyField": "value1",
2091 "authoritativeField": map[string]interface{}{
2092 "external": "value2",
2093 },
2094 },
2095 },
2096 },
2097 },
2098 pathUpToSlice: []string{"pathUpToSlice"},
2099 nonReferenceFieldPath: []string{"legacyField"},
2100 referenceFieldPath: []string{"authoritativeField"},
2101 hasError: true,
2102 },
2103 }
2104
2105 for _, tc := range tests {
2106 t.Run(tc.name, func(t *testing.T) {
2107 err := FavorReferenceFieldOverNonReferenceFieldUnderSlice(tc.original, tc.pathUpToSlice, tc.nonReferenceFieldPath, tc.referenceFieldPath)
2108 if tc.hasError {
2109 if err == nil {
2110 t.Fatalf("got nil, but expect to have error")
2111 }
2112 return
2113 }
2114 if err != nil {
2115 t.Fatalf("unexpected error: %v", err)
2116 }
2117
2118 if !reflect.DeepEqual(tc.original, tc.expected) {
2119 t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.original))
2120 }
2121 })
2122 }
2123 }
2124
2125 func TestFavorReferenceArrayFieldOverNonReferenceArrayField(t *testing.T) {
2126 t.Parallel()
2127 tests := []struct {
2128 name string
2129 original *k8s.Resource
2130 nonReferenceFieldPath []string
2131 referenceFieldPath []string
2132 expected *k8s.Resource
2133 hasError bool
2134 }{
2135 {
2136 name: "neither the non-reference array field nor the reference " +
2137 "array field is set",
2138 original: &k8s.Resource{
2139 TypeMeta: v1.TypeMeta{
2140 Kind: fooKind,
2141 },
2142 Spec: map[string]interface{}{
2143 "field1": "value",
2144 },
2145 },
2146 nonReferenceFieldPath: []string{"nonReferenceField"},
2147 referenceFieldPath: []string{"referenceField"},
2148 expected: &k8s.Resource{
2149 TypeMeta: v1.TypeMeta{
2150 Kind: fooKind,
2151 },
2152 Spec: map[string]interface{}{
2153 "field1": "value",
2154 },
2155 },
2156 },
2157 {
2158 name: "only the non-reference array field is set",
2159 original: &k8s.Resource{
2160 TypeMeta: v1.TypeMeta{
2161 Kind: fooKind,
2162 },
2163 Spec: map[string]interface{}{
2164 "field1": "value",
2165 "nonReferenceField": []interface{}{
2166 "testValue1",
2167 "testValue2",
2168 },
2169 },
2170 },
2171 nonReferenceFieldPath: []string{"nonReferenceField"},
2172 referenceFieldPath: []string{"referenceField"},
2173 expected: &k8s.Resource{
2174 TypeMeta: v1.TypeMeta{
2175 Kind: fooKind,
2176 },
2177 Spec: map[string]interface{}{
2178 "field1": "value",
2179 "referenceField": []interface{}{
2180 map[string]interface{}{
2181 "external": "testValue1",
2182 },
2183 map[string]interface{}{
2184 "external": "testValue2",
2185 },
2186 },
2187 },
2188 },
2189 },
2190 {
2191 name: "only the nested non-reference array field is set",
2192 original: &k8s.Resource{
2193 TypeMeta: v1.TypeMeta{
2194 Kind: fooKind,
2195 },
2196 Spec: map[string]interface{}{
2197 "field1": "value",
2198 "topField": map[string]interface{}{
2199 "field2": "value",
2200 "nonReferenceField": []interface{}{
2201 "testValue1",
2202 "testValue2",
2203 },
2204 },
2205 },
2206 },
2207 nonReferenceFieldPath: []string{"topField", "nonReferenceField"},
2208 referenceFieldPath: []string{"topField", "referenceField"},
2209 expected: &k8s.Resource{
2210 TypeMeta: v1.TypeMeta{
2211 Kind: fooKind,
2212 },
2213 Spec: map[string]interface{}{
2214 "field1": "value",
2215 "topField": map[string]interface{}{
2216 "field2": "value",
2217 "referenceField": []interface{}{
2218 map[string]interface{}{
2219 "external": "testValue1",
2220 },
2221 map[string]interface{}{
2222 "external": "testValue2",
2223 },
2224 },
2225 },
2226 },
2227 },
2228 },
2229 {
2230 name: "only the reference array field is set",
2231 original: &k8s.Resource{
2232 TypeMeta: v1.TypeMeta{
2233 Kind: fooKind,
2234 },
2235 Spec: map[string]interface{}{
2236 "field1": "value",
2237 "referenceField": []interface{}{
2238 map[string]interface{}{
2239 "name": "reference1",
2240 },
2241 map[string]interface{}{
2242 "external": "reference2",
2243 },
2244 },
2245 },
2246 },
2247 nonReferenceFieldPath: []string{"nonReferenceField"},
2248 referenceFieldPath: []string{"referenceField"},
2249 expected: &k8s.Resource{
2250 TypeMeta: v1.TypeMeta{
2251 Kind: fooKind,
2252 },
2253 Spec: map[string]interface{}{
2254 "field1": "value",
2255 "referenceField": []interface{}{
2256 map[string]interface{}{
2257 "name": "reference1",
2258 },
2259 map[string]interface{}{
2260 "external": "reference2",
2261 },
2262 },
2263 },
2264 },
2265 },
2266 {
2267 name: "only the nested reference array field is set",
2268 original: &k8s.Resource{
2269 TypeMeta: v1.TypeMeta{
2270 Kind: fooKind,
2271 },
2272 Spec: map[string]interface{}{
2273 "field1": "value",
2274 "topField": map[string]interface{}{
2275 "field2": "value",
2276 "referenceField": []interface{}{
2277 map[string]interface{}{
2278 "name": "reference1",
2279 },
2280 map[string]interface{}{
2281 "external": "reference2",
2282 },
2283 },
2284 },
2285 },
2286 },
2287 nonReferenceFieldPath: []string{"topField", "nonReferenceField"},
2288 referenceFieldPath: []string{"topField", "referenceField"},
2289 expected: &k8s.Resource{
2290 TypeMeta: v1.TypeMeta{
2291 Kind: fooKind,
2292 },
2293 Spec: map[string]interface{}{
2294 "field1": "value",
2295 "topField": map[string]interface{}{
2296 "field2": "value",
2297 "referenceField": []interface{}{
2298 map[string]interface{}{
2299 "name": "reference1",
2300 },
2301 map[string]interface{}{
2302 "external": "reference2",
2303 },
2304 },
2305 },
2306 },
2307 },
2308 },
2309 {
2310 name: "both the non-reference array field and the reference array " +
2311 "field are set",
2312 original: &k8s.Resource{
2313 TypeMeta: v1.TypeMeta{
2314 Kind: fooKind,
2315 },
2316 Spec: map[string]interface{}{
2317 "field1": "value",
2318 "nonReferenceField": []interface{}{
2319 "testValue1",
2320 "testValue2",
2321 },
2322 "referenceField": []interface{}{
2323 map[string]interface{}{
2324 "name": "reference1",
2325 },
2326 map[string]interface{}{
2327 "external": "reference2",
2328 },
2329 },
2330 },
2331 },
2332 nonReferenceFieldPath: []string{"nonReferenceField"},
2333 referenceFieldPath: []string{"referenceField"},
2334 hasError: true,
2335 },
2336 {
2337 name: "both the nested non-reference array field and the nested " +
2338 "reference array field are set",
2339 original: &k8s.Resource{
2340 TypeMeta: v1.TypeMeta{
2341 Kind: fooKind,
2342 },
2343 Spec: map[string]interface{}{
2344 "field1": "value",
2345 "topField": map[string]interface{}{
2346 "nonReferenceField": []interface{}{
2347 "testValue1",
2348 "testValue2",
2349 },
2350 "referenceField": []interface{}{
2351 map[string]interface{}{
2352 "name": "reference1",
2353 },
2354 map[string]interface{}{
2355 "external": "reference2",
2356 },
2357 },
2358 },
2359 },
2360 },
2361 nonReferenceFieldPath: []string{"topField", "nonReferenceField"},
2362 referenceFieldPath: []string{"topField", "referenceField"},
2363 hasError: true,
2364 },
2365 {
2366 name: "the non-reference field is not an array",
2367 original: &k8s.Resource{
2368 TypeMeta: v1.TypeMeta{
2369 Kind: fooKind,
2370 },
2371 Spec: map[string]interface{}{
2372 "field1": "value",
2373 "nonReferenceField": "testValue",
2374 },
2375 },
2376 nonReferenceFieldPath: []string{"nonReferenceField"},
2377 referenceFieldPath: []string{"referenceField"},
2378 hasError: true,
2379 },
2380 {
2381 name: "the reference field is not an array",
2382 original: &k8s.Resource{
2383 TypeMeta: v1.TypeMeta{
2384 Kind: fooKind,
2385 },
2386 Spec: map[string]interface{}{
2387 "field1": "value",
2388 "referenceField": "testValue",
2389 },
2390 },
2391 nonReferenceFieldPath: []string{"nonReferenceField"},
2392 referenceFieldPath: []string{"referenceField"},
2393 hasError: true,
2394 },
2395 }
2396
2397 for _, tc := range tests {
2398 t.Run(tc.name, func(t *testing.T) {
2399 err := FavorReferenceArrayFieldOverNonReferenceArrayField(tc.original, tc.nonReferenceFieldPath, tc.referenceFieldPath)
2400 if tc.hasError {
2401 if err == nil {
2402 t.Fatalf("got nil, but expect to have error")
2403 }
2404 return
2405 }
2406 if err != nil {
2407 t.Fatalf("unexpected error: %v", err)
2408 }
2409 if got, want := tc.original, tc.expected; !reflect.DeepEqual(got, want) {
2410 t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(want, got))
2411 }
2412 })
2413 }
2414 }
2415
2416 func TestPruneNoOpsField(t *testing.T) {
2417 t.Parallel()
2418 tests := []struct {
2419 name string
2420 original *k8s.Resource
2421 fieldPath []string
2422 expected *k8s.Resource
2423 }{
2424 {
2425 name: "no-ops fields are not set",
2426 original: &k8s.Resource{
2427 TypeMeta: v1.TypeMeta{
2428 Kind: fooKind,
2429 },
2430 Spec: map[string]interface{}{
2431 "field1": "value",
2432 },
2433 },
2434 fieldPath: []string{"noops"},
2435 expected: &k8s.Resource{
2436 TypeMeta: v1.TypeMeta{
2437 Kind: fooKind,
2438 },
2439 Spec: map[string]interface{}{
2440 "field1": "value",
2441 },
2442 },
2443 },
2444 {
2445 name: "prune no-ops fields",
2446 original: &k8s.Resource{
2447 TypeMeta: v1.TypeMeta{
2448 Kind: fooKind,
2449 },
2450 Spec: map[string]interface{}{
2451 "field1": "value",
2452 "noops": "value",
2453 },
2454 },
2455 fieldPath: []string{"noops"},
2456 expected: &k8s.Resource{
2457 TypeMeta: v1.TypeMeta{
2458 Kind: fooKind,
2459 },
2460 Spec: map[string]interface{}{
2461 "field1": "value",
2462 },
2463 },
2464 },
2465 {
2466 name: "prune nested no-ops fields",
2467 original: &k8s.Resource{
2468 TypeMeta: v1.TypeMeta{
2469 Kind: fooKind,
2470 },
2471 Spec: map[string]interface{}{
2472 "field1": "value",
2473 "topField": map[string]interface{}{
2474 "field2": "value",
2475 "noops": "value",
2476 },
2477 },
2478 },
2479 fieldPath: []string{"topField", "noops"},
2480 expected: &k8s.Resource{
2481 TypeMeta: v1.TypeMeta{
2482 Kind: fooKind,
2483 },
2484 Spec: map[string]interface{}{
2485 "field1": "value",
2486 "topField": map[string]interface{}{
2487 "field2": "value",
2488 },
2489 },
2490 },
2491 },
2492 }
2493
2494 for _, tc := range tests {
2495 t.Run(tc.name, func(t *testing.T) {
2496 if err := PruneNoOpsField(tc.original, tc.fieldPath...); err != nil {
2497 t.Fatalf("unexpected error: %v", err)
2498 }
2499 if !reflect.DeepEqual(tc.original, tc.expected) {
2500 t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.original))
2501 }
2502 })
2503 }
2504 }
2505
2506 func TestPreserveMutuallyExclusiveNonRefField(t *testing.T) {
2507 t.Parallel()
2508 crdWithOptionalReferenceField := &apiextensions.CustomResourceDefinition{
2509 Spec: apiextensions.CustomResourceDefinitionSpec{
2510 Versions: []apiextensions.CustomResourceDefinitionVersion{
2511 {
2512 Name: "v1beta1",
2513 Schema: &apiextensions.CustomResourceValidation{
2514 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2515 Properties: map[string]apiextensions.JSONSchemaProps{
2516 "spec": {
2517 Properties: map[string]apiextensions.JSONSchemaProps{
2518 "testRef": {
2519 Properties: map[string]apiextensions.JSONSchemaProps{
2520 "name": {
2521 Description: "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
2522 Type: "string",
2523 },
2524 "namespace": {
2525 Description: "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/",
2526 Type: "string",
2527 },
2528 "kind": {
2529 Description: "Kind of the referent. Allowed values: ReferenceKind",
2530 Type: "string",
2531 },
2532 "external": {
2533 Description: "Test description",
2534 Type: "string",
2535 },
2536 },
2537 OneOf: []apiextensions.JSONSchemaProps{
2538 {
2539 Required: []string{"name", "kind"},
2540 Not: &apiextensions.JSONSchemaProps{
2541 Required: []string{"external"},
2542 },
2543 },
2544 {
2545 Required: []string{"external"},
2546 Not: &apiextensions.JSONSchemaProps{
2547 AnyOf: []apiextensions.JSONSchemaProps{
2548 {Required: []string{"name"}},
2549 {Required: []string{"namespace"}},
2550 {Required: []string{"kind"}},
2551 },
2552 },
2553 },
2554 },
2555 Type: "object",
2556 },
2557 },
2558 Type: "object",
2559 },
2560 },
2561 Type: "object",
2562 },
2563 },
2564 },
2565 },
2566 },
2567 }
2568
2569 tests := []struct {
2570 name string
2571 originalCRD *apiextensions.CustomResourceDefinition
2572 parentPath []string
2573 referenceFieldName string
2574 nonReferenceFieldName string
2575 expectedCRD *apiextensions.CustomResourceDefinition
2576 hasError bool
2577 }{
2578 {
2579 name: "required top-level non-reference field is added when there" +
2580 "is another required field",
2581 originalCRD: &apiextensions.CustomResourceDefinition{
2582 Spec: apiextensions.CustomResourceDefinitionSpec{
2583 Versions: []apiextensions.CustomResourceDefinitionVersion{
2584 {
2585 Name: "v1beta1",
2586 Schema: &apiextensions.CustomResourceValidation{
2587 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2588 Properties: map[string]apiextensions.JSONSchemaProps{
2589 "spec": {
2590 Properties: map[string]apiextensions.JSONSchemaProps{
2591 "topLevelRef": {
2592 Properties: map[string]apiextensions.JSONSchemaProps{
2593 "kind": {},
2594 },
2595 Description: "fake reference schema for testing",
2596 Type: "object",
2597 },
2598 "otherRequired": {Type: "string"},
2599 },
2600 Required: []string{
2601 "topLevelRef",
2602 "otherRequired",
2603 },
2604 Type: "object",
2605 },
2606 },
2607 Type: "object",
2608 },
2609 },
2610 },
2611 },
2612 },
2613 },
2614 referenceFieldName: "topLevelRef",
2615 nonReferenceFieldName: "topLevel",
2616 expectedCRD: &apiextensions.CustomResourceDefinition{
2617 Spec: apiextensions.CustomResourceDefinitionSpec{
2618 Versions: []apiextensions.CustomResourceDefinitionVersion{
2619 {
2620 Name: "v1beta1",
2621 Schema: &apiextensions.CustomResourceValidation{
2622 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2623 Properties: map[string]apiextensions.JSONSchemaProps{
2624 "spec": {
2625 Properties: map[string]apiextensions.JSONSchemaProps{
2626 "topLevelRef": {
2627 Properties: map[string]apiextensions.JSONSchemaProps{
2628 "kind": {},
2629 },
2630 Description: "fake reference schema for testing",
2631 Type: "object",
2632 },
2633 "topLevel": {
2634 Description: "DEPRECATED. Although this field is still available, there is limited support. " +
2635 "We recommend that you use `spec.topLevelRef` instead.",
2636 Type: "string",
2637 },
2638 "otherRequired": {Type: "string"},
2639 },
2640 OneOf: []apiextensions.JSONSchemaProps{
2641 {Required: []string{"topLevel"}},
2642 {Required: []string{"topLevelRef"}},
2643 },
2644 Required: []string{
2645 "otherRequired",
2646 },
2647 Type: "object",
2648 },
2649 },
2650 Type: "object",
2651 },
2652 },
2653 },
2654 },
2655 },
2656 },
2657 },
2658 {
2659 name: "optional top-level non-reference field is added",
2660 originalCRD: &apiextensions.CustomResourceDefinition{
2661 Spec: apiextensions.CustomResourceDefinitionSpec{
2662 Versions: []apiextensions.CustomResourceDefinitionVersion{
2663 {
2664 Name: "v1beta1",
2665 Schema: &apiextensions.CustomResourceValidation{
2666 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2667 Properties: map[string]apiextensions.JSONSchemaProps{
2668 "spec": {
2669 Properties: map[string]apiextensions.JSONSchemaProps{
2670 "topLevelRef": {
2671 Properties: map[string]apiextensions.JSONSchemaProps{
2672 "kind": {},
2673 },
2674 Description: "fake reference schema for testing",
2675 Type: "object",
2676 },
2677 },
2678 Type: "object",
2679 },
2680 },
2681 Type: "object",
2682 },
2683 },
2684 },
2685 },
2686 },
2687 },
2688 referenceFieldName: "topLevelRef",
2689 nonReferenceFieldName: "topLevel",
2690 expectedCRD: &apiextensions.CustomResourceDefinition{
2691 Spec: apiextensions.CustomResourceDefinitionSpec{
2692 Versions: []apiextensions.CustomResourceDefinitionVersion{
2693 {
2694 Name: "v1beta1",
2695 Schema: &apiextensions.CustomResourceValidation{
2696 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2697 Properties: map[string]apiextensions.JSONSchemaProps{
2698 "spec": {
2699 Properties: map[string]apiextensions.JSONSchemaProps{
2700 "topLevelRef": {
2701 Properties: map[string]apiextensions.JSONSchemaProps{
2702 "kind": {},
2703 },
2704 Description: "fake reference schema for testing",
2705 Type: "object",
2706 },
2707 "topLevel": {
2708 Description: "DEPRECATED. Although this field is still available, there is limited support. " +
2709 "We recommend that you use `spec.topLevelRef` instead.",
2710 Type: "string",
2711 },
2712 },
2713 Not: &apiextensions.JSONSchemaProps{
2714 Required: []string{"topLevel", "topLevelRef"},
2715 },
2716 Type: "object",
2717 },
2718 },
2719 Type: "object",
2720 },
2721 },
2722 },
2723 },
2724 },
2725 },
2726 },
2727 {
2728 name: "required nested non-reference array field is added",
2729 originalCRD: &apiextensions.CustomResourceDefinition{
2730 Spec: apiextensions.CustomResourceDefinitionSpec{
2731 Versions: []apiextensions.CustomResourceDefinitionVersion{
2732 {
2733 Name: "v1beta1",
2734 Schema: &apiextensions.CustomResourceValidation{
2735 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2736 Properties: map[string]apiextensions.JSONSchemaProps{
2737 "spec": {
2738 Properties: map[string]apiextensions.JSONSchemaProps{
2739 "topLevelObject": {
2740 Properties: map[string]apiextensions.JSONSchemaProps{
2741 "nestedArrayRefs": {
2742 Items: &apiextensions.JSONSchemaPropsOrArray{
2743 Schema: &apiextensions.JSONSchemaProps{
2744 Properties: map[string]apiextensions.JSONSchemaProps{
2745 "kind": {},
2746 },
2747 Description: "fake reference schema for testing",
2748 Type: "object",
2749 },
2750 },
2751 Type: "array",
2752 },
2753 },
2754 Required: []string{
2755 "nestedArrayRefs",
2756 },
2757 Type: "object",
2758 },
2759 },
2760 Type: "object",
2761 },
2762 },
2763 Type: "object",
2764 },
2765 },
2766 },
2767 },
2768 },
2769 },
2770 parentPath: []string{"topLevelObject"},
2771 referenceFieldName: "nestedArrayRefs",
2772 nonReferenceFieldName: "nestedArray",
2773 expectedCRD: &apiextensions.CustomResourceDefinition{
2774 Spec: apiextensions.CustomResourceDefinitionSpec{
2775 Versions: []apiextensions.CustomResourceDefinitionVersion{
2776 {
2777 Name: "v1beta1",
2778 Schema: &apiextensions.CustomResourceValidation{
2779 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2780 Properties: map[string]apiextensions.JSONSchemaProps{
2781 "spec": {
2782 Properties: map[string]apiextensions.JSONSchemaProps{
2783 "topLevelObject": {
2784 Properties: map[string]apiextensions.JSONSchemaProps{
2785 "nestedArrayRefs": {
2786 Items: &apiextensions.JSONSchemaPropsOrArray{
2787 Schema: &apiextensions.JSONSchemaProps{
2788 Properties: map[string]apiextensions.JSONSchemaProps{
2789 "kind": {},
2790 },
2791 Description: "fake reference schema for testing",
2792 Type: "object",
2793 },
2794 },
2795 Type: "array",
2796 },
2797 "nestedArray": {
2798 Description: "DEPRECATED. Although this field is still available, there is limited support. " +
2799 "We recommend that you use `spec.topLevelObject.nestedArrayRefs` instead.",
2800 Items: &apiextensions.JSONSchemaPropsOrArray{
2801 Schema: &apiextensions.JSONSchemaProps{
2802 Type: "string",
2803 },
2804 },
2805 Type: "array",
2806 },
2807 },
2808 OneOf: []apiextensions.JSONSchemaProps{
2809 {Required: []string{"nestedArray"}},
2810 {Required: []string{"nestedArrayRefs"}},
2811 },
2812 Type: "object",
2813 },
2814 },
2815 Type: "object",
2816 },
2817 },
2818 Type: "object",
2819 },
2820 },
2821 },
2822 },
2823 },
2824 },
2825 },
2826 {
2827 name: "optional nested non-reference field is added when the " +
2828 "parent's parent is an array field",
2829 originalCRD: &apiextensions.CustomResourceDefinition{
2830 Spec: apiextensions.CustomResourceDefinitionSpec{
2831 Versions: []apiextensions.CustomResourceDefinitionVersion{
2832 {
2833 Name: "v1beta1",
2834 Schema: &apiextensions.CustomResourceValidation{
2835 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2836 Properties: map[string]apiextensions.JSONSchemaProps{
2837 "spec": {
2838 Properties: map[string]apiextensions.JSONSchemaProps{
2839 "topLevelArray": {
2840 Items: &apiextensions.JSONSchemaPropsOrArray{
2841 Schema: &apiextensions.JSONSchemaProps{
2842 Properties: map[string]apiextensions.JSONSchemaProps{
2843 "secondLevelObject": {
2844 Properties: map[string]apiextensions.JSONSchemaProps{
2845 "nestedRef": {
2846 Properties: map[string]apiextensions.JSONSchemaProps{
2847 "kind": {},
2848 },
2849 Description: "fake reference schema for testing",
2850 Type: "object",
2851 },
2852 "otherField": {Type: "string"},
2853 },
2854 Required: []string{"otherField"},
2855 Type: "object",
2856 },
2857 "secondLevelString": {Type: "string"},
2858 },
2859 Type: "object",
2860 },
2861 },
2862 Type: "array",
2863 },
2864 },
2865 Type: "object",
2866 },
2867 },
2868 Type: "object",
2869 },
2870 },
2871 },
2872 },
2873 },
2874 },
2875 parentPath: []string{"topLevelArray", "secondLevelObject"},
2876 referenceFieldName: "nestedRef",
2877 nonReferenceFieldName: "nested",
2878 expectedCRD: &apiextensions.CustomResourceDefinition{
2879 Spec: apiextensions.CustomResourceDefinitionSpec{
2880 Versions: []apiextensions.CustomResourceDefinitionVersion{
2881 {
2882 Name: "v1beta1",
2883 Schema: &apiextensions.CustomResourceValidation{
2884 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2885 Properties: map[string]apiextensions.JSONSchemaProps{
2886 "spec": {
2887 Properties: map[string]apiextensions.JSONSchemaProps{
2888 "topLevelArray": {
2889 Items: &apiextensions.JSONSchemaPropsOrArray{
2890 Schema: &apiextensions.JSONSchemaProps{
2891 Properties: map[string]apiextensions.JSONSchemaProps{
2892 "secondLevelObject": {
2893 Properties: map[string]apiextensions.JSONSchemaProps{
2894 "nestedRef": {
2895 Properties: map[string]apiextensions.JSONSchemaProps{
2896 "kind": {},
2897 },
2898 Description: "fake reference schema for testing",
2899 Type: "object",
2900 },
2901 "nested": {
2902 Description: "DEPRECATED. Although this field is still available, there is limited support. " +
2903 "We recommend that you use `spec.topLevelArray.secondLevelObject.nestedRef` instead.",
2904 Type: "string",
2905 },
2906 "otherField": {Type: "string"},
2907 },
2908 Not: &apiextensions.JSONSchemaProps{
2909 Required: []string{"nested", "nestedRef"},
2910 },
2911 Required: []string{"otherField"},
2912 Type: "object",
2913 },
2914 "secondLevelString": {Type: "string"},
2915 },
2916 Type: "object",
2917 },
2918 },
2919 Type: "array",
2920 },
2921 },
2922 Type: "object",
2923 },
2924 },
2925 Type: "object",
2926 },
2927 },
2928 },
2929 },
2930 },
2931 },
2932 },
2933 {
2934 name: "optional nested non-reference array field is added when " +
2935 "the parent is also an array field",
2936 originalCRD: &apiextensions.CustomResourceDefinition{
2937 Spec: apiextensions.CustomResourceDefinitionSpec{
2938 Versions: []apiextensions.CustomResourceDefinitionVersion{
2939 {
2940 Name: "v1beta1",
2941 Schema: &apiextensions.CustomResourceValidation{
2942 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2943 Properties: map[string]apiextensions.JSONSchemaProps{
2944 "spec": {
2945 Properties: map[string]apiextensions.JSONSchemaProps{
2946 "topLevelArray": {
2947 Items: &apiextensions.JSONSchemaPropsOrArray{
2948 Schema: &apiextensions.JSONSchemaProps{
2949 Properties: map[string]apiextensions.JSONSchemaProps{
2950 "nestedArrayRefs": {
2951 Items: &apiextensions.JSONSchemaPropsOrArray{
2952 Schema: &apiextensions.JSONSchemaProps{
2953 Properties: map[string]apiextensions.JSONSchemaProps{
2954 "kind": {},
2955 },
2956 Description: "fake reference schema for testing",
2957 Type: "object",
2958 },
2959 },
2960 Type: "array",
2961 },
2962 },
2963 Type: "object",
2964 },
2965 },
2966 Type: "array",
2967 },
2968 },
2969 Type: "object",
2970 },
2971 },
2972 Type: "object",
2973 },
2974 },
2975 },
2976 },
2977 },
2978 },
2979 parentPath: []string{"topLevelArray"},
2980 referenceFieldName: "nestedArrayRefs",
2981 nonReferenceFieldName: "nestedArray",
2982 expectedCRD: &apiextensions.CustomResourceDefinition{
2983 Spec: apiextensions.CustomResourceDefinitionSpec{
2984 Versions: []apiextensions.CustomResourceDefinitionVersion{
2985 {
2986 Name: "v1beta1",
2987 Schema: &apiextensions.CustomResourceValidation{
2988 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2989 Properties: map[string]apiextensions.JSONSchemaProps{
2990 "spec": {
2991 Properties: map[string]apiextensions.JSONSchemaProps{
2992 "topLevelArray": {
2993 Items: &apiextensions.JSONSchemaPropsOrArray{
2994 Schema: &apiextensions.JSONSchemaProps{
2995 Properties: map[string]apiextensions.JSONSchemaProps{
2996 "nestedArrayRefs": {
2997 Items: &apiextensions.JSONSchemaPropsOrArray{
2998 Schema: &apiextensions.JSONSchemaProps{
2999 Properties: map[string]apiextensions.JSONSchemaProps{
3000 "kind": {},
3001 },
3002 Description: "fake reference schema for testing",
3003 Type: "object",
3004 },
3005 },
3006 Type: "array",
3007 },
3008 "nestedArray": {
3009 Description: "DEPRECATED. Although this field is still available, there is limited support. " +
3010 "We recommend that you use `spec.topLevelArray.nestedArrayRefs` instead.",
3011 Items: &apiextensions.JSONSchemaPropsOrArray{
3012 Schema: &apiextensions.JSONSchemaProps{
3013 Type: "string",
3014 },
3015 },
3016 Type: "array",
3017 },
3018 },
3019 Not: &apiextensions.JSONSchemaProps{
3020 Required: []string{"nestedArray", "nestedArrayRefs"},
3021 },
3022 Type: "object",
3023 },
3024 },
3025 Type: "array",
3026 },
3027 },
3028 Type: "object",
3029 },
3030 },
3031 Type: "object",
3032 },
3033 },
3034 },
3035 },
3036 },
3037 },
3038 },
3039 {
3040 name: "reference with no 'kind' field",
3041 originalCRD: &apiextensions.CustomResourceDefinition{
3042 Spec: apiextensions.CustomResourceDefinitionSpec{
3043 Versions: []apiextensions.CustomResourceDefinitionVersion{
3044 {
3045 Name: "v1beta1",
3046 Schema: &apiextensions.CustomResourceValidation{
3047 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3048 Properties: map[string]apiextensions.JSONSchemaProps{
3049 "spec": {
3050 Properties: map[string]apiextensions.JSONSchemaProps{
3051 "testRef": {
3052 Properties: map[string]apiextensions.JSONSchemaProps{
3053 "external": {Description: "Test description"},
3054 },
3055 Description: "reference schema with no 'kind' field",
3056 Type: "object",
3057 },
3058 },
3059 Required: []string{
3060 "testRef",
3061 },
3062 Type: "object",
3063 },
3064 },
3065 Type: "object",
3066 },
3067 },
3068 },
3069 },
3070 },
3071 },
3072 referenceFieldName: "testRef",
3073 nonReferenceFieldName: "test",
3074 expectedCRD: &apiextensions.CustomResourceDefinition{
3075 Spec: apiextensions.CustomResourceDefinitionSpec{
3076 Versions: []apiextensions.CustomResourceDefinitionVersion{
3077 {
3078 Name: "v1beta1",
3079 Schema: &apiextensions.CustomResourceValidation{
3080 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3081 Properties: map[string]apiextensions.JSONSchemaProps{
3082 "spec": {
3083 Properties: map[string]apiextensions.JSONSchemaProps{
3084 "testRef": {
3085 Properties: map[string]apiextensions.JSONSchemaProps{
3086 "external": {Description: "Test description"},
3087 },
3088 Description: "reference schema with no 'kind' field",
3089 Type: "object",
3090 },
3091 "test": {
3092 Description: "DEPRECATED. Although this field is still available, there is limited support. " +
3093 "We recommend that you use `spec.testRef` instead.",
3094 Type: "string",
3095 },
3096 },
3097 OneOf: []apiextensions.JSONSchemaProps{
3098 {Required: []string{"test"}},
3099 {Required: []string{"testRef"}},
3100 },
3101 Type: "object",
3102 },
3103 },
3104 Type: "object",
3105 },
3106 },
3107 },
3108 },
3109 },
3110 },
3111 },
3112 {
3113 name: "required non-reference field can't be added due to " +
3114 "existing oneOf rule",
3115 originalCRD: &apiextensions.CustomResourceDefinition{
3116 Spec: apiextensions.CustomResourceDefinitionSpec{
3117 Versions: []apiextensions.CustomResourceDefinitionVersion{
3118 {
3119 Name: "v1beta1",
3120 Schema: &apiextensions.CustomResourceValidation{
3121 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3122 Properties: map[string]apiextensions.JSONSchemaProps{
3123 "spec": {
3124 Properties: map[string]apiextensions.JSONSchemaProps{
3125 "testRef": {
3126 Description: "fake reference schema for testing",
3127 Type: "object",
3128 },
3129 },
3130 OneOf: []apiextensions.JSONSchemaProps{
3131 {Required: []string{"randomField"}},
3132 {Required: []string{"randomField2"}},
3133 },
3134 Required: []string{
3135 "testRef",
3136 },
3137 Type: "object",
3138 },
3139 },
3140 Type: "object",
3141 },
3142 },
3143 },
3144 },
3145 },
3146 },
3147 referenceFieldName: "testRef",
3148 nonReferenceFieldName: "test",
3149 hasError: true,
3150 },
3151 {
3152 name: "optional non-reference field can't be added due to " +
3153 "existing not rule",
3154 originalCRD: &apiextensions.CustomResourceDefinition{
3155 Spec: apiextensions.CustomResourceDefinitionSpec{
3156 Versions: []apiextensions.CustomResourceDefinitionVersion{
3157 {
3158 Name: "v1beta1",
3159 Schema: &apiextensions.CustomResourceValidation{
3160 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3161 Properties: map[string]apiextensions.JSONSchemaProps{
3162 "spec": {
3163 Properties: map[string]apiextensions.JSONSchemaProps{
3164 "testRef": {
3165 Description: "fake reference schema for testing",
3166 Type: "object",
3167 },
3168 },
3169 Not: &apiextensions.JSONSchemaProps{
3170 Required: []string{"randomField"},
3171 },
3172 Type: "object",
3173 },
3174 },
3175 Type: "object",
3176 },
3177 },
3178 },
3179 },
3180 },
3181 },
3182 parentPath: []string{},
3183 referenceFieldName: "testRef",
3184 nonReferenceFieldName: "test",
3185 hasError: true,
3186 },
3187 {
3188 name: "non-reference field can't be added due to empty reference " +
3189 "field name",
3190 originalCRD: crdWithOptionalReferenceField,
3191 parentPath: []string{},
3192 referenceFieldName: "",
3193 nonReferenceFieldName: "test",
3194 hasError: true,
3195 },
3196 {
3197 name: "non-reference field can't be added due to empty " +
3198 "non-reference field name",
3199 originalCRD: crdWithOptionalReferenceField,
3200 parentPath: []string{},
3201 referenceFieldName: "testRef",
3202 nonReferenceFieldName: "",
3203 hasError: true,
3204 },
3205 {
3206 name: "non-reference field can't be added due to incorrect " +
3207 "reference field name",
3208 originalCRD: crdWithOptionalReferenceField,
3209 parentPath: []string{},
3210 referenceFieldName: "wrongRef",
3211 nonReferenceFieldName: "wrong",
3212 hasError: true,
3213 },
3214 {
3215 name: "non-reference field can't be added due to incorrect " +
3216 "reference field type",
3217 originalCRD: &apiextensions.CustomResourceDefinition{
3218 Spec: apiextensions.CustomResourceDefinitionSpec{
3219 Versions: []apiextensions.CustomResourceDefinitionVersion{
3220 {
3221 Name: "v1beta1",
3222 Schema: &apiextensions.CustomResourceValidation{
3223 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3224 Properties: map[string]apiextensions.JSONSchemaProps{
3225 "spec": {
3226 Properties: map[string]apiextensions.JSONSchemaProps{
3227 "testRef": {
3228 Description: "reference schema of wrong type",
3229 Type: "string",
3230 },
3231 },
3232 Type: "object",
3233 },
3234 },
3235 Type: "object",
3236 },
3237 },
3238 },
3239 },
3240 },
3241 },
3242 parentPath: []string{},
3243 referenceFieldName: "testRef",
3244 nonReferenceFieldName: "test",
3245 hasError: true,
3246 },
3247 }
3248
3249 for _, tc := range tests {
3250 tc := tc
3251 t.Run(tc.name, func(t *testing.T) {
3252 err := PreserveMutuallyExclusiveNonReferenceField(tc.originalCRD, tc.parentPath, tc.referenceFieldName, tc.nonReferenceFieldName)
3253 if err != nil {
3254 if !tc.hasError {
3255 t.Fatalf("error preserving the mutually exclusive non-reference field: got an error, but want no error: %v", err)
3256 }
3257 return
3258 }
3259 if !reflect.DeepEqual(tc.expectedCRD, tc.originalCRD) {
3260 t.Fatalf("unexpected diff in CRD after supporting the mutually exclusive non-reference field (-want +got): \n%v", cmp.Diff(tc.expectedCRD, tc.originalCRD))
3261 }
3262 })
3263 }
3264 }
3265
3266 func TestEnsureReferenceFieldIsMultiKind(t *testing.T) {
3267 t.Parallel()
3268 crdWithOptionalReferenceField := &apiextensions.CustomResourceDefinition{
3269 Spec: apiextensions.CustomResourceDefinitionSpec{
3270 Versions: []apiextensions.CustomResourceDefinitionVersion{
3271 {
3272 Name: "v1beta1",
3273 Schema: &apiextensions.CustomResourceValidation{
3274 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3275 Properties: map[string]apiextensions.JSONSchemaProps{
3276 "spec": {
3277 Properties: map[string]apiextensions.JSONSchemaProps{
3278 "testRef": {
3279 Properties: map[string]apiextensions.JSONSchemaProps{
3280 "name": {
3281 Description: "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
3282 Type: "string",
3283 },
3284 "namespace": {
3285 Description: "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/",
3286 Type: "string",
3287 },
3288 "kind": {
3289 Description: "Kind of the referent. Allowed values: ReferenceKind",
3290 Type: "string",
3291 },
3292 "external": {
3293 Description: "Test description",
3294 Type: "string",
3295 },
3296 },
3297 OneOf: []apiextensions.JSONSchemaProps{
3298 {
3299 Required: []string{"name", "kind"},
3300 Not: &apiextensions.JSONSchemaProps{
3301 Required: []string{"external"},
3302 },
3303 },
3304 {
3305 Required: []string{"external"},
3306 Not: &apiextensions.JSONSchemaProps{
3307 AnyOf: []apiextensions.JSONSchemaProps{
3308 {Required: []string{"name"}},
3309 {Required: []string{"namespace"}},
3310 {Required: []string{"kind"}},
3311 },
3312 },
3313 },
3314 },
3315 Type: "object",
3316 },
3317 },
3318 Type: "object",
3319 },
3320 },
3321 Type: "object",
3322 },
3323 },
3324 },
3325 },
3326 },
3327 }
3328
3329 tests := []struct {
3330 name string
3331 originalCRD *apiextensions.CustomResourceDefinition
3332 parentPath []string
3333 referenceFieldName string
3334 supportedKinds []string
3335 expectedCRD *apiextensions.CustomResourceDefinition
3336 hasError bool
3337 }{
3338 {
3339 name: "top-level reference field has 'kind' field",
3340 originalCRD: &apiextensions.CustomResourceDefinition{
3341 Spec: apiextensions.CustomResourceDefinitionSpec{
3342 Versions: []apiextensions.CustomResourceDefinitionVersion{
3343 {
3344 Name: "v1beta1",
3345 Schema: &apiextensions.CustomResourceValidation{
3346 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3347 Properties: map[string]apiextensions.JSONSchemaProps{
3348 "spec": {
3349 Properties: map[string]apiextensions.JSONSchemaProps{
3350 "topLevelRef": {
3351 Properties: map[string]apiextensions.JSONSchemaProps{
3352 "kind": {},
3353 },
3354 Description: "fake reference schema for testing",
3355 Type: "object",
3356 },
3357 "topLevel": {
3358 Description: "DEPRECATED. Although this field is still available, there is limited support. " +
3359 "We recommend that you use `spec.topLevelRef` instead.",
3360 Type: "string",
3361 },
3362 "otherRequired": {Type: "string"},
3363 },
3364 OneOf: []apiextensions.JSONSchemaProps{
3365 {Required: []string{"topLevel"}},
3366 {Required: []string{"topLevelRef"}},
3367 },
3368 Required: []string{
3369 "otherRequired",
3370 },
3371 Type: "object",
3372 },
3373 },
3374 Type: "object",
3375 },
3376 },
3377 },
3378 },
3379 },
3380 },
3381 referenceFieldName: "topLevelRef",
3382 expectedCRD: &apiextensions.CustomResourceDefinition{
3383 Spec: apiextensions.CustomResourceDefinitionSpec{
3384 Versions: []apiextensions.CustomResourceDefinitionVersion{
3385 {
3386 Name: "v1beta1",
3387 Schema: &apiextensions.CustomResourceValidation{
3388 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3389 Properties: map[string]apiextensions.JSONSchemaProps{
3390 "spec": {
3391 Properties: map[string]apiextensions.JSONSchemaProps{
3392 "topLevelRef": {
3393 Properties: map[string]apiextensions.JSONSchemaProps{
3394 "kind": {},
3395 },
3396 Description: "fake reference schema for testing",
3397 Type: "object",
3398 },
3399 "topLevel": {
3400 Description: "DEPRECATED. Although this field is still available, there is limited support. " +
3401 "We recommend that you use `spec.topLevelRef` instead.",
3402 Type: "string",
3403 },
3404 "otherRequired": {Type: "string"},
3405 },
3406 OneOf: []apiextensions.JSONSchemaProps{
3407 {Required: []string{"topLevel"}},
3408 {Required: []string{"topLevelRef"}},
3409 },
3410 Required: []string{
3411 "otherRequired",
3412 },
3413 Type: "object",
3414 },
3415 },
3416 Type: "object",
3417 },
3418 },
3419 },
3420 },
3421 },
3422 },
3423 },
3424 {
3425 name: "top-level reference field doesn't have 'kind' field",
3426 originalCRD: &apiextensions.CustomResourceDefinition{
3427 Spec: apiextensions.CustomResourceDefinitionSpec{
3428 Versions: []apiextensions.CustomResourceDefinitionVersion{
3429 {
3430 Name: "v1beta1",
3431 Schema: &apiextensions.CustomResourceValidation{
3432 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3433 Properties: map[string]apiextensions.JSONSchemaProps{
3434 "spec": {
3435 Properties: map[string]apiextensions.JSONSchemaProps{
3436 "topLevelRef": {
3437 Properties: map[string]apiextensions.JSONSchemaProps{
3438 "external": {Description: "Test description"},
3439 },
3440 Description: "reference schema with no 'kind' field",
3441 Type: "object",
3442 },
3443 "topLevel": {
3444 Description: "DEPRECATED. Although this field is still available, there is limited support. " +
3445 "We recommend that you use `spec.topLevelRef` instead.",
3446 Type: "string",
3447 },
3448 },
3449 Not: &apiextensions.JSONSchemaProps{
3450 Required: []string{"topLevel", "topLevelRef"},
3451 },
3452 Type: "object",
3453 },
3454 },
3455 Type: "object",
3456 },
3457 },
3458 },
3459 },
3460 },
3461 },
3462 referenceFieldName: "topLevelRef",
3463 supportedKinds: []string{"ReferenceKind"},
3464 expectedCRD: &apiextensions.CustomResourceDefinition{
3465 Spec: apiextensions.CustomResourceDefinitionSpec{
3466 Versions: []apiextensions.CustomResourceDefinitionVersion{
3467 {
3468 Name: "v1beta1",
3469 Schema: &apiextensions.CustomResourceValidation{
3470 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3471 Properties: map[string]apiextensions.JSONSchemaProps{
3472 "spec": {
3473 Properties: map[string]apiextensions.JSONSchemaProps{
3474 "topLevelRef": {
3475 Properties: map[string]apiextensions.JSONSchemaProps{
3476 "name": {
3477 Description: "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
3478 Type: "string",
3479 },
3480 "namespace": {
3481 Description: "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/",
3482 Type: "string",
3483 },
3484 "kind": {
3485 Description: "Kind of the referent. Allowed values: ReferenceKind",
3486 Type: "string",
3487 },
3488 "external": {
3489 Description: "Test description",
3490 Type: "string",
3491 },
3492 },
3493 OneOf: []apiextensions.JSONSchemaProps{
3494 {
3495 Required: []string{"name", "kind"},
3496 Not: &apiextensions.JSONSchemaProps{
3497 Required: []string{"external"},
3498 },
3499 },
3500 {
3501 Required: []string{"external"},
3502 Not: &apiextensions.JSONSchemaProps{
3503 AnyOf: []apiextensions.JSONSchemaProps{
3504 {Required: []string{"name"}},
3505 {Required: []string{"namespace"}},
3506 {Required: []string{"kind"}},
3507 },
3508 },
3509 },
3510 },
3511 Type: "object",
3512 },
3513 "topLevel": {
3514 Description: "DEPRECATED. Although this field is still available, there is limited support. " +
3515 "We recommend that you use `spec.topLevelRef` instead.",
3516 Type: "string",
3517 },
3518 },
3519 Not: &apiextensions.JSONSchemaProps{
3520 Required: []string{"topLevel", "topLevelRef"},
3521 },
3522 Type: "object",
3523 },
3524 },
3525 Type: "object",
3526 },
3527 },
3528 },
3529 },
3530 },
3531 },
3532 },
3533 {
3534 name: "nested reference array field has 'kind' field",
3535 originalCRD: &apiextensions.CustomResourceDefinition{
3536 Spec: apiextensions.CustomResourceDefinitionSpec{
3537 Versions: []apiextensions.CustomResourceDefinitionVersion{
3538 {
3539 Name: "v1beta1",
3540 Schema: &apiextensions.CustomResourceValidation{
3541 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3542 Properties: map[string]apiextensions.JSONSchemaProps{
3543 "spec": {
3544 Properties: map[string]apiextensions.JSONSchemaProps{
3545 "topLevelObject": {
3546 Properties: map[string]apiextensions.JSONSchemaProps{
3547 "nestedArrayRefs": {
3548 Items: &apiextensions.JSONSchemaPropsOrArray{
3549 Schema: &apiextensions.JSONSchemaProps{
3550 Properties: map[string]apiextensions.JSONSchemaProps{
3551 "kind": {},
3552 },
3553 Description: "fake reference schema for testing",
3554 Type: "object",
3555 },
3556 },
3557 Type: "array",
3558 },
3559 },
3560 Required: []string{
3561 "nestedArrayRefs",
3562 },
3563 Type: "object",
3564 },
3565 },
3566 Type: "object",
3567 },
3568 },
3569 Type: "object",
3570 },
3571 },
3572 },
3573 },
3574 },
3575 },
3576 parentPath: []string{"topLevelObject"},
3577 referenceFieldName: "nestedArrayRefs",
3578 expectedCRD: &apiextensions.CustomResourceDefinition{
3579 Spec: apiextensions.CustomResourceDefinitionSpec{
3580 Versions: []apiextensions.CustomResourceDefinitionVersion{
3581 {
3582 Name: "v1beta1",
3583 Schema: &apiextensions.CustomResourceValidation{
3584 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3585 Properties: map[string]apiextensions.JSONSchemaProps{
3586 "spec": {
3587 Properties: map[string]apiextensions.JSONSchemaProps{
3588 "topLevelObject": {
3589 Properties: map[string]apiextensions.JSONSchemaProps{
3590 "nestedArrayRefs": {
3591 Items: &apiextensions.JSONSchemaPropsOrArray{
3592 Schema: &apiextensions.JSONSchemaProps{
3593 Properties: map[string]apiextensions.JSONSchemaProps{
3594 "kind": {},
3595 },
3596 Description: "fake reference schema for testing",
3597 Type: "object",
3598 },
3599 },
3600 Type: "array",
3601 },
3602 },
3603 Required: []string{
3604 "nestedArrayRefs",
3605 },
3606 Type: "object",
3607 },
3608 },
3609 Type: "object",
3610 },
3611 },
3612 Type: "object",
3613 },
3614 },
3615 },
3616 },
3617 },
3618 },
3619 },
3620 {
3621 name: "nested reference field doesn't have 'kind' field when the " +
3622 "parent's parent is an array field",
3623 originalCRD: &apiextensions.CustomResourceDefinition{
3624 Spec: apiextensions.CustomResourceDefinitionSpec{
3625 Versions: []apiextensions.CustomResourceDefinitionVersion{
3626 {
3627 Name: "v1beta1",
3628 Schema: &apiextensions.CustomResourceValidation{
3629 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3630 Properties: map[string]apiextensions.JSONSchemaProps{
3631 "spec": {
3632 Properties: map[string]apiextensions.JSONSchemaProps{
3633 "topLevelArray": {
3634 Items: &apiextensions.JSONSchemaPropsOrArray{
3635 Schema: &apiextensions.JSONSchemaProps{
3636 Properties: map[string]apiextensions.JSONSchemaProps{
3637 "secondLevelObject": {
3638 Properties: map[string]apiextensions.JSONSchemaProps{
3639 "nestedRef": {
3640 Properties: map[string]apiextensions.JSONSchemaProps{
3641 "external": {Description: "Test description"},
3642 },
3643 Description: "reference schema with no 'kind' field",
3644 Type: "object",
3645 },
3646 "otherField": {Type: "string"},
3647 },
3648 Required: []string{"otherField"},
3649 Type: "object",
3650 },
3651 "secondLevelString": {Type: "string"},
3652 },
3653 Type: "object",
3654 },
3655 },
3656 Type: "array",
3657 },
3658 },
3659 Type: "object",
3660 },
3661 },
3662 Type: "object",
3663 },
3664 },
3665 },
3666 },
3667 },
3668 },
3669 parentPath: []string{"topLevelArray", "secondLevelObject"},
3670 referenceFieldName: "nestedRef",
3671 supportedKinds: []string{"ReferenceKind"},
3672 expectedCRD: &apiextensions.CustomResourceDefinition{
3673 Spec: apiextensions.CustomResourceDefinitionSpec{
3674 Versions: []apiextensions.CustomResourceDefinitionVersion{
3675 {
3676 Name: "v1beta1",
3677 Schema: &apiextensions.CustomResourceValidation{
3678 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3679 Properties: map[string]apiextensions.JSONSchemaProps{
3680 "spec": {
3681 Properties: map[string]apiextensions.JSONSchemaProps{
3682 "topLevelArray": {
3683 Items: &apiextensions.JSONSchemaPropsOrArray{
3684 Schema: &apiextensions.JSONSchemaProps{
3685 Properties: map[string]apiextensions.JSONSchemaProps{
3686 "secondLevelObject": {
3687 Properties: map[string]apiextensions.JSONSchemaProps{
3688 "nestedRef": {
3689 Properties: map[string]apiextensions.JSONSchemaProps{
3690 "name": {
3691 Description: "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
3692 Type: "string",
3693 },
3694 "namespace": {
3695 Description: "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/",
3696 Type: "string",
3697 },
3698 "kind": {
3699 Description: "Kind of the referent. Allowed values: ReferenceKind",
3700 Type: "string",
3701 },
3702 "external": {
3703 Description: "Test description",
3704 Type: "string",
3705 },
3706 },
3707 OneOf: []apiextensions.JSONSchemaProps{
3708 {
3709 Required: []string{"name", "kind"},
3710 Not: &apiextensions.JSONSchemaProps{
3711 Required: []string{"external"},
3712 },
3713 },
3714 {
3715 Required: []string{"external"},
3716 Not: &apiextensions.JSONSchemaProps{
3717 AnyOf: []apiextensions.JSONSchemaProps{
3718 {Required: []string{"name"}},
3719 {Required: []string{"namespace"}},
3720 {Required: []string{"kind"}},
3721 },
3722 },
3723 },
3724 },
3725 Type: "object",
3726 },
3727 "otherField": {Type: "string"},
3728 },
3729 Required: []string{"otherField"},
3730 Type: "object",
3731 },
3732 "secondLevelString": {Type: "string"},
3733 },
3734 Type: "object",
3735 },
3736 },
3737 Type: "array",
3738 },
3739 },
3740 Type: "object",
3741 },
3742 },
3743 Type: "object",
3744 },
3745 },
3746 },
3747 },
3748 },
3749 },
3750 },
3751 {
3752 name: "nested reference array field doesn't have 'kind' field " +
3753 "when the parent is also an array field",
3754 originalCRD: &apiextensions.CustomResourceDefinition{
3755 Spec: apiextensions.CustomResourceDefinitionSpec{
3756 Versions: []apiextensions.CustomResourceDefinitionVersion{
3757 {
3758 Name: "v1beta1",
3759 Schema: &apiextensions.CustomResourceValidation{
3760 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3761 Properties: map[string]apiextensions.JSONSchemaProps{
3762 "spec": {
3763 Properties: map[string]apiextensions.JSONSchemaProps{
3764 "topLevelArray": {
3765 Items: &apiextensions.JSONSchemaPropsOrArray{
3766 Schema: &apiextensions.JSONSchemaProps{
3767 Properties: map[string]apiextensions.JSONSchemaProps{
3768 "nestedArrayRefs": {
3769 Items: &apiextensions.JSONSchemaPropsOrArray{
3770 Schema: &apiextensions.JSONSchemaProps{
3771 Properties: map[string]apiextensions.JSONSchemaProps{
3772 "external": {Description: "Test description"},
3773 },
3774 Description: "reference schema with no 'kind' field",
3775 Type: "object",
3776 },
3777 },
3778 Type: "array",
3779 },
3780 },
3781 Type: "object",
3782 },
3783 },
3784 Type: "array",
3785 },
3786 },
3787 Type: "object",
3788 },
3789 },
3790 Type: "object",
3791 },
3792 },
3793 },
3794 },
3795 },
3796 },
3797 parentPath: []string{"topLevelArray"},
3798 referenceFieldName: "nestedArrayRefs",
3799 supportedKinds: []string{"ReferenceKind"},
3800 expectedCRD: &apiextensions.CustomResourceDefinition{
3801 Spec: apiextensions.CustomResourceDefinitionSpec{
3802 Versions: []apiextensions.CustomResourceDefinitionVersion{
3803 {
3804 Name: "v1beta1",
3805 Schema: &apiextensions.CustomResourceValidation{
3806 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3807 Properties: map[string]apiextensions.JSONSchemaProps{
3808 "spec": {
3809 Properties: map[string]apiextensions.JSONSchemaProps{
3810 "topLevelArray": {
3811 Items: &apiextensions.JSONSchemaPropsOrArray{
3812 Schema: &apiextensions.JSONSchemaProps{
3813 Properties: map[string]apiextensions.JSONSchemaProps{
3814 "nestedArrayRefs": {
3815 Items: &apiextensions.JSONSchemaPropsOrArray{
3816 Schema: &apiextensions.JSONSchemaProps{
3817 Properties: map[string]apiextensions.JSONSchemaProps{
3818 "name": {
3819 Description: "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
3820 Type: "string",
3821 },
3822 "namespace": {
3823 Description: "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/",
3824 Type: "string",
3825 },
3826 "kind": {
3827 Description: "Kind of the referent. Allowed values: ReferenceKind",
3828 Type: "string",
3829 },
3830 "external": {
3831 Description: "Test description",
3832 Type: "string",
3833 },
3834 },
3835 OneOf: []apiextensions.JSONSchemaProps{
3836 {
3837 Required: []string{"name", "kind"},
3838 Not: &apiextensions.JSONSchemaProps{
3839 Required: []string{"external"},
3840 },
3841 },
3842 {
3843 Required: []string{"external"},
3844 Not: &apiextensions.JSONSchemaProps{
3845 AnyOf: []apiextensions.JSONSchemaProps{
3846 {Required: []string{"name"}},
3847 {Required: []string{"namespace"}},
3848 {Required: []string{"kind"}},
3849 },
3850 },
3851 },
3852 },
3853 Type: "object",
3854 },
3855 },
3856 Type: "array",
3857 },
3858 },
3859 Type: "object",
3860 },
3861 },
3862 Type: "array",
3863 },
3864 },
3865 Type: "object",
3866 },
3867 },
3868 Type: "object",
3869 },
3870 },
3871 },
3872 },
3873 },
3874 },
3875 },
3876 {
3877 name: "error to empty reference field name",
3878 originalCRD: crdWithOptionalReferenceField,
3879 parentPath: []string{},
3880 referenceFieldName: "",
3881 hasError: true,
3882 },
3883 {
3884 name: "error due to incorrect reference field name",
3885 originalCRD: crdWithOptionalReferenceField,
3886 parentPath: []string{},
3887 referenceFieldName: "wrongRef",
3888 hasError: true,
3889 },
3890 {
3891 name: "error due to incorrect reference field type",
3892 originalCRD: &apiextensions.CustomResourceDefinition{
3893 Spec: apiextensions.CustomResourceDefinitionSpec{
3894 Versions: []apiextensions.CustomResourceDefinitionVersion{
3895 {
3896 Name: "v1beta1",
3897 Schema: &apiextensions.CustomResourceValidation{
3898 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3899 Properties: map[string]apiextensions.JSONSchemaProps{
3900 "spec": {
3901 Properties: map[string]apiextensions.JSONSchemaProps{
3902 "testRef": {
3903 Description: "reference schema of wrong type",
3904 Type: "string",
3905 },
3906 },
3907 Type: "object",
3908 },
3909 },
3910 Type: "object",
3911 },
3912 },
3913 },
3914 },
3915 },
3916 },
3917 parentPath: []string{},
3918 referenceFieldName: "testRef",
3919 hasError: true,
3920 },
3921 {
3922 name: "non-reference field can't be added because the 'external' " +
3923 "field is missing when the 'kind' field doesn't exist",
3924 originalCRD: &apiextensions.CustomResourceDefinition{
3925 Spec: apiextensions.CustomResourceDefinitionSpec{
3926 Versions: []apiextensions.CustomResourceDefinitionVersion{
3927 {
3928 Name: "v1beta1",
3929 Schema: &apiextensions.CustomResourceValidation{
3930 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3931 Properties: map[string]apiextensions.JSONSchemaProps{
3932 "spec": {
3933 Properties: map[string]apiextensions.JSONSchemaProps{
3934 "testRef": {
3935 Description: "reference schema with no 'kind' and 'external' fields",
3936 Type: "object",
3937 },
3938 },
3939 Type: "object",
3940 },
3941 },
3942 Type: "object",
3943 },
3944 },
3945 },
3946 },
3947 },
3948 },
3949 parentPath: []string{},
3950 referenceFieldName: "testRef",
3951 hasError: true,
3952 },
3953 {
3954 name: "non-reference field can't be added because supportedKinds " +
3955 "param is unset when the 'kind' field doesn't exist",
3956 originalCRD: &apiextensions.CustomResourceDefinition{
3957 Spec: apiextensions.CustomResourceDefinitionSpec{
3958 Versions: []apiextensions.CustomResourceDefinitionVersion{
3959 {
3960 Name: "v1beta1",
3961 Schema: &apiextensions.CustomResourceValidation{
3962 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3963 Properties: map[string]apiextensions.JSONSchemaProps{
3964 "spec": {
3965 Properties: map[string]apiextensions.JSONSchemaProps{
3966 "testRef": {
3967 Properties: map[string]apiextensions.JSONSchemaProps{
3968 "external": {Description: "Test description"},
3969 },
3970 Description: "reference schema with no 'kind' field",
3971 Type: "object",
3972 },
3973 },
3974 Type: "object",
3975 },
3976 },
3977 Type: "object",
3978 },
3979 },
3980 },
3981 },
3982 },
3983 },
3984 parentPath: []string{},
3985 referenceFieldName: "testRef",
3986 hasError: true,
3987 },
3988 }
3989
3990 for _, tc := range tests {
3991 tc := tc
3992 t.Run(tc.name, func(t *testing.T) {
3993 err := EnsureReferenceFieldIsMultiKind(tc.originalCRD, tc.parentPath, tc.referenceFieldName, tc.supportedKinds)
3994 if err != nil {
3995 if !tc.hasError {
3996 t.Fatalf("error ensuring the reference field supports multi-kind: got an error, but want no error: %v", err)
3997 }
3998 return
3999 }
4000 if !reflect.DeepEqual(tc.expectedCRD, tc.originalCRD) {
4001 t.Fatalf("unexpected diff in CRD after ensuring the reference field supports multi-kind (-want +got): \n%v", cmp.Diff(tc.expectedCRD, tc.originalCRD))
4002 }
4003 })
4004 }
4005 }
4006
4007 func TestRemovePrefixFromStringFieldInSpec(t *testing.T) {
4008 t.Parallel()
4009 prefixToRemove := "prefix/"
4010 tests := []struct {
4011 name string
4012 original *k8s.Resource
4013 fieldPath []string
4014 expected *k8s.Resource
4015 expectErr bool
4016 }{
4017 {
4018 name: "field exists, is string, has matching prefix",
4019 original: &k8s.Resource{
4020 TypeMeta: v1.TypeMeta{
4021 Kind: fooKind,
4022 },
4023 Spec: map[string]interface{}{
4024 "field1": prefixToRemove + "value1",
4025 "field2": prefixToRemove + "value2",
4026 },
4027 },
4028 fieldPath: []string{"field1"},
4029 expected: &k8s.Resource{
4030 TypeMeta: v1.TypeMeta{
4031 Kind: fooKind,
4032 },
4033 Spec: map[string]interface{}{
4034 "field1": "value1",
4035 "field2": prefixToRemove + "value2",
4036 },
4037 },
4038 },
4039 {
4040 name: "nested field exists, is string, has matching prefix",
4041 original: &k8s.Resource{
4042 TypeMeta: v1.TypeMeta{
4043 Kind: fooKind,
4044 },
4045 Spec: map[string]interface{}{
4046 "field": prefixToRemove + "value",
4047 "topField": map[string]interface{}{
4048 "field": prefixToRemove + "value",
4049 },
4050 },
4051 },
4052 fieldPath: []string{"topField", "field"},
4053 expected: &k8s.Resource{
4054 TypeMeta: v1.TypeMeta{
4055 Kind: fooKind,
4056 },
4057 Spec: map[string]interface{}{
4058 "field": prefixToRemove + "value",
4059 "topField": map[string]interface{}{
4060 "field": "value",
4061 },
4062 },
4063 },
4064 },
4065 {
4066 name: "field does not exist",
4067 original: &k8s.Resource{
4068 TypeMeta: v1.TypeMeta{
4069 Kind: fooKind,
4070 },
4071 Spec: map[string]interface{}{
4072 "field1": "value1",
4073 },
4074 },
4075 fieldPath: []string{"field2"},
4076 expected: &k8s.Resource{
4077 TypeMeta: v1.TypeMeta{
4078 Kind: fooKind,
4079 },
4080 Spec: map[string]interface{}{
4081 "field1": "value1",
4082 },
4083 },
4084 },
4085 {
4086 name: "field exits, is not a string",
4087 original: &k8s.Resource{
4088 TypeMeta: v1.TypeMeta{
4089 Kind: fooKind,
4090 },
4091 Spec: map[string]interface{}{
4092 "field1": true,
4093 },
4094 },
4095 fieldPath: []string{"field1"},
4096 expectErr: true,
4097 },
4098 {
4099 name: "field exists, prefix does not match",
4100 original: &k8s.Resource{
4101 TypeMeta: v1.TypeMeta{
4102 Kind: fooKind,
4103 },
4104 Spec: map[string]interface{}{
4105 "field1": "prefix1/value1",
4106 },
4107 },
4108 fieldPath: []string{"field1"},
4109 expected: &k8s.Resource{
4110 TypeMeta: v1.TypeMeta{
4111 Kind: fooKind,
4112 },
4113 Spec: map[string]interface{}{
4114 "field1": "prefix1/value1",
4115 },
4116 },
4117 },
4118 }
4119
4120 for _, tc := range tests {
4121 t.Run(tc.name, func(t *testing.T) {
4122 err := RemovePrefixFromStringFieldInSpec(tc.original, prefixToRemove, tc.fieldPath...)
4123 if tc.expectErr {
4124 if err == nil {
4125 t.Fatalf("got nil, but expect to have error")
4126 }
4127 return
4128 }
4129 if err != nil {
4130 t.Fatalf("unexpected error: %v", err)
4131 }
4132 if !reflect.DeepEqual(tc.original, tc.expected) {
4133 t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.original))
4134 }
4135 })
4136 }
4137 }
4138
View as plain text