1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package krmtotf_test
16
17 import (
18 "reflect"
19 "testing"
20
21 corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
22 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
23 . "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/krmtotf"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test"
25 testk8s "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/k8s"
26
27 tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
28 "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
29 k8sschema "k8s.io/apimachinery/pkg/runtime/schema"
30 "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
31 )
32
33 func TestConvertTFObjToKCCObj(t *testing.T) {
34 tests := []struct {
35 name string
36 rc *corekccv1alpha1.ResourceConfig
37 state map[string]interface{}
38 schemaOverride map[string]*tfschema.Schema
39 prevSpec map[string]interface{}
40 managedFields *fieldpath.Set
41 expected map[string]interface{}
42 }{
43 {
44 name: "defaulted non-zero primitive values are set",
45 state: map[string]interface{}{
46 "int_key": float64(1),
47 "float_key": float64(0.5),
48 "string_key": "my-string",
49 "bool_key": true,
50 "map_key": map[string]interface{}{
51 "foo": "bar",
52 },
53 "list_of_primitives_key": []interface{}{
54 "element_1",
55 "element_2",
56 },
57 },
58 prevSpec: map[string]interface{}{},
59 expected: map[string]interface{}{
60 "intKey": float64(1),
61 "floatKey": float64(0.5),
62 "stringKey": "my-string",
63 "boolKey": true,
64 "mapKey": map[string]interface{}{
65 "foo": "bar",
66 },
67 "listOfPrimitivesKey": []interface{}{
68 "element_1",
69 "element_2",
70 },
71 },
72 },
73 {
74 name: "defaulted zero-value data structures are pruned",
75 state: map[string]interface{}{
76 "map_key": map[string]interface{}{},
77 "list_of_primitives_key": []interface{}{},
78 "nested_object_key": []interface{}{},
79 "list_of_objects_key": []interface{}{},
80 },
81 prevSpec: nil,
82 expected: nil,
83 },
84
85
86
87 {
88 name: "non-string primitives are not pruned if differ from default value",
89 state: map[string]interface{}{
90 "int_key": float64(0),
91 "float_key": float64(0.0),
92 "string_key": "",
93 "bool_key": false,
94 },
95 prevSpec: nil,
96 expected: map[string]interface{}{
97 "intKey": float64(0),
98 "floatKey": float64(0.0),
99 "boolKey": false,
100 },
101 schemaOverride: map[string]*tfschema.Schema{
102 "int_key": {
103 Type: tfschema.TypeInt,
104 Optional: true,
105 Default: 1,
106 },
107 "float_key": {
108 Type: tfschema.TypeFloat,
109 Optional: true,
110 Default: 1,
111 },
112 "string_key": {
113 Type: tfschema.TypeString,
114 Optional: true,
115 Default: "foo",
116 },
117 "bool_key": {
118 Type: tfschema.TypeBool,
119 Optional: true,
120 Default: true,
121 },
122 },
123 },
124 {
125 name: "lists of objects are set",
126 state: map[string]interface{}{
127 "list_of_objects_key": []interface{}{
128 map[string]interface{}{
129 "nested_int_key": float64(1),
130 },
131 },
132 },
133 prevSpec: map[string]interface{}{},
134 expected: map[string]interface{}{
135 "listOfObjectsKey": []interface{}{
136 map[string]interface{}{
137 "nestedIntKey": float64(1),
138 },
139 },
140 },
141 },
142 {
143 name: "nested objects are converted to maps and set",
144 state: map[string]interface{}{
145 "nested_object_key": []interface{}{
146 map[string]interface{}{
147 "nested_float_key": float64(0.5),
148 },
149 },
150 },
151 prevSpec: map[string]interface{}{},
152 expected: map[string]interface{}{
153 "nestedObjectKey": map[string]interface{}{
154 "nestedFloatKey": float64(0.5),
155 },
156 },
157 },
158 {
159 name: "individual resource references are preserved",
160 state: map[string]interface{}{
161 "reference_key": "ref-val",
162 },
163 prevSpec: map[string]interface{}{
164 "referenceRef": map[string]interface{}{
165 "name": "my-reference",
166 },
167 },
168 expected: map[string]interface{}{
169 "referenceRef": map[string]interface{}{
170 "name": "my-reference",
171 },
172 },
173 },
174 {
175 name: "lists of resource references are preserved",
176 state: map[string]interface{}{
177 "list_of_references_key": []interface{}{
178 "ref1",
179 "ref2",
180 },
181 },
182 prevSpec: map[string]interface{}{
183 "listOfReferencesKey": []interface{}{
184 map[string]interface{}{
185 "name": "my-reference1",
186 },
187 map[string]interface{}{
188 "name": "my-reference2",
189 },
190 },
191 },
192 expected: map[string]interface{}{
193 "listOfReferencesKey": []interface{}{
194 map[string]interface{}{
195 "name": "my-reference1",
196 },
197 map[string]interface{}{
198 "name": "my-reference2",
199 },
200 },
201 },
202 },
203 {
204 name: "resource references nested in lists of objects are preserved",
205 state: map[string]interface{}{
206 "list_of_objects_key": []interface{}{
207 map[string]interface{}{
208 "reference_nested_in_list_of_objects_key": "ref-val1",
209 },
210 map[string]interface{}{
211 "reference_nested_in_list_of_objects_key": "ref-val2",
212 },
213 },
214 },
215 prevSpec: map[string]interface{}{
216 "listOfObjectsKey": []interface{}{
217 map[string]interface{}{
218 "nestedInListOfObjectsRef": map[string]interface{}{
219 "name": "my-reference1",
220 },
221 },
222 map[string]interface{}{
223 "nestedInListOfObjectsRef": map[string]interface{}{
224 "name": "my-reference2",
225 },
226 },
227 },
228 },
229 expected: map[string]interface{}{
230 "listOfObjectsKey": []interface{}{
231 map[string]interface{}{
232 "nestedInListOfObjectsRef": map[string]interface{}{
233 "name": "my-reference1",
234 },
235 },
236 map[string]interface{}{
237 "nestedInListOfObjectsRef": map[string]interface{}{
238 "name": "my-reference2",
239 },
240 },
241 },
242 },
243 },
244 {
245 name: "external resource references are preserved",
246 state: map[string]interface{}{
247 "reference_key": "ref-val",
248 },
249 prevSpec: map[string]interface{}{
250 "referenceRef": map[string]interface{}{
251 "external": "my-reference",
252 },
253 },
254 expected: map[string]interface{}{
255 "referenceRef": map[string]interface{}{
256 "external": "my-reference",
257 },
258 },
259 },
260 {
261 name: "external resource reference set if no reference defined by spec",
262 state: map[string]interface{}{
263 "reference_key": "ref-val",
264 },
265 prevSpec: map[string]interface{}{},
266 expected: map[string]interface{}{
267 "referenceRef": map[string]interface{}{
268 "external": "ref-val",
269 },
270 },
271 },
272 {
273 name: "list of external resource references set if no list defined by spec",
274 state: map[string]interface{}{
275 "list_of_references_key": []interface{}{
276 "ref-val-1",
277 "ref-val-2",
278 },
279 },
280 prevSpec: map[string]interface{}{},
281 expected: map[string]interface{}{
282 "listOfReferencesKey": []interface{}{
283 map[string]interface{}{
284 "external": "ref-val-1",
285 },
286 map[string]interface{}{
287 "external": "ref-val-2",
288 },
289 },
290 },
291 },
292 {
293 name: "set of external resource references with complex key set if no set defined by spec",
294 state: map[string]interface{}{
295 "complex_set_of_references_key": []interface{}{
296 "ref-val-1",
297 "ref-val-2",
298 },
299 },
300 prevSpec: map[string]interface{}{},
301 expected: map[string]interface{}{
302 "complexSetOfReferencesKey": []interface{}{
303 map[string]interface{}{
304 "subKeyRef": map[string]interface{}{
305 "external": "ref-val-1",
306 },
307 },
308 map[string]interface{}{
309 "subKeyRef": map[string]interface{}{
310 "external": "ref-val-2",
311 },
312 },
313 },
314 },
315 },
316 {
317 name: "spec-defined values are preserved",
318 state: map[string]interface{}{
319 "string_key": "fully-expanded-string-value",
320 },
321 prevSpec: map[string]interface{}{
322 "stringKey": "short-string-val",
323 },
324 expected: map[string]interface{}{
325 "stringKey": "short-string-val",
326 },
327 },
328 {
329 name: "primitive set ordering is kept consistent",
330 state: map[string]interface{}{
331 "primitive_set_key": []interface{}{
332 "a",
333 "b",
334 "c",
335 "d",
336 },
337 },
338 prevSpec: map[string]interface{}{
339 "primitiveSetKey": []interface{}{
340 "b",
341 "a",
342 "c",
343 },
344 },
345 expected: map[string]interface{}{
346 "primitiveSetKey": []interface{}{
347 "b",
348 "a",
349 "c",
350 "d",
351 },
352 },
353 },
354 {
355 name: "object set ordering is kept consistent",
356 state: map[string]interface{}{
357 "object_set_key": []interface{}{
358 map[string]interface{}{
359 "index": float64(0),
360 },
361 map[string]interface{}{
362 "index": float64(1),
363 },
364 map[string]interface{}{
365 "index": float64(3),
366 },
367 },
368 },
369 prevSpec: map[string]interface{}{
370 "objectSetKey": []interface{}{
371 map[string]interface{}{
372 "index": float64(1),
373 },
374 map[string]interface{}{
375 "index": float64(0),
376 },
377 },
378 },
379 expected: map[string]interface{}{
380 "objectSetKey": []interface{}{
381 map[string]interface{}{
382 "index": float64(1),
383 },
384 map[string]interface{}{
385 "index": float64(0),
386 },
387 map[string]interface{}{
388 "index": float64(3),
389 },
390 },
391 },
392 },
393 {
394 name: "defaulting is applied to the correct object in the set",
395 state: map[string]interface{}{
396 "object_set_key": []interface{}{
397 map[string]interface{}{
398 "index": float64(0),
399 "nested_bool_key": true,
400 },
401 map[string]interface{}{
402 "index": float64(1),
403 "nested_bool_key": false,
404 },
405 },
406 },
407 prevSpec: map[string]interface{}{
408 "objectSetKey": []interface{}{
409 map[string]interface{}{
410 "index": float64(1),
411 },
412 map[string]interface{}{
413 "index": float64(0),
414 },
415 },
416 },
417 expected: map[string]interface{}{
418 "objectSetKey": []interface{}{
419 map[string]interface{}{
420 "index": float64(1),
421 },
422 map[string]interface{}{
423 "index": float64(0),
424 "nestedBoolKey": true,
425 },
426 },
427 },
428 },
429 {
430 name: "defaulting is applied to correct complex resource reference type",
431 state: map[string]interface{}{
432 "complex_reference_key": "ref-val",
433 },
434 prevSpec: map[string]interface{}{},
435 expected: map[string]interface{}{
436 "complexReferenceKey": map[string]interface{}{
437 "value": "ref-val",
438 },
439 },
440 },
441 {
442 name: "parent values are filtered out of result if resource only supports container annotations",
443 rc: &corekccv1alpha1.ResourceConfig{
444 Containers: []corekccv1alpha1.Container{
445 {
446 Type: corekccv1alpha1.ContainerTypeFolder,
447 TFField: "parent_key",
448 },
449 },
450 },
451 state: map[string]interface{}{
452 "parent_key": "project-id-from-tf-state",
453 "string_key": "string-val",
454 },
455 prevSpec: map[string]interface{}{
456 "stringKey": "string-val",
457 },
458 expected: map[string]interface{}{
459 "stringKey": "string-val",
460 },
461 },
462 {
463 name: "parent values are set as external hierarchical references if resource supports hierarchical references",
464 rc: &corekccv1alpha1.ResourceConfig{
465 Containers: []corekccv1alpha1.Container{
466 {
467 Type: corekccv1alpha1.ContainerTypeProject,
468 TFField: "parent_key",
469 },
470 },
471 HierarchicalReferences: []corekccv1alpha1.HierarchicalReference{
472 {
473 Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
474 Key: "projectRef",
475 },
476 },
477 ResourceReferences: []corekccv1alpha1.ReferenceConfig{
478 {
479 TFField: "parent_key",
480 TypeConfig: corekccv1alpha1.TypeConfig{
481 Key: "projectRef",
482 GVK: k8sschema.GroupVersionKind{
483 Group: "test1.cnrm.cloud.google.com",
484 Version: "v1alpha1",
485 Kind: "Test1Bar",
486 },
487 },
488 },
489 },
490 },
491 state: map[string]interface{}{
492 "parent_key": "project-id-from-tf-state",
493 "string_key": "string-val",
494 },
495 prevSpec: map[string]interface{}{
496 "stringKey": "string-val",
497 },
498 expected: map[string]interface{}{
499 "stringKey": "string-val",
500 "projectRef": map[string]interface{}{
501 "external": "project-id-from-tf-state",
502 },
503 },
504 },
505 {
506 name: "parent values are set as external hierarchical references if resource only supports hierarchical references",
507 rc: &corekccv1alpha1.ResourceConfig{
508 HierarchicalReferences: []corekccv1alpha1.HierarchicalReference{
509 {
510 Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
511 Key: "projectRef",
512 },
513 },
514 ResourceReferences: []corekccv1alpha1.ReferenceConfig{
515 {
516 TFField: "parent_key",
517 TypeConfig: corekccv1alpha1.TypeConfig{
518 Key: "projectRef",
519 GVK: k8sschema.GroupVersionKind{
520 Group: "test1.cnrm.cloud.google.com",
521 Version: "v1alpha1",
522 Kind: "Test1Bar",
523 },
524 },
525 },
526 },
527 },
528 state: map[string]interface{}{
529 "parent_key": "project-id-from-tf-state",
530 "string_key": "string-val",
531 },
532 prevSpec: map[string]interface{}{
533 "stringKey": "string-val",
534 },
535 expected: map[string]interface{}{
536 "stringKey": "string-val",
537 "projectRef": map[string]interface{}{
538 "external": "project-id-from-tf-state",
539 },
540 },
541 },
542 {
543 name: "hierarchical references are preserved",
544 rc: &corekccv1alpha1.ResourceConfig{
545 Containers: []corekccv1alpha1.Container{
546 {
547 Type: corekccv1alpha1.ContainerTypeProject,
548 TFField: "parent_key",
549 },
550 },
551 HierarchicalReferences: []corekccv1alpha1.HierarchicalReference{
552 {
553 Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
554 Key: "projectRef",
555 },
556 },
557 ResourceReferences: []corekccv1alpha1.ReferenceConfig{
558 {
559 TFField: "parent_key",
560 TypeConfig: corekccv1alpha1.TypeConfig{
561 Key: "projectRef",
562 GVK: k8sschema.GroupVersionKind{
563 Group: "test1.cnrm.cloud.google.com",
564 Version: "v1alpha1",
565 Kind: "Test1Bar",
566 },
567 },
568 },
569 },
570 },
571 state: map[string]interface{}{
572 "parent_key": "project-id-from-tf-state",
573 "string_key": "string-val",
574 },
575 prevSpec: map[string]interface{}{
576 "stringKey": "string-val",
577 "projectRef": map[string]interface{}{
578 "name": "my-ref",
579 },
580 },
581 expected: map[string]interface{}{
582 "stringKey": "string-val",
583 "projectRef": map[string]interface{}{
584 "name": "my-ref",
585 },
586 },
587 },
588 {
589 name: "external hierarchical references are preserved",
590 rc: &corekccv1alpha1.ResourceConfig{
591 Containers: []corekccv1alpha1.Container{
592 {
593 Type: corekccv1alpha1.ContainerTypeProject,
594 TFField: "parent_key",
595 },
596 },
597 HierarchicalReferences: []corekccv1alpha1.HierarchicalReference{
598 {
599 Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
600 Key: "projectRef",
601 },
602 },
603 ResourceReferences: []corekccv1alpha1.ReferenceConfig{
604 {
605 TFField: "parent_key",
606 TypeConfig: corekccv1alpha1.TypeConfig{
607 Key: "projectRef",
608 GVK: k8sschema.GroupVersionKind{
609 Group: "test1.cnrm.cloud.google.com",
610 Version: "v1alpha1",
611 Kind: "Test1Bar",
612 },
613 },
614 },
615 },
616 },
617 state: map[string]interface{}{
618 "parent_key": "project-id-from-tf-state",
619 "string_key": "string-val",
620 },
621 prevSpec: map[string]interface{}{
622 "stringKey": "string-val",
623 "projectRef": map[string]interface{}{
624 "external": "my-ref",
625 },
626 },
627 expected: map[string]interface{}{
628 "stringKey": "string-val",
629 "projectRef": map[string]interface{}{
630 "external": "my-ref",
631 },
632 },
633 },
634 {
635 name: "sensitive fields with simple values are preserved",
636 state: map[string]interface{}{
637 "sensitive_field_key": "val",
638 },
639 prevSpec: map[string]interface{}{
640 "sensitiveFieldKey": map[string]interface{}{
641 "value": "old-val",
642 },
643 },
644 expected: map[string]interface{}{
645 "sensitiveFieldKey": map[string]interface{}{
646 "value": "old-val",
647 },
648 },
649 },
650 {
651 name: "sensitive fields with values from secret refs are preserved",
652 state: map[string]interface{}{
653 "sensitive_field_key": "val",
654 },
655 prevSpec: map[string]interface{}{
656 "sensitiveFieldKey": map[string]interface{}{
657 "valueFrom": map[string]interface{}{
658 "secretKeyRef": map[string]interface{}{
659 "name": "secret1",
660 "key": "key1",
661 },
662 },
663 },
664 },
665 expected: map[string]interface{}{
666 "sensitiveFieldKey": map[string]interface{}{
667 "valueFrom": map[string]interface{}{
668 "secretKeyRef": map[string]interface{}{
669 "name": "secret1",
670 "key": "key1",
671 },
672 },
673 },
674 },
675 },
676 {
677 name: "sensitive fields nested in lists of objects are preserved",
678 state: map[string]interface{}{
679 "list_of_objects_key": []interface{}{
680 map[string]interface{}{
681 "sensitive_field_nested_in_list_of_objects_key": "val1",
682 },
683 map[string]interface{}{
684 "sensitive_field_nested_in_list_of_objects_key": "val2",
685 },
686 },
687 },
688 prevSpec: map[string]interface{}{
689 "listOfObjectsKey": []interface{}{
690 map[string]interface{}{
691 "sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
692 "valueFrom": map[string]interface{}{
693 "secretKeyRef": map[string]interface{}{
694 "name": "secret1",
695 "key": "key1",
696 },
697 },
698 },
699 },
700 map[string]interface{}{
701 "sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
702 "valueFrom": map[string]interface{}{
703 "secretKeyRef": map[string]interface{}{
704 "name": "secret2",
705 "key": "key2",
706 },
707 },
708 },
709 },
710 },
711 },
712 expected: map[string]interface{}{
713 "listOfObjectsKey": []interface{}{
714 map[string]interface{}{
715 "sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
716 "valueFrom": map[string]interface{}{
717 "secretKeyRef": map[string]interface{}{
718 "name": "secret1",
719 "key": "key1",
720 },
721 },
722 },
723 },
724 map[string]interface{}{
725 "sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
726 "valueFrom": map[string]interface{}{
727 "secretKeyRef": map[string]interface{}{
728 "name": "secret2",
729 "key": "key2",
730 },
731 },
732 },
733 },
734 },
735 },
736 },
737 {
738 name: "sensitive fields nested in objects are preserved",
739 state: map[string]interface{}{
740 "nested_object_key": []interface{}{
741 map[string]interface{}{
742 "nested_sensitive_field_key": "val",
743 },
744 },
745 },
746 prevSpec: map[string]interface{}{
747 "nestedObjectKey": map[string]interface{}{
748 "nestedSensitiveFieldKey": map[string]interface{}{
749 "valueFrom": map[string]interface{}{
750 "secretKeyRef": map[string]interface{}{
751 "name": "secret1",
752 "key": "key1",
753 },
754 },
755 },
756 },
757 },
758 expected: map[string]interface{}{
759 "nestedObjectKey": map[string]interface{}{
760 "nestedSensitiveFieldKey": map[string]interface{}{
761 "valueFrom": map[string]interface{}{
762 "secretKeyRef": map[string]interface{}{
763 "name": "secret1",
764 "key": "key1",
765 },
766 },
767 },
768 },
769 },
770 },
771 {
772 name: "sensitive fields set with simple value if not specified",
773 state: map[string]interface{}{
774 "sensitive_field_key": "val",
775 },
776 prevSpec: map[string]interface{}{},
777 expected: map[string]interface{}{
778 "sensitiveFieldKey": map[string]interface{}{
779 "value": "val",
780 },
781 },
782 },
783 {
784 name: "sensitive fields nested in lists of objects set with simple value if not specified",
785 state: map[string]interface{}{
786 "list_of_objects_key": []interface{}{
787 map[string]interface{}{
788 "sensitive_field_nested_in_list_of_objects_key": "val1",
789 },
790 map[string]interface{}{
791 "sensitive_field_nested_in_list_of_objects_key": "val2",
792 },
793 },
794 },
795 prevSpec: map[string]interface{}{},
796 expected: map[string]interface{}{
797 "listOfObjectsKey": []interface{}{
798 map[string]interface{}{
799 "sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
800 "value": "val1",
801 },
802 },
803 map[string]interface{}{
804 "sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
805 "value": "val2",
806 },
807 },
808 },
809 },
810 },
811 {
812 name: "sensitive fields nested in objects set with simple value if not specified",
813 state: map[string]interface{}{
814 "nested_object_key": []interface{}{
815 map[string]interface{}{
816 "nested_sensitive_field_key": "val",
817 },
818 },
819 },
820 prevSpec: map[string]interface{}{},
821 expected: map[string]interface{}{
822 "nestedObjectKey": map[string]interface{}{
823 "nestedSensitiveFieldKey": map[string]interface{}{
824 "value": "val",
825 },
826 },
827 },
828 },
829 {
830 name: "maps are treated as atomic when specified by user",
831 state: map[string]interface{}{
832 "map_key": map[string]interface{}{
833 "foo": "bar",
834 "baz": "abc",
835 },
836 },
837 prevSpec: map[string]interface{}{
838 "mapKey": map[string]interface{}{
839 "foo": "bar",
840 },
841 },
842 expected: map[string]interface{}{
843 "mapKey": map[string]interface{}{
844 "foo": "bar",
845 },
846 },
847 },
848
849 {
850 name: "values are sourced from live state when not in managed fields set",
851 state: map[string]interface{}{
852 "int_key": float64(2),
853 "float_key": float64(1.5),
854 "string_key": "external",
855 "bool_key": true,
856 "nested_object_key": []interface{}{
857 map[string]interface{}{
858 "nested_float_key": float64(1.5),
859 },
860 },
861 },
862 prevSpec: map[string]interface{}{
863 "intKey": float64(1),
864 "floatKey": float64(0.5),
865 "stringKey": "k8s",
866 "boolKey": false,
867 "nestedObjectKey": map[string]interface{}{
868 "nestedFloatKey": float64(0.5),
869 },
870 },
871 managedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
872 "f:unrelated": emptyObject,
873 }),
874 expected: map[string]interface{}{
875 "intKey": float64(2),
876 "floatKey": float64(1.5),
877 "stringKey": "external",
878 "boolKey": true,
879 "nestedObjectKey": map[string]interface{}{
880 "nestedFloatKey": float64(1.5),
881 },
882 },
883 },
884 {
885 name: "values are sourced from spec when in managed fields set",
886 state: map[string]interface{}{
887 "int_key": float64(2),
888 "float_key": float64(1.5),
889 "string_key": "external",
890 "bool_key": true,
891 "nested_object_key": []interface{}{
892 map[string]interface{}{
893 "nested_float_key": float64(1.5),
894 },
895 },
896 },
897 prevSpec: map[string]interface{}{
898 "intKey": float64(1),
899 "floatKey": float64(0.5),
900 "stringKey": "k8s",
901 "boolKey": false,
902 "nestedObjectKey": map[string]interface{}{
903 "nestedFloatKey": float64(0.5),
904 },
905 },
906 managedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
907 "f:intKey": emptyObject,
908 "f:floatKey": emptyObject,
909 "f:stringKey": emptyObject,
910 "f:boolKey": emptyObject,
911 "f:nestedObjectKey": map[string]interface{}{
912 "f:nestedFloatKey": emptyObject,
913 },
914 }),
915 expected: map[string]interface{}{
916 "intKey": float64(1),
917 "floatKey": float64(0.5),
918 "stringKey": "k8s",
919 "boolKey": false,
920 "nestedObjectKey": map[string]interface{}{
921 "nestedFloatKey": float64(0.5),
922 },
923 },
924 },
925 {
926 name: "maps are treated as atomic when k8s-managed",
927 state: map[string]interface{}{
928 "map_key": map[string]interface{}{
929 "foo": "bar",
930 "baz": "abc",
931 },
932 },
933 prevSpec: map[string]interface{}{
934 "mapKey": map[string]interface{}{
935 "foo": "bar",
936 },
937 },
938 managedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
939 "f:mapKey": map[string]interface{}{
940 "f:foo": emptyObject,
941 },
942 }),
943 expected: map[string]interface{}{
944 "mapKey": map[string]interface{}{
945 "foo": "bar",
946 },
947 },
948 },
949
950
951
952
953 {
954 name: "values in lists of objects ignore managed fields",
955 state: map[string]interface{}{
956 "list_of_objects_key": []interface{}{
957 map[string]interface{}{
958 "nested_int_key": float64(1),
959 },
960 },
961 },
962 prevSpec: map[string]interface{}{
963 "listOfObjectsKey": []interface{}{
964 map[string]interface{}{
965 "nestedIntKey": float64(2),
966 },
967 },
968 },
969 managedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
970 "f:unrelated": emptyObject,
971 }),
972 expected: map[string]interface{}{
973
974
975 "listOfObjectsKey": []interface{}{
976 map[string]interface{}{
977 "nestedIntKey": float64(2),
978 },
979 },
980 },
981 },
982 {
983 name: "values in primitive lists ignore managed fields",
984 state: map[string]interface{}{
985 "list_of_primitives_key": []interface{}{
986 "element_1",
987 "element_2",
988 },
989 },
990 prevSpec: map[string]interface{}{
991 "listOfPrimitivesKey": []interface{}{
992 "element1",
993 },
994 },
995 managedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
996 "f:listOfPrimitivesKey": emptyObject,
997 }),
998 expected: map[string]interface{}{
999
1000 "listOfPrimitivesKey": []interface{}{
1001 "element_1",
1002 "element_2",
1003 },
1004 },
1005 },
1006 }
1007
1008 for _, tc := range tests {
1009 tc := tc
1010 t.Run(tc.name, func(t *testing.T) {
1011 t.Parallel()
1012 r := resourceSkeleton()
1013 if tc.rc != nil {
1014 r.ResourceConfig = *tc.rc
1015 }
1016 if tc.schemaOverride != nil {
1017 r.TFResource.Schema = tc.schemaOverride
1018 }
1019 r.SetNamespace(test.Namespace)
1020 actual := ConvertTFObjToKCCObj(tc.state, tc.prevSpec, r.TFResource.Schema, &r.ResourceConfig, "", tc.managedFields)
1021 if !reflect.DeepEqual(tc.expected, actual) {
1022 t.Fatalf("expected: %v, actual: %v", tc.expected, actual)
1023 }
1024 })
1025
1026 }
1027 }
1028
1029 func TestGetLabelsFromState(t *testing.T) {
1030 tests := []struct {
1031 name string
1032 rc *corekccv1alpha1.ResourceConfig
1033 tfAttributes map[string]string
1034 expected map[string]string
1035 }{
1036 {
1037 name: "empty labels should resolve",
1038 rc: &corekccv1alpha1.ResourceConfig{
1039 MetadataMapping: corekccv1alpha1.MetadataMapping{
1040 Labels: "map_key",
1041 },
1042 },
1043 tfAttributes: map[string]string{
1044 "map_key.%": "0",
1045 },
1046 expected: map[string]string{},
1047 },
1048 {
1049 name: "simple labels should resolve",
1050 rc: &corekccv1alpha1.ResourceConfig{
1051 MetadataMapping: corekccv1alpha1.MetadataMapping{
1052 Labels: "map_key",
1053 },
1054 },
1055 tfAttributes: map[string]string{
1056 "map_key.%": "2",
1057 "map_key.key1": "val1",
1058 "map_key.key2": "val2",
1059 },
1060 expected: map[string]string{
1061 "key1": "val1",
1062 "key2": "val2",
1063 },
1064 },
1065 {
1066 name: "nested labels should resolve",
1067 rc: &corekccv1alpha1.ResourceConfig{
1068 MetadataMapping: corekccv1alpha1.MetadataMapping{
1069 Labels: "nested_object_key.nested_map_key",
1070 },
1071 },
1072 tfAttributes: map[string]string{
1073 "nested_object_key.#": "1",
1074 "nested_object_key.0.nested_map_key.%": "2",
1075 "nested_object_key.0.nested_map_key.key1": "val1",
1076 "nested_object_key.0.nested_map_key.key2": "val2",
1077 },
1078 expected: map[string]string{
1079 "key1": "val1",
1080 "key2": "val2",
1081 },
1082 },
1083 {
1084 name: "nested labels with nil should resolve",
1085 rc: &corekccv1alpha1.ResourceConfig{
1086 MetadataMapping: corekccv1alpha1.MetadataMapping{
1087 Labels: "nested_object_key.nested_map_key",
1088 },
1089 },
1090 tfAttributes: map[string]string{
1091 "nested_object_key.#": "1",
1092 },
1093 expected: map[string]string{},
1094 },
1095 }
1096 for _, tc := range tests {
1097 t.Run(tc.name, func(t *testing.T) {
1098 tc := tc
1099 t.Parallel()
1100 r := resourceSkeleton()
1101 if tc.rc != nil {
1102 r.ResourceConfig = *tc.rc
1103 }
1104 rawState := terraform.InstanceState{
1105 Attributes: tc.tfAttributes,
1106 }
1107 labels := GetLabelsFromState(r, &rawState)
1108 if !reflect.DeepEqual(tc.expected, labels) {
1109 t.Fatalf("expected: %v, actual: %v", tc.expected, labels)
1110 }
1111 })
1112 }
1113 }
1114
1115 func TestResolveSpecAndStatusWithResourceID_WithDesiredStateInSpecAndObservedStateInStatus(t *testing.T) {
1116 tests := []struct {
1117 name string
1118 rc *corekccv1alpha1.ResourceConfig
1119 metadataName string
1120 prevSpec map[string]interface{}
1121 prevStatus map[string]interface{}
1122 tfResource *tfschema.Resource
1123 tfAttributes map[string]string
1124 expectedSpec map[string]interface{}
1125 expectedStatus map[string]interface{}
1126 managedFields *fieldpath.Set
1127 }{
1128 {
1129 name: "only persist specified fields in spec",
1130 rc: &corekccv1alpha1.ResourceConfig{
1131 ResourceID: corekccv1alpha1.ResourceID{
1132 TargetField: "test_field",
1133 },
1134 MetadataMapping: corekccv1alpha1.MetadataMapping{
1135 Name: "test_field",
1136 },
1137 HierarchicalReferences: []corekccv1alpha1.HierarchicalReference{
1138 {
1139 Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
1140 Key: "projectRef",
1141 },
1142 },
1143 ResourceReferences: []corekccv1alpha1.ReferenceConfig{
1144 {
1145 TFField: "parent_key",
1146 TypeConfig: corekccv1alpha1.TypeConfig{
1147 Key: "projectRef",
1148 GVK: k8sschema.GroupVersionKind{
1149 Group: "test1.cnrm.cloud.google.com",
1150 Version: "v1alpha1",
1151 Kind: "Test1Bar",
1152 },
1153 },
1154 },
1155 },
1156 },
1157 prevSpec: map[string]interface{}{
1158 "resourceID": "resource-id",
1159 "intKey": int64(1),
1160 "floatKey": 0.5,
1161 "projectRef": map[string]interface{}{
1162 "name": "my-ref",
1163 },
1164 "sensitiveFieldKey": map[string]interface{}{
1165 "valueFrom": map[string]interface{}{
1166 "secretKeyRef": map[string]interface{}{
1167 "name": "secret1",
1168 "key": "key1",
1169 },
1170 },
1171 },
1172 },
1173 prevStatus: map[string]interface{}{},
1174 tfResource: &tfschema.Resource{
1175 Schema: map[string]*tfschema.Schema{
1176 "test_field": {
1177 Type: tfschema.TypeString,
1178 Required: true,
1179 },
1180 "parent_key": {
1181 Type: tfschema.TypeString,
1182 Required: true,
1183 },
1184 "int_key": {
1185 Type: tfschema.TypeInt,
1186 Optional: true,
1187 },
1188 "float_key": {
1189 Type: tfschema.TypeFloat,
1190 Optional: true,
1191 },
1192 "bool_key": {
1193 Type: tfschema.TypeBool,
1194 Optional: true,
1195 },
1196 "sensitive_field_key": {
1197 Type: tfschema.TypeString,
1198 Required: true,
1199 Sensitive: true,
1200 },
1201 },
1202 },
1203 tfAttributes: map[string]string{
1204 "test_field": "resource-id",
1205 "int_key": "1",
1206 "float_key": "0.5",
1207 "bool_key": "false",
1208 "parent_key": "project-id-from-tf-state",
1209 "sensitive_field_key": "val",
1210 },
1211 expectedSpec: map[string]interface{}{
1212 "resourceID": "resource-id",
1213 "intKey": int64(1),
1214 "floatKey": 0.5,
1215 "projectRef": map[string]interface{}{
1216 "name": "my-ref",
1217 },
1218 "sensitiveFieldKey": map[string]interface{}{
1219 "valueFrom": map[string]interface{}{
1220 "secretKeyRef": map[string]interface{}{
1221 "name": "secret1",
1222 "key": "key1",
1223 },
1224 },
1225 },
1226 },
1227 expectedStatus: nil,
1228 },
1229 {
1230 name: "observed state for output-only fields are persisted in status",
1231 prevSpec: map[string]interface{}{},
1232 prevStatus: map[string]interface{}{},
1233 tfResource: &tfschema.Resource{
1234 Schema: map[string]*tfschema.Schema{
1235 "status_field": {
1236 Type: tfschema.TypeString,
1237 Computed: true,
1238 },
1239 },
1240 },
1241 tfAttributes: map[string]string{
1242 "status_field": "strVal",
1243 },
1244 expectedSpec: map[string]interface{}{},
1245 expectedStatus: map[string]interface{}{
1246 "statusField": "strVal",
1247 },
1248 },
1249 {
1250 name: "persist desired state in spec and output-only observed state in status",
1251 rc: &corekccv1alpha1.ResourceConfig{
1252 ResourceID: corekccv1alpha1.ResourceID{
1253 TargetField: "test_field",
1254 },
1255 MetadataMapping: corekccv1alpha1.MetadataMapping{
1256 Name: "test_field",
1257 },
1258 },
1259 prevSpec: map[string]interface{}{
1260 "resourceID": "resource-id",
1261 "intKey": int64(1),
1262 "floatKey": 0.5,
1263 },
1264 prevStatus: map[string]interface{}{},
1265 tfResource: &tfschema.Resource{
1266 Schema: map[string]*tfschema.Schema{
1267 "test_field": {
1268 Type: tfschema.TypeString,
1269 Required: true,
1270 },
1271 "int_key": {
1272 Type: tfschema.TypeInt,
1273 Optional: true,
1274 },
1275 "float_key": {
1276 Type: tfschema.TypeFloat,
1277 Optional: true,
1278 },
1279 "bool_key": {
1280 Type: tfschema.TypeBool,
1281 Optional: true,
1282 },
1283 "status_field": {
1284 Type: tfschema.TypeString,
1285 Computed: true,
1286 },
1287 },
1288 },
1289 tfAttributes: map[string]string{
1290 "test_field": "resource-id",
1291 "int_key": "1",
1292 "float_key": "0.5",
1293 "bool_key": "false",
1294 "status_field": "strVal",
1295 },
1296 expectedSpec: map[string]interface{}{
1297 "resourceID": "resource-id",
1298 "intKey": int64(1),
1299 "floatKey": 0.5,
1300 },
1301 expectedStatus: map[string]interface{}{
1302 "statusField": "strVal",
1303 },
1304 },
1305 {
1306 name: "only persist specified nested fields in spec",
1307 rc: &corekccv1alpha1.ResourceConfig{
1308 ResourceID: corekccv1alpha1.ResourceID{
1309 TargetField: "test_field",
1310 },
1311 MetadataMapping: corekccv1alpha1.MetadataMapping{
1312 Name: "test_field",
1313 },
1314 },
1315 prevSpec: map[string]interface{}{
1316 "resourceID": "resource-id",
1317 "nestedObjectKey": map[string]interface{}{
1318 "nestedKey1": "val1",
1319 },
1320 },
1321 prevStatus: map[string]interface{}{},
1322 tfResource: &tfschema.Resource{
1323 Schema: map[string]*tfschema.Schema{
1324 "test_field": {
1325 Type: tfschema.TypeString,
1326 Required: true,
1327 },
1328 "nested_object_key": {
1329 Type: tfschema.TypeList,
1330 MaxItems: 1,
1331 Optional: true,
1332 Elem: &tfschema.Resource{
1333 Schema: map[string]*tfschema.Schema{
1334 "nested_key1": {
1335 Type: tfschema.TypeString,
1336 Optional: true,
1337 },
1338 "nested_key2": {
1339 Type: tfschema.TypeString,
1340 Optional: true,
1341 },
1342 },
1343 },
1344 },
1345 },
1346 },
1347 tfAttributes: map[string]string{
1348 "test_field": "resource-id",
1349 "nested_object_key.#": "1",
1350 "nested_object_key.0.nested_key1": "val1",
1351 "nested_object_key.0.nested_key2": "val2",
1352 },
1353 expectedSpec: map[string]interface{}{
1354 "resourceID": "resource-id",
1355 "nestedObjectKey": map[string]interface{}{
1356 "nestedKey1": "val1",
1357 },
1358 },
1359 },
1360 {
1361 name: "preserve lists of objects unmodified in spec if specified",
1362 rc: &corekccv1alpha1.ResourceConfig{
1363 ResourceID: corekccv1alpha1.ResourceID{
1364 TargetField: "test_field",
1365 },
1366 MetadataMapping: corekccv1alpha1.MetadataMapping{
1367 Name: "test_field",
1368 },
1369 },
1370 prevSpec: map[string]interface{}{
1371 "resourceID": "resource-id",
1372 "listOfObjectsKey": []interface{}{
1373 map[string]interface{}{
1374 "field1": 0.5,
1375 "field2": "strval1",
1376 },
1377 map[string]interface{}{
1378 "field1": 0.7,
1379 },
1380 },
1381 },
1382 prevStatus: map[string]interface{}{},
1383 tfResource: &tfschema.Resource{
1384 Schema: map[string]*tfschema.Schema{
1385 "test_field": {
1386 Type: tfschema.TypeString,
1387 Required: true,
1388 },
1389 "list_of_objects_key": {
1390 Type: tfschema.TypeList,
1391 Optional: true,
1392 Elem: &tfschema.Resource{
1393 Schema: map[string]*tfschema.Schema{
1394 "field1": {
1395 Type: tfschema.TypeFloat,
1396 Optional: true,
1397 },
1398 "field2": {
1399 Type: tfschema.TypeString,
1400 Optional: true,
1401 },
1402 },
1403 },
1404 },
1405 },
1406 },
1407 tfAttributes: map[string]string{
1408 "test_field": "resource-id",
1409 "list_of_objects_key.#": "2",
1410 "list_of_objects_key.0.field1": "0.5",
1411 "list_of_objects_key.0.field2": "strval1",
1412 "list_of_objects_key.1.field1": "0.7",
1413 "list_of_objects_key.1.field2": "strval2",
1414 },
1415 expectedSpec: map[string]interface{}{
1416 "resourceID": "resource-id",
1417 "listOfObjectsKey": []interface{}{
1418 map[string]interface{}{
1419 "field1": 0.5,
1420 "field2": "strval1",
1421 },
1422 map[string]interface{}{
1423 "field1": 0.7,
1424 },
1425 },
1426 },
1427 },
1428 {
1429 name: "primitive lists are preserved with specified values",
1430 rc: &corekccv1alpha1.ResourceConfig{
1431 ResourceID: corekccv1alpha1.ResourceID{
1432 TargetField: "test_field",
1433 },
1434 MetadataMapping: corekccv1alpha1.MetadataMapping{
1435 Name: "test_field",
1436 },
1437 },
1438 prevSpec: map[string]interface{}{
1439 "resourceID": "resource-id",
1440 "listOfPrimitivesKey": []interface{}{
1441 "element_1",
1442 },
1443 },
1444 prevStatus: map[string]interface{}{},
1445 tfResource: &tfschema.Resource{
1446 Schema: map[string]*tfschema.Schema{
1447 "test_field": {
1448 Type: tfschema.TypeString,
1449 Required: true,
1450 },
1451 "list_of_primitives_key": {
1452 Type: tfschema.TypeList,
1453 Optional: true,
1454 Elem: &tfschema.Schema{
1455 Type: tfschema.TypeString,
1456 },
1457 },
1458 },
1459 },
1460 tfAttributes: map[string]string{
1461 "test_field": "resource-id",
1462 "list_of_primitives_key.#": "2",
1463 "list_of_primitives_key.0": "element_1",
1464 "list_of_primitives_key.1": "element_2",
1465 },
1466 expectedSpec: map[string]interface{}{
1467 "resourceID": "resource-id",
1468 "listOfPrimitivesKey": []interface{}{
1469 "element_1",
1470 },
1471 },
1472 },
1473 {
1474 name: "server-generated id is retrieved from state and persisted",
1475 rc: &corekccv1alpha1.ResourceConfig{
1476 ResourceID: corekccv1alpha1.ResourceID{
1477 TargetField: "test_field",
1478 },
1479 ServerGeneratedIDField: "test_field",
1480 },
1481 prevSpec: map[string]interface{}{},
1482 prevStatus: map[string]interface{}{},
1483 tfResource: &tfschema.Resource{
1484 Schema: map[string]*tfschema.Schema{
1485 "test_field": {
1486 Type: tfschema.TypeString,
1487 Computed: true,
1488 },
1489 },
1490 },
1491 tfAttributes: map[string]string{
1492 "test_field": "new-server-generated-id",
1493 },
1494 expectedSpec: map[string]interface{}{
1495 "resourceID": "new-server-generated-id",
1496 },
1497 expectedStatus: map[string]interface{}{
1498 "testField": "new-server-generated-id",
1499 },
1500 },
1501 {
1502 name: "fields in spec are persisted even if they not in managed fields set",
1503 rc: &corekccv1alpha1.ResourceConfig{
1504 ResourceID: corekccv1alpha1.ResourceID{
1505 TargetField: "test_field",
1506 },
1507 MetadataMapping: corekccv1alpha1.MetadataMapping{
1508 Name: "test_field",
1509 },
1510 },
1511 prevSpec: map[string]interface{}{
1512 "resourceID": "resource-id",
1513 "intKey": int64(1),
1514 "floatKey": 0.5,
1515 "boolKey": false,
1516 },
1517 prevStatus: map[string]interface{}{},
1518 tfResource: &tfschema.Resource{
1519 Schema: map[string]*tfschema.Schema{
1520 "test_field": {
1521 Type: tfschema.TypeString,
1522 Required: true,
1523 },
1524 "int_key": {
1525 Type: tfschema.TypeInt,
1526 Optional: true,
1527 },
1528 "float_key": {
1529 Type: tfschema.TypeFloat,
1530 Optional: true,
1531 },
1532 "bool_key": {
1533 Type: tfschema.TypeBool,
1534 Optional: true,
1535 },
1536 "list_of_objects_key": {
1537 Type: tfschema.TypeList,
1538 Optional: true,
1539 Elem: &tfschema.Resource{
1540 Schema: map[string]*tfschema.Schema{
1541 "field1": {
1542 Type: tfschema.TypeFloat,
1543 Optional: true,
1544 },
1545 "field2": {
1546 Type: tfschema.TypeString,
1547 Optional: true,
1548 },
1549 },
1550 },
1551 },
1552 },
1553 },
1554 tfAttributes: map[string]string{
1555 "test_field": "resource-id",
1556 "int_key": "1",
1557 "float_key": "0.5",
1558 "bool_key": "false",
1559 "list_of_objects_key.#": "2",
1560 "list_of_objects_key.0.field1": "0.5",
1561 "list_of_objects_key.0.field2": "strval1",
1562 "list_of_objects_key.1.field1": "0.7",
1563 "list_of_objects_key.1.field2": "strval2",
1564 },
1565 expectedSpec: map[string]interface{}{
1566 "resourceID": "resource-id",
1567 "intKey": int64(1),
1568 "floatKey": 0.5,
1569 "boolKey": false,
1570 },
1571 managedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
1572 "f:unrelated": emptyObject,
1573 }),
1574 },
1575 }
1576
1577 for _, tc := range tests {
1578 t.Run(tc.name, func(t *testing.T) {
1579 tc := tc
1580 t.Parallel()
1581 r := resourceSkeleton()
1582 if tc.metadataName != "" {
1583 r.SetName(tc.metadataName)
1584 }
1585 r.Spec = tc.prevSpec
1586 r.Status = tc.prevStatus
1587 r.TFResource = tc.tfResource
1588 r.ManagedFields = tc.managedFields
1589 if tc.rc != nil {
1590 r.ResourceConfig = *tc.rc
1591 }
1592 state := terraform.InstanceState{
1593 Attributes: tc.tfAttributes,
1594 }
1595 k8s.SetAnnotation(k8s.StateIntoSpecAnnotation, k8s.StateAbsentInSpec, r)
1596 spec, status := ResolveSpecAndStatusWithResourceID(r, &state)
1597 if got, want := spec, tc.expectedSpec; !reflect.DeepEqual(got, want) {
1598 t.Fatalf("got: %v, want: %v", got, want)
1599 }
1600 if got, want := status, tc.expectedStatus; !reflect.DeepEqual(got, want) {
1601 t.Fatalf("got: %v, want: %v", got, want)
1602 }
1603 })
1604 }
1605 }
1606
1607 func TestResolveSpecAndStatusWithResourceID(t *testing.T) {
1608 tests := []struct {
1609 name string
1610 rc *corekccv1alpha1.ResourceConfig
1611 metadataName string
1612 prevSpec map[string]interface{}
1613 prevStatus map[string]interface{}
1614 tfResource *tfschema.Resource
1615 tfAttributes map[string]string
1616 expectedSpec map[string]interface{}
1617 expectedStatus map[string]interface{}
1618 }{
1619 {
1620 name: "with existing user-specified resource ID",
1621 rc: &corekccv1alpha1.ResourceConfig{
1622 ResourceID: corekccv1alpha1.ResourceID{
1623 TargetField: "test_field",
1624 },
1625 MetadataMapping: corekccv1alpha1.MetadataMapping{
1626 Name: "test_field",
1627 },
1628 },
1629 prevSpec: map[string]interface{}{
1630 "resourceID": "resource-id",
1631 },
1632 prevStatus: map[string]interface{}{},
1633 tfResource: &tfschema.Resource{
1634 Schema: map[string]*tfschema.Schema{
1635 "test_field": {
1636 Type: tfschema.TypeString,
1637 Required: true,
1638 },
1639 },
1640 },
1641 tfAttributes: map[string]string{
1642 "test_field": "resource-id",
1643 },
1644 expectedSpec: map[string]interface{}{
1645 "resourceID": "resource-id",
1646 },
1647 expectedStatus: nil,
1648 },
1649 {
1650 name: "with empty user-specified resource ID",
1651 rc: &corekccv1alpha1.ResourceConfig{
1652 ResourceID: corekccv1alpha1.ResourceID{
1653 TargetField: "test_field",
1654 },
1655 MetadataMapping: corekccv1alpha1.MetadataMapping{
1656 Name: "test_field",
1657 },
1658 },
1659 prevSpec: map[string]interface{}{
1660 "resourceID": "",
1661 },
1662 prevStatus: map[string]interface{}{},
1663 tfResource: &tfschema.Resource{
1664 Schema: map[string]*tfschema.Schema{
1665 "test_field": {
1666 Type: tfschema.TypeString,
1667 Required: true,
1668 },
1669 },
1670 },
1671 tfAttributes: map[string]string{
1672 "test_field": "metadata-name-value",
1673 },
1674 expectedSpec: map[string]interface{}{
1675 "resourceID": "",
1676 },
1677 expectedStatus: nil,
1678 },
1679 {
1680 name: "with user-specified resource ID unset and metadata.name set",
1681 rc: &corekccv1alpha1.ResourceConfig{
1682 ResourceID: corekccv1alpha1.ResourceID{
1683 TargetField: "test_field",
1684 },
1685 MetadataMapping: corekccv1alpha1.MetadataMapping{
1686 Name: "test_field",
1687 },
1688 },
1689 metadataName: "default-id",
1690 prevSpec: map[string]interface{}{},
1691 prevStatus: map[string]interface{}{},
1692 tfResource: &tfschema.Resource{
1693 Schema: map[string]*tfschema.Schema{
1694 "test_field": {
1695 Type: tfschema.TypeString,
1696 Required: true,
1697 },
1698 },
1699 },
1700 tfAttributes: map[string]string{
1701 "test_field": "metadata-name-value",
1702 },
1703 expectedSpec: map[string]interface{}{
1704 "resourceID": "default-id",
1705 },
1706 expectedStatus: nil,
1707 },
1708 {
1709 name: "specifying server-generated resource ID for the first time",
1710 rc: &corekccv1alpha1.ResourceConfig{
1711 ResourceID: corekccv1alpha1.ResourceID{
1712 TargetField: "test_field",
1713 },
1714 ServerGeneratedIDField: "test_field",
1715 },
1716 prevSpec: map[string]interface{}{},
1717 prevStatus: map[string]interface{}{},
1718 tfResource: &tfschema.Resource{
1719 Schema: map[string]*tfschema.Schema{
1720 "test_field": {
1721 Type: tfschema.TypeString,
1722 Computed: true,
1723 },
1724 },
1725 },
1726 tfAttributes: map[string]string{
1727 "test_field": "new-server-generated-id",
1728 },
1729 expectedSpec: map[string]interface{}{
1730 "resourceID": "new-server-generated-id",
1731 },
1732 expectedStatus: map[string]interface{}{
1733 "testField": "new-server-generated-id",
1734 },
1735 },
1736 {
1737 name: "specifying server-generated resource ID with a value " +
1738 "template for the first time",
1739 rc: &corekccv1alpha1.ResourceConfig{
1740 ResourceID: corekccv1alpha1.ResourceID{
1741 TargetField: "test_field",
1742 ValueTemplate: "id/{{value}}",
1743 },
1744 ServerGeneratedIDField: "test_field",
1745 },
1746 prevSpec: map[string]interface{}{},
1747 prevStatus: map[string]interface{}{},
1748 tfResource: &tfschema.Resource{
1749 Schema: map[string]*tfschema.Schema{
1750 "test_field": {
1751 Type: tfschema.TypeString,
1752 Computed: true,
1753 },
1754 },
1755 },
1756 tfAttributes: map[string]string{
1757 "test_field": "id/id-with-value-template",
1758 },
1759 expectedSpec: map[string]interface{}{
1760 "resourceID": "id-with-value-template",
1761 },
1762 expectedStatus: map[string]interface{}{
1763 "testField": "id/id-with-value-template",
1764 },
1765 },
1766 {
1767 name: "specifying server-generated resource ID after it is " +
1768 "supported in resource config",
1769 rc: &corekccv1alpha1.ResourceConfig{
1770 ResourceID: corekccv1alpha1.ResourceID{
1771 TargetField: "test_field",
1772 },
1773 ServerGeneratedIDField: "test_field",
1774 },
1775 prevSpec: map[string]interface{}{},
1776 prevStatus: map[string]interface{}{
1777 "testField": "existing-server-generated-id",
1778 },
1779 tfResource: &tfschema.Resource{
1780 Schema: map[string]*tfschema.Schema{
1781 "test_field": {
1782 Type: tfschema.TypeString,
1783 Computed: true,
1784 },
1785 },
1786 },
1787 tfAttributes: map[string]string{
1788 "test_field": "existing-server-generated-id",
1789 },
1790 expectedSpec: map[string]interface{}{
1791 "resourceID": "existing-server-generated-id",
1792 },
1793 expectedStatus: map[string]interface{}{
1794 "testField": "existing-server-generated-id",
1795 },
1796 },
1797 {
1798 name: "with server-generated resource ID already set",
1799 rc: &corekccv1alpha1.ResourceConfig{
1800 ResourceID: corekccv1alpha1.ResourceID{
1801 TargetField: "test_field",
1802 },
1803 ServerGeneratedIDField: "test_field",
1804 },
1805 prevSpec: map[string]interface{}{
1806 "resourceID": "existing-server-generated-id",
1807 },
1808 prevStatus: map[string]interface{}{},
1809 tfResource: &tfschema.Resource{
1810 Schema: map[string]*tfschema.Schema{
1811 "test_field": {
1812 Type: tfschema.TypeString,
1813 Computed: true,
1814 },
1815 },
1816 },
1817 tfAttributes: map[string]string{
1818 "test_field": "existing-server-generated-id",
1819 },
1820 expectedSpec: map[string]interface{}{
1821 "resourceID": "existing-server-generated-id",
1822 },
1823 expectedStatus: map[string]interface{}{
1824 "testField": "existing-server-generated-id",
1825 },
1826 },
1827 {
1828 name: "with resource ID not supported",
1829 rc: &corekccv1alpha1.ResourceConfig{},
1830 prevSpec: map[string]interface{}{
1831 "testField": "testValue",
1832 },
1833 prevStatus: map[string]interface{}{},
1834 tfResource: &tfschema.Resource{
1835 Schema: map[string]*tfschema.Schema{
1836 "test_field": {
1837 Type: tfschema.TypeString,
1838 Optional: true,
1839 },
1840 },
1841 },
1842 tfAttributes: map[string]string{
1843 "test_field": "testValue",
1844 },
1845 expectedSpec: map[string]interface{}{
1846 "testField": "testValue",
1847 },
1848 expectedStatus: nil,
1849 },
1850 }
1851
1852 for _, tc := range tests {
1853 t.Run(tc.name, func(t *testing.T) {
1854 tc := tc
1855 t.Parallel()
1856 r := resourceSkeleton()
1857 if tc.metadataName != "" {
1858 r.SetName(tc.metadataName)
1859 }
1860 r.Spec = tc.prevSpec
1861 r.Status = tc.prevStatus
1862 r.TFResource = tc.tfResource
1863 if tc.rc != nil {
1864 r.ResourceConfig = *tc.rc
1865 }
1866 state := terraform.InstanceState{
1867 Attributes: tc.tfAttributes,
1868 }
1869 spec, status := ResolveSpecAndStatusWithResourceID(r, &state)
1870 if got, want := spec, tc.expectedSpec; !reflect.DeepEqual(got, want) {
1871 t.Fatalf("got: %v, want: %v", got, want)
1872 }
1873 if got, want := status, tc.expectedStatus; !reflect.DeepEqual(got, want) {
1874 t.Fatalf("got: %v, want: %v", got, want)
1875 }
1876 })
1877 }
1878 }
1879
1880 func TestResolveSpecAndStatusWithFieldRenaming(t *testing.T) {
1881 tests := []struct {
1882 name string
1883 rc *corekccv1alpha1.ResourceConfig
1884 tfResource *tfschema.Resource
1885 tfAttributes map[string]string
1886 expectedSpec map[string]interface{}
1887 expectedStatus map[string]interface{}
1888 }{
1889 {
1890 name: "status fields that collide with reserved status fields are renamed",
1891 rc: &corekccv1alpha1.ResourceConfig{
1892 Name: "test-tf-resource-name",
1893 },
1894 tfResource: &tfschema.Resource{
1895 Schema: map[string]*tfschema.Schema{
1896 "generation": {
1897 Type: tfschema.TypeString,
1898 Computed: true,
1899 },
1900 },
1901 },
1902 tfAttributes: map[string]string{
1903 "generation": "testValue1",
1904 },
1905 expectedStatus: map[string]interface{}{
1906 "resourceGeneration": "testValue1",
1907 },
1908 },
1909 {
1910 name: "spec fields that collide with reserved status fields are not renamed",
1911 rc: &corekccv1alpha1.ResourceConfig{
1912 Name: "test-tf-resource-name",
1913 },
1914 tfResource: &tfschema.Resource{
1915 Schema: map[string]*tfschema.Schema{
1916 "generation": {
1917 Type: tfschema.TypeString,
1918 Optional: true,
1919 },
1920 },
1921 },
1922 tfAttributes: map[string]string{
1923 "generation": "testValue1",
1924 },
1925 expectedSpec: map[string]interface{}{
1926 "generation": "testValue1",
1927 },
1928 },
1929 {
1930 name: "status fields that collide with reserved status fields are not renamed if resource is in the exclude list",
1931 rc: &corekccv1alpha1.ResourceConfig{
1932 Name: "google_storage_default_object_access_control",
1933 },
1934 tfResource: &tfschema.Resource{
1935 Schema: map[string]*tfschema.Schema{
1936 "generation": {
1937 Type: tfschema.TypeString,
1938 Computed: true,
1939 },
1940 },
1941 },
1942 tfAttributes: map[string]string{
1943 "generation": "testValue1",
1944 },
1945 expectedStatus: map[string]interface{}{
1946 "generation": "testValue1",
1947 },
1948 },
1949 }
1950
1951 for _, tc := range tests {
1952 t.Run(tc.name, func(t *testing.T) {
1953 tc := tc
1954 t.Parallel()
1955 r := resourceSkeleton()
1956 r.TFResource = tc.tfResource
1957 if tc.rc != nil {
1958 r.ResourceConfig = *tc.rc
1959 }
1960 state := terraform.InstanceState{
1961 Attributes: tc.tfAttributes,
1962 }
1963 spec, status := ResolveSpecAndStatus(r, &state)
1964 t.Logf("spec = %v\nstatus = %v\n", spec, status)
1965 if got, want := spec, tc.expectedSpec; !reflect.DeepEqual(got, want) {
1966 t.Fatalf("got: %v, want: %v", got, want)
1967 }
1968 if got, want := status, tc.expectedStatus; !reflect.DeepEqual(got, want) {
1969 t.Fatalf("got: %v, want: %v", got, want)
1970 }
1971 })
1972 }
1973 }
1974
1975 func TestResolveSpecAndStatusWithResourceIDPanic(t *testing.T) {
1976 tests := []struct {
1977 name string
1978 rc *corekccv1alpha1.ResourceConfig
1979 metadataName string
1980 prevSpec map[string]interface{}
1981 prevStatus map[string]interface{}
1982 tfResource *tfschema.Resource
1983 tfAttributes map[string]string
1984 }{
1985 {
1986 name: "with user-specified resource ID unset and metadata.name unset",
1987 rc: &corekccv1alpha1.ResourceConfig{
1988 ResourceID: corekccv1alpha1.ResourceID{
1989 TargetField: "test_field",
1990 },
1991 MetadataMapping: corekccv1alpha1.MetadataMapping{
1992 Name: "test_field",
1993 },
1994 },
1995 prevSpec: map[string]interface{}{},
1996 prevStatus: map[string]interface{}{},
1997 tfResource: &tfschema.Resource{
1998 Schema: map[string]*tfschema.Schema{
1999 "test_field": {
2000 Type: tfschema.TypeString,
2001 Required: true,
2002 },
2003 },
2004 },
2005 tfAttributes: map[string]string{
2006 "test_field": "metadata-name-value",
2007 },
2008 },
2009 {
2010 name: "with server-generated resource ID not found",
2011 rc: &corekccv1alpha1.ResourceConfig{
2012 ResourceID: corekccv1alpha1.ResourceID{
2013 TargetField: "test_field",
2014 },
2015 ServerGeneratedIDField: "test_field",
2016 },
2017 prevSpec: map[string]interface{}{},
2018 prevStatus: map[string]interface{}{},
2019 tfResource: &tfschema.Resource{
2020 Schema: map[string]*tfschema.Schema{
2021 "test_field": {
2022 Type: tfschema.TypeString,
2023 Computed: true,
2024 },
2025 },
2026 },
2027 tfAttributes: map[string]string{
2028 "different_field": "new-server-generated-id",
2029 },
2030 },
2031 }
2032
2033 for _, tc := range tests {
2034 t.Run(tc.name, func(t *testing.T) {
2035 tc := tc
2036 t.Parallel()
2037 r := resourceSkeleton()
2038 r.Spec = tc.prevSpec
2039 r.Status = tc.prevStatus
2040 r.TFResource = tc.tfResource
2041 if tc.rc != nil {
2042 r.ResourceConfig = *tc.rc
2043 }
2044 state := terraform.InstanceState{
2045 Attributes: tc.tfAttributes,
2046 }
2047
2048 assertGetSpecAndStatusFromStateWithResourceIDPanic(t, r, &state)
2049 })
2050 }
2051 }
2052
2053 func assertGetSpecAndStatusFromStateWithResourceIDPanic(t *testing.T, resource *Resource, state *terraform.InstanceState) {
2054 defer func() {
2055 if r := recover(); r == nil {
2056 t.Fatalf("GetSpecAndStatusFromState should have panicked")
2057 }
2058 }()
2059 ResolveSpecAndStatusWithResourceID(resource, state)
2060 }
2061
View as plain text