1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package krmtotf_test
16
17 import (
18 "encoding/json"
19 "testing"
20
21 "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 testcontroller "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/controller"
26 testvariable "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/resourcefixture/variable"
27 "github.com/google/go-cmp/cmp"
28 tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
31 )
32
33 func TestWithFieldsPresetForRead(t *testing.T) {
34 nowTime := metav1.Now()
35 tests := []struct {
36 name string
37 imported map[string]interface{}
38 resource *krmtotf.Resource
39 expectedRet map[string]interface{}
40 }{
41 {
42 name: "immutable fields",
43 imported: map[string]interface{}{
44 "imported_field": "imported_val",
45 },
46 resource: &krmtotf.Resource{
47 Resource: k8s.Resource{
48 Spec: map[string]interface{}{
49 "primitiveField": "primitive_val",
50 "listOfPrimitivesField": []interface{}{
51 "list_of_primitives_val_0",
52 },
53 "mapField": map[string]interface{}{
54 "map_key_a": "map_val_a",
55 },
56 "nestedObjectField": map[string]interface{}{
57 "immutableField": "immutable_val",
58 "mutableField": "mutable_val",
59 },
60 "listOfObjectsField": []interface{}{
61 map[string]interface{}{
62 "immutableFieldA": "immutable_val_a",
63 "immutableFieldB": "immutable_val_b",
64 },
65 map[string]interface{}{
66 "immutableFieldA": "immutable_val_a",
67 "mutableField": "mutable_val",
68 },
69 },
70 },
71 },
72 TFResource: &tfschema.Resource{
73 Schema: map[string]*tfschema.Schema{
74 "imported_field": &tfschema.Schema{
75 Type: tfschema.TypeString,
76 Optional: true,
77 },
78 "primitive_field": &tfschema.Schema{
79 Type: tfschema.TypeString,
80 Optional: true,
81 ForceNew: true,
82 },
83 "list_of_primitives_field": &tfschema.Schema{
84 Type: tfschema.TypeList,
85 Optional: true,
86 Elem: &tfschema.Schema{
87 Type: tfschema.TypeString,
88 },
89 ForceNew: true,
90 },
91 "map_field": &tfschema.Schema{
92 Type: tfschema.TypeMap,
93 Optional: true,
94 ForceNew: true,
95 },
96 "nested_object_field": &tfschema.Schema{
97 Type: tfschema.TypeList,
98 MaxItems: 1,
99 Optional: true,
100 Elem: &tfschema.Resource{
101 Schema: map[string]*tfschema.Schema{
102 "immutable_field": &tfschema.Schema{
103 Type: tfschema.TypeString,
104 Optional: true,
105 ForceNew: true,
106 },
107 "mutable_field": &tfschema.Schema{
108 Type: tfschema.TypeString,
109 Optional: true,
110 },
111 },
112 },
113 },
114 "list_of_objects_field": &tfschema.Schema{
115 Type: tfschema.TypeList,
116 Optional: true,
117 Elem: &tfschema.Resource{
118 Schema: map[string]*tfschema.Schema{
119 "immutable_field_a": &tfschema.Schema{
120 Type: tfschema.TypeString,
121 Optional: true,
122 ForceNew: true,
123 },
124 "immutable_field_b": &tfschema.Schema{
125 Type: tfschema.TypeString,
126 Optional: true,
127 ForceNew: true,
128 },
129 "mutable_field": &tfschema.Schema{
130 Type: tfschema.TypeString,
131 Optional: true,
132 },
133 },
134 },
135 },
136 },
137 },
138 },
139 expectedRet: map[string]interface{}{
140 "imported_field": "imported_val",
141 "primitive_field": "primitive_val",
142 "list_of_primitives_field": []interface{}{
143 "list_of_primitives_val_0",
144 },
145 "map_field": map[string]interface{}{
146 "map_key_a": "map_val_a",
147 },
148 "nested_object_field": []interface{}{
149 map[string]interface{}{
150 "immutable_field": "immutable_val",
151 },
152 },
153 "list_of_objects_field": []interface{}{
154 map[string]interface{}{
155 "immutable_field_a": "immutable_val_a",
156 "immutable_field_b": "immutable_val_b",
157 },
158 map[string]interface{}{
159 "immutable_field_a": "immutable_val_a",
160 "immutable_field_b": "",
161 },
162 },
163 },
164 },
165 {
166 name: "mutable-but-unreadable fields; none set in annotation",
167 imported: map[string]interface{}{
168 "imported_field": "imported_val",
169 },
170 resource: &krmtotf.Resource{
171 Resource: k8s.Resource{
172 ObjectMeta: metav1.ObjectMeta{
173 Annotations: map[string]string{
174 k8s.MutableButUnreadableFieldsAnnotation: `{}`,
175 },
176 },
177 },
178 TFResource: &tfschema.Resource{
179 Schema: map[string]*tfschema.Schema{
180 "imported_field": &tfschema.Schema{
181 Type: tfschema.TypeString,
182 Optional: true,
183 },
184 "primitive_field": &tfschema.Schema{
185 Type: tfschema.TypeString,
186 Optional: true,
187 },
188 },
189 },
190 ResourceConfig: v1alpha1.ResourceConfig{
191 MutableButUnreadableFields: []string{
192 "primitive_field",
193 },
194 },
195 },
196 expectedRet: map[string]interface{}{
197 "imported_field": "imported_val",
198 },
199 },
200 {
201 name: "mutable-but-unreadable fields",
202 imported: map[string]interface{}{
203 "imported_field": "imported_val",
204 },
205 resource: &krmtotf.Resource{
206 Resource: k8s.Resource{
207 ObjectMeta: metav1.ObjectMeta{
208 Annotations: map[string]string{
209 k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"primitiveField":"primitive_val","listOfPrimitivesField":["list_of_primitives_val_0"],"mapField":{"map_key_a":"map_val_a"},"nestedObjectField":{"mutableButUnreadableField":"mutable_but_unreadable_val"}}}`,
210 },
211 },
212 },
213 TFResource: &tfschema.Resource{
214 Schema: map[string]*tfschema.Schema{
215 "imported_field": &tfschema.Schema{
216 Type: tfschema.TypeString,
217 Optional: true,
218 },
219 "primitive_field": &tfschema.Schema{
220 Type: tfschema.TypeString,
221 Optional: true,
222 },
223 "list_of_primitives_field": &tfschema.Schema{
224 Type: tfschema.TypeList,
225 Optional: true,
226 Elem: &tfschema.Schema{
227 Type: tfschema.TypeString,
228 },
229 },
230 "map_field": &tfschema.Schema{
231 Type: tfschema.TypeMap,
232 Optional: true,
233 },
234 "nested_object_field": &tfschema.Schema{
235 Type: tfschema.TypeList,
236 MaxItems: 1,
237 Optional: true,
238 Elem: &tfschema.Resource{
239 Schema: map[string]*tfschema.Schema{
240 "mutable_but_unreadable_field": &tfschema.Schema{
241 Type: tfschema.TypeString,
242 Optional: true,
243 },
244 },
245 },
246 },
247 },
248 },
249 ResourceConfig: v1alpha1.ResourceConfig{
250 MutableButUnreadableFields: []string{
251 "primitive_field",
252 "list_of_primitives_field",
253 "map_field",
254 "nested_object_field.mutable_but_unreadable_field",
255 },
256 },
257 },
258 expectedRet: map[string]interface{}{
259 "imported_field": "imported_val",
260 "primitive_field": "primitive_val",
261 "list_of_primitives_field": []interface{}{
262 "list_of_primitives_val_0",
263 },
264 "map_field": map[string]interface{}{
265 "map_key_a": "map_val_a",
266 },
267 "nested_object_field": []interface{}{
268 map[string]interface{}{
269 "mutable_but_unreadable_field": "mutable_but_unreadable_val",
270 },
271 },
272 },
273 },
274 {
275 name: "mutable-but-unreadable fields; annotation values differ from spec values",
276 imported: map[string]interface{}{
277 "imported_field": "imported_val",
278 },
279 resource: &krmtotf.Resource{
280 Resource: k8s.Resource{
281 ObjectMeta: metav1.ObjectMeta{
282 Annotations: map[string]string{
283 k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"primitiveField":"primitive_val","listOfPrimitivesField":["list_of_primitives_val_0"],"mapField":{"map_key_a":"map_val_a"},"nestedObjectField":{"mutableButUnreadableField":"mutable_but_unreadable_val"}}}`,
284 },
285 },
286 Spec: map[string]interface{}{
287 "primitiveField": "primitive_val_from_spec",
288 "listOfPrimitivesField": []interface{}{
289 "list_of_primitives_val_0_from_spec",
290 },
291 "mapField": map[string]interface{}{
292 "map_key_a": "map_val_a_from_spec",
293 },
294 "nestedObjectField": map[string]interface{}{
295 "mutableButUnreadableField": "mutable_but_unreadable_val_from_spec",
296 },
297 },
298 },
299 TFResource: &tfschema.Resource{
300 Schema: map[string]*tfschema.Schema{
301 "imported_field": &tfschema.Schema{
302 Type: tfschema.TypeString,
303 Optional: true,
304 },
305 "primitive_field": &tfschema.Schema{
306 Type: tfschema.TypeString,
307 Optional: true,
308 },
309 "list_of_primitives_field": &tfschema.Schema{
310 Type: tfschema.TypeList,
311 Optional: true,
312 Elem: &tfschema.Schema{
313 Type: tfschema.TypeString,
314 },
315 },
316 "map_field": &tfschema.Schema{
317 Type: tfschema.TypeMap,
318 Optional: true,
319 },
320 "nested_object_field": &tfschema.Schema{
321 Type: tfschema.TypeList,
322 MaxItems: 1,
323 Optional: true,
324 Elem: &tfschema.Resource{
325 Schema: map[string]*tfschema.Schema{
326 "mutable_but_unreadable_field": &tfschema.Schema{
327 Type: tfschema.TypeString,
328 Optional: true,
329 },
330 },
331 },
332 },
333 },
334 },
335 ResourceConfig: v1alpha1.ResourceConfig{
336 MutableButUnreadableFields: []string{
337 "primitive_field",
338 "list_of_primitives_field",
339 "map_field",
340 "nested_object_field.mutable_but_unreadable_field",
341 },
342 },
343 },
344 expectedRet: map[string]interface{}{
345 "imported_field": "imported_val",
346 "primitive_field": "primitive_val",
347 "list_of_primitives_field": []interface{}{
348 "list_of_primitives_val_0",
349 },
350 "map_field": map[string]interface{}{
351 "map_key_a": "map_val_a",
352 },
353 "nested_object_field": []interface{}{
354 map[string]interface{}{
355 "mutable_but_unreadable_field": "mutable_but_unreadable_val",
356 },
357 },
358 },
359 },
360 {
361 name: "mutable-but-unreadable fields; field in nested object that is set in imported state",
362 imported: map[string]interface{}{
363 "nested_object": []interface{}{
364 map[string]interface{}{
365 "field": "val",
366 },
367 },
368 },
369 resource: &krmtotf.Resource{
370 Resource: k8s.Resource{
371 ObjectMeta: metav1.ObjectMeta{
372 Annotations: map[string]string{
373 k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"nestedObject":{"mutableButUnreadableField":"mutable_but_unreadable_val"}}}`,
374 },
375 },
376 },
377 TFResource: &tfschema.Resource{
378 Schema: map[string]*tfschema.Schema{
379 "nested_object": &tfschema.Schema{
380 Type: tfschema.TypeList,
381 MaxItems: 1,
382 Optional: true,
383 Elem: &tfschema.Resource{
384 Schema: map[string]*tfschema.Schema{
385 "field": &tfschema.Schema{
386 Type: tfschema.TypeString,
387 Optional: true,
388 },
389 "mutable_but_unreadable_field": &tfschema.Schema{
390 Type: tfschema.TypeString,
391 Optional: true,
392 },
393 },
394 },
395 },
396 },
397 },
398 ResourceConfig: v1alpha1.ResourceConfig{
399 MutableButUnreadableFields: []string{
400 "nested_object.mutable_but_unreadable_field",
401 },
402 },
403 },
404 expectedRet: map[string]interface{}{
405 "nested_object": []interface{}{
406 map[string]interface{}{
407 "field": "val",
408 "mutable_but_unreadable_field": "mutable_but_unreadable_val",
409 },
410 },
411 },
412 },
413 {
414 name: "mutable-but-unreadable fields; annotation not set (new KCC resource or backwards compatibility scenario); should get mutable-but-unreadable fields from spec",
415 imported: map[string]interface{}{
416 "imported_field": "imported_val",
417 },
418 resource: &krmtotf.Resource{
419 Resource: k8s.Resource{
420 Spec: map[string]interface{}{
421 "primitiveField": "primitive_val",
422 "listOfPrimitivesField": []interface{}{
423 "list_of_primitives_val_0",
424 },
425 "mapField": map[string]interface{}{
426 "map_key_a": "map_val_a",
427 },
428 "nestedObjectField": map[string]interface{}{
429 "mutableButUnreadableField": "mutable_but_unreadable_val",
430 },
431 },
432 },
433 TFResource: &tfschema.Resource{
434 Schema: map[string]*tfschema.Schema{
435 "imported_field": &tfschema.Schema{
436 Type: tfschema.TypeString,
437 Optional: true,
438 },
439 "primitive_field": &tfschema.Schema{
440 Type: tfschema.TypeString,
441 Optional: true,
442 },
443 "list_of_primitives_field": &tfschema.Schema{
444 Type: tfschema.TypeList,
445 Optional: true,
446 Elem: &tfschema.Schema{
447 Type: tfschema.TypeString,
448 },
449 },
450 "map_field": &tfschema.Schema{
451 Type: tfschema.TypeMap,
452 Optional: true,
453 },
454 "nested_object_field": &tfschema.Schema{
455 Type: tfschema.TypeList,
456 MaxItems: 1,
457 Optional: true,
458 Elem: &tfschema.Resource{
459 Schema: map[string]*tfschema.Schema{
460 "mutable_but_unreadable_field": &tfschema.Schema{
461 Type: tfschema.TypeString,
462 Optional: true,
463 },
464 },
465 },
466 },
467 },
468 },
469 ResourceConfig: v1alpha1.ResourceConfig{
470 MutableButUnreadableFields: []string{
471 "primitive_field",
472 "list_of_primitives_field",
473 "map_field",
474 "nested_object_field.mutable_but_unreadable_field",
475 },
476 },
477 },
478 expectedRet: map[string]interface{}{
479 "imported_field": "imported_val",
480 "primitive_field": "primitive_val",
481 "list_of_primitives_field": []interface{}{
482 "list_of_primitives_val_0",
483 },
484 "map_field": map[string]interface{}{
485 "map_key_a": "map_val_a",
486 },
487 "nested_object_field": []interface{}{
488 map[string]interface{}{
489 "mutable_but_unreadable_field": "mutable_but_unreadable_val",
490 },
491 },
492 },
493 },
494 {
495 name: "directives",
496 imported: map[string]interface{}{
497 "imported_field": "imported_val",
498 },
499 resource: &krmtotf.Resource{
500 Resource: k8s.Resource{
501 ObjectMeta: metav1.ObjectMeta{
502 Annotations: map[string]string{
503 k8s.FormatAnnotation("directive-field-b"): "directive_val_b",
504 },
505 },
506 },
507 TFResource: &tfschema.Resource{
508 Schema: map[string]*tfschema.Schema{
509 "imported_field": &tfschema.Schema{
510 Type: tfschema.TypeString,
511 Optional: true,
512 },
513 "directive_field_b": &tfschema.Schema{
514 Type: tfschema.TypeString,
515 Optional: true,
516 },
517 "directive_field_c": &tfschema.Schema{
518 Type: tfschema.TypeString,
519 Optional: true,
520 Default: "directive_val_c",
521 },
522 },
523 },
524 ResourceConfig: v1alpha1.ResourceConfig{
525 Directives: []string{
526 "directive_field_b",
527 "directive_field_c",
528 },
529 },
530 },
531 expectedRet: map[string]interface{}{
532 "imported_field": "imported_val",
533 "directive_field_b": "directive_val_b",
534 "directive_field_c": "directive_val_c",
535 },
536 },
537 {
538 name: "status fields",
539 imported: map[string]interface{}{
540 "imported_field": "imported_val",
541 },
542 resource: &krmtotf.Resource{
543 Resource: k8s.Resource{
544 Status: map[string]interface{}{
545 "primitiveField": "val_b",
546 "listOfPrimitivesField": []interface{}{
547 "list_of_primitives_val_0",
548 },
549 "mapField": map[string]interface{}{
550 "map_key_a": "map_val_a",
551 },
552 "nestedObjectField": map[string]interface{}{
553 "field": "val",
554 },
555 "listOfObjectsField": []interface{}{
556 map[string]interface{}{
557 "fieldA": "val_a",
558 },
559 map[string]interface{}{
560 "fieldB": "val_b",
561 },
562 },
563 },
564 },
565 TFResource: &tfschema.Resource{
566 Schema: map[string]*tfschema.Schema{
567 "imported_field": &tfschema.Schema{
568 Type: tfschema.TypeString,
569 },
570 "primitive_field": &tfschema.Schema{
571 Type: tfschema.TypeString,
572 },
573 "list_of_primitives_field": &tfschema.Schema{
574 Type: tfschema.TypeList,
575 Elem: &tfschema.Schema{
576 Type: tfschema.TypeString,
577 },
578 },
579 "map_field": &tfschema.Schema{
580 Type: tfschema.TypeMap,
581 },
582 "nested_object_field": &tfschema.Schema{
583 Type: tfschema.TypeList,
584 MaxItems: 1,
585 Elem: &tfschema.Resource{
586 Schema: map[string]*tfschema.Schema{
587 "field": &tfschema.Schema{
588 Type: tfschema.TypeString,
589 },
590 },
591 },
592 },
593 "list_of_objects_field": &tfschema.Schema{
594 Type: tfschema.TypeList,
595 Elem: &tfschema.Resource{
596 Schema: map[string]*tfschema.Schema{
597 "field_a": &tfschema.Schema{
598 Type: tfschema.TypeString,
599 },
600 "field_b": &tfschema.Schema{
601 Type: tfschema.TypeString,
602 },
603 },
604 },
605 },
606 },
607 },
608 },
609 expectedRet: map[string]interface{}{
610 "imported_field": "imported_val",
611 "primitive_field": "val_b",
612 "list_of_primitives_field": []interface{}{
613 "list_of_primitives_val_0",
614 },
615 "map_field": map[string]interface{}{
616 "map_key_a": "map_val_a",
617 },
618 "nested_object_field": []interface{}{
619 map[string]interface{}{
620 "field": "val",
621 },
622 },
623 "list_of_objects_field": []interface{}{
624 map[string]interface{}{
625 "field_a": "val_a",
626 },
627 map[string]interface{}{
628 "field_b": "val_b",
629 },
630 },
631 },
632 },
633 {
634 name: "if the object is marked for deletion, withPresetFieldsForRead should not return an error when a referenced secret does not exist",
635 resource: &krmtotf.Resource{
636 Resource: k8s.Resource{
637 ObjectMeta: metav1.ObjectMeta{
638 DeletionTimestamp: &nowTime,
639 },
640 Spec: map[string]interface{}{
641 "primitiveField": "primitive_val",
642 "sensitiveField": map[string]interface{}{
643 "valueFrom": map[string]interface{}{
644 "secretKeyRef": map[string]interface{}{
645 "name": "secret1",
646 "key": "secret-key1",
647 },
648 },
649 },
650 },
651 },
652 TFResource: &tfschema.Resource{
653 Schema: map[string]*tfschema.Schema{
654 "sensitive_field": &tfschema.Schema{
655 Type: tfschema.TypeString,
656 Optional: true,
657 Sensitive: true,
658 },
659 "primitive_field": &tfschema.Schema{
660 Type: tfschema.TypeString,
661 Optional: true,
662 ForceNew: true,
663 },
664 },
665 },
666 },
667 expectedRet: map[string]interface{}{
668 "primitive_field": "primitive_val",
669 },
670 },
671 }
672
673 for _, tc := range tests {
674 tc := tc
675 t.Run(tc.name, func(t *testing.T) {
676 t.Parallel()
677 testId := testvariable.NewUniqueId()
678 c := mgr.GetClient()
679
680 testcontroller.EnsureNamespaceExistsT(t, c, testId)
681 tc.resource.SetNamespace(testId)
682 ret, err := krmtotf.WithFieldsPresetForRead(tc.imported, tc.resource, mgr.GetClient(), nil)
683 if err != nil {
684 t.Fatal(err)
685 }
686 if !test.Equals(t, tc.expectedRet, ret) {
687 diff := cmp.Diff(tc.expectedRet, ret)
688 t.Fatalf("actual result did not match expected result; diff (-expected +actual):\n%v", diff)
689 }
690 })
691 }
692 }
693
694 func TestWithFieldsPresetForReadMutableUnreadableSensitiveFields(t *testing.T) {
695
696 importedState := map[string]interface{}{
697 "imported_field": "imported_val",
698 }
699 tfResource := &tfschema.Resource{
700 Schema: map[string]*tfschema.Schema{
701 "imported_field": &tfschema.Schema{
702 Type: tfschema.TypeString,
703 Optional: true,
704 },
705 "sensitive_field": &tfschema.Schema{
706 Type: tfschema.TypeString,
707 Optional: true,
708 Sensitive: true,
709 },
710 "nested_object_field": &tfschema.Schema{
711 Type: tfschema.TypeList,
712 MaxItems: 1,
713 Optional: true,
714 Elem: &tfschema.Resource{
715 Schema: map[string]*tfschema.Schema{
716 "sensitive_field": &tfschema.Schema{
717 Type: tfschema.TypeString,
718 Optional: true,
719 Sensitive: true,
720 },
721 },
722 },
723 },
724 },
725 }
726 resourceConfig := v1alpha1.ResourceConfig{
727 MutableButUnreadableFields: []string{
728 "sensitive_field",
729 "nested_object_field.sensitive_field",
730 },
731 }
732
733 type versionStatus int
734 const (
735 UP_TO_DATE versionStatus = iota
736 OUTDATED
737 NOT_FOUND
738 )
739 tests := []struct {
740 name string
741 imported map[string]interface{}
742
743
744
745
746
747
748
749
750
751
752 observedSecretVersions map[string]versionStatus
753
754 resource *krmtotf.Resource
755 referencedSecrets []*unstructured.Unstructured
756 expectedRet map[string]interface{}
757 }{
758 {
759 name: "sensitive fields with values from Secrets in mutable-but-unreadable-fields, and observed-secret-versions is up-to-date",
760 imported: importedState,
761 observedSecretVersions: map[string]versionStatus{
762 "secret1": UP_TO_DATE,
763 "secret2": UP_TO_DATE,
764 },
765 resource: &krmtotf.Resource{
766 Resource: k8s.Resource{
767 ObjectMeta: metav1.ObjectMeta{
768 Annotations: map[string]string{
769 k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret1","key":"secret-key1"}}},"nestedObjectField":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret2","key":"secret-key2"}}}}}}`,
770 },
771 },
772 },
773 TFResource: tfResource,
774 ResourceConfig: resourceConfig,
775 },
776
777 referencedSecrets: []*unstructured.Unstructured{
778 test.NewSecretUnstructured("secret1", "", map[string]interface{}{"secret-key1": "secret-val1"}),
779 test.NewSecretUnstructured("secret2", "", map[string]interface{}{"secret-key2": "secret-val2"}),
780 },
781 expectedRet: map[string]interface{}{
782 "imported_field": "imported_val",
783 "sensitive_field": "secret-val1",
784 "nested_object_field": []interface{}{
785 map[string]interface{}{
786 "sensitive_field": "secret-val2",
787 },
788 },
789 },
790 },
791 {
792 name: "sensitive fields with values from Secrets in mutable-but-unreadable-fields, but observed-secret-versions is outdated",
793 imported: importedState,
794 observedSecretVersions: map[string]versionStatus{
795 "secret1": OUTDATED,
796 "secret2": OUTDATED,
797 },
798 resource: &krmtotf.Resource{
799 Resource: k8s.Resource{
800 ObjectMeta: metav1.ObjectMeta{
801 Annotations: map[string]string{
802 k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret1","key":"secret-key1"}}},"nestedObjectField":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret2","key":"secret-key2"}}}}}}`,
803 },
804 },
805 },
806 TFResource: tfResource,
807 ResourceConfig: resourceConfig,
808 },
809 referencedSecrets: []*unstructured.Unstructured{
810 test.NewSecretUnstructured("secret1", "", map[string]interface{}{"secret-key1": "secret-val1"}),
811 test.NewSecretUnstructured("secret2", "", map[string]interface{}{"secret-key2": "secret-val2"}),
812 },
813 expectedRet: map[string]interface{}{
814 "imported_field": "imported_val",
815 },
816 },
817 {
818 name: "sensitive fields with values from Secrets in mutable-but-unreadable-fields, but Secrets are not found",
819 imported: importedState,
820 observedSecretVersions: map[string]versionStatus{
821 "secret1": NOT_FOUND,
822 "secret2": NOT_FOUND,
823 },
824 resource: &krmtotf.Resource{
825 Resource: k8s.Resource{
826 ObjectMeta: metav1.ObjectMeta{
827 Annotations: map[string]string{
828 k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret1","key":"secret-key1"}}},"nestedObjectField":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret2","key":"secret-key2"}}}}}}`,
829 },
830 },
831 },
832 TFResource: tfResource,
833 ResourceConfig: resourceConfig,
834 },
835 expectedRet: map[string]interface{}{
836 "imported_field": "imported_val",
837 },
838 },
839 {
840 name: "sensitive fields with values from Secrets in mutable-but-unreadable-fields, and observed-secret-versions is up-to-date, but keys can't be found in Secrets",
841 imported: importedState,
842 observedSecretVersions: map[string]versionStatus{
843 "secret1": UP_TO_DATE,
844 "secret2": UP_TO_DATE,
845 },
846 resource: &krmtotf.Resource{
847 Resource: k8s.Resource{
848 ObjectMeta: metav1.ObjectMeta{
849 Annotations: map[string]string{
850 k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret1","key":"secret-key1"}}},"nestedObjectField":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret2","key":"secret-key2"}}}}}}`,
851 },
852 },
853 },
854 TFResource: tfResource,
855 ResourceConfig: resourceConfig,
856 },
857 referencedSecrets: []*unstructured.Unstructured{
858 test.NewSecretUnstructured("secret1", "", map[string]interface{}{"unused-secret-key1": "secret-val1"}),
859 test.NewSecretUnstructured("secret2", "", map[string]interface{}{"unused-secret-key2": "secret-val2"}),
860 },
861 expectedRet: map[string]interface{}{
862 "imported_field": "imported_val",
863 },
864 },
865 {
866 name: "sensitive fields with simple values in mutable-but-unreadable-fields",
867 imported: importedState,
868 resource: &krmtotf.Resource{
869 Resource: k8s.Resource{
870 ObjectMeta: metav1.ObjectMeta{
871 Annotations: map[string]string{
872 k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"sensitiveField":{"value":"sensitive_val"},"nestedObjectField":{"sensitiveField":{"value":"nested_sensitive_val"}}}}`,
873 },
874 },
875 },
876 TFResource: tfResource,
877 ResourceConfig: resourceConfig,
878 },
879 expectedRet: map[string]interface{}{
880 "imported_field": "imported_val",
881 "sensitive_field": "sensitive_val",
882 "nested_object_field": []interface{}{
883 map[string]interface{}{
884 "sensitive_field": "nested_sensitive_val",
885 },
886 },
887 },
888 },
889 {
890 name: "mutable-but-unreadable-fields and observed-secret-versions annotations not set (new KCC resource or backwards compatibility scenario); should get sensitive, mutable, unreadable fields from spec (where sensitive fields are from Secrets)",
891 imported: importedState,
892 resource: &krmtotf.Resource{
893 Resource: k8s.Resource{
894 Spec: map[string]interface{}{
895 "sensitiveField": map[string]interface{}{
896 "valueFrom": map[string]interface{}{
897 "secretKeyRef": map[string]interface{}{
898 "name": "secret1",
899 "key": "secret-key1",
900 },
901 },
902 },
903 "nestedObjectField": map[string]interface{}{
904 "sensitiveField": map[string]interface{}{
905 "valueFrom": map[string]interface{}{
906 "secretKeyRef": map[string]interface{}{
907 "name": "secret2",
908 "key": "secret-key2",
909 },
910 },
911 },
912 },
913 },
914 },
915 TFResource: tfResource,
916 ResourceConfig: resourceConfig,
917 },
918 referencedSecrets: []*unstructured.Unstructured{
919 test.NewSecretUnstructured("secret1", "", map[string]interface{}{"secret-key1": "secret-val1"}),
920 test.NewSecretUnstructured("secret2", "", map[string]interface{}{"secret-key2": "secret-val2"}),
921 },
922 expectedRet: map[string]interface{}{
923 "imported_field": "imported_val",
924 "sensitive_field": "secret-val1",
925 "nested_object_field": []interface{}{
926 map[string]interface{}{
927 "sensitive_field": "secret-val2",
928 },
929 },
930 },
931 },
932 {
933 name: "mutable-but-unreadable-fields and observed-secret-versions annotations not set (new KCC resource or backwards compatibility scenario); should get sensitive, mutable, unreadable fields from spec (where sensitive fields are simple values)",
934 imported: importedState,
935 resource: &krmtotf.Resource{
936 Resource: k8s.Resource{
937 Spec: map[string]interface{}{
938 "sensitiveField": map[string]interface{}{
939 "value": "sensitive_val",
940 },
941 "nestedObjectField": map[string]interface{}{
942 "sensitiveField": map[string]interface{}{
943 "value": "nested_sensitive_val",
944 },
945 },
946 },
947 },
948 TFResource: tfResource,
949 ResourceConfig: resourceConfig,
950 },
951 expectedRet: map[string]interface{}{
952 "imported_field": "imported_val",
953 "sensitive_field": "sensitive_val",
954 "nested_object_field": []interface{}{
955 map[string]interface{}{
956 "sensitive_field": "nested_sensitive_val",
957 },
958 },
959 },
960 },
961 {
962 name: "mutable-but-unreadable-fields annotation values differ from spec values (where sensitive fields in annotation are from Secrets, but sensitive fields in Spec are simple values)",
963 imported: importedState,
964 observedSecretVersions: map[string]versionStatus{
965 "secret1": UP_TO_DATE,
966 "secret2": UP_TO_DATE,
967 },
968 resource: &krmtotf.Resource{
969 Resource: k8s.Resource{
970 ObjectMeta: metav1.ObjectMeta{
971 Annotations: map[string]string{
972 k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret1","key":"secret-key1"}}},"nestedObjectField":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret2","key":"secret-key2"}}}}}}`,
973 },
974 },
975 Spec: map[string]interface{}{
976 "sensitiveField": map[string]interface{}{
977 "value": "sensitive_val",
978 },
979 "nestedObjectField": map[string]interface{}{
980 "sensitiveField": map[string]interface{}{
981 "value": "nested_sensitive_val",
982 },
983 },
984 },
985 },
986 TFResource: tfResource,
987 ResourceConfig: resourceConfig,
988 },
989 referencedSecrets: []*unstructured.Unstructured{
990 test.NewSecretUnstructured("secret1", "", map[string]interface{}{"secret-key1": "secret-val1"}),
991 test.NewSecretUnstructured("secret2", "", map[string]interface{}{"secret-key2": "secret-val2"}),
992 },
993 expectedRet: map[string]interface{}{
994 "imported_field": "imported_val",
995 "sensitive_field": "secret-val1",
996 "nested_object_field": []interface{}{
997 map[string]interface{}{
998 "sensitive_field": "secret-val2",
999 },
1000 },
1001 },
1002 },
1003 {
1004 name: "mutable-but-unreadable-fields annotation values differ from spec values (where sensitive fields in annotation are simple values, but sensitive fields in Spec are from Secrets)",
1005 imported: importedState,
1006 observedSecretVersions: map[string]versionStatus{},
1007 resource: &krmtotf.Resource{
1008 Resource: k8s.Resource{
1009 ObjectMeta: metav1.ObjectMeta{
1010 Annotations: map[string]string{
1011 k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"sensitiveField":{"value":"sensitive_val"},"nestedObjectField":{"sensitiveField":{"value":"nested_sensitive_val"}}}}`,
1012 },
1013 },
1014 Spec: map[string]interface{}{
1015 "sensitiveField": map[string]interface{}{
1016 "valueFrom": map[string]interface{}{
1017 "secretKeyRef": map[string]interface{}{
1018 "name": "secret1",
1019 "key": "secret-key1",
1020 },
1021 },
1022 },
1023 "nestedObjectField": map[string]interface{}{
1024 "sensitiveField": map[string]interface{}{
1025 "valueFrom": map[string]interface{}{
1026 "secretKeyRef": map[string]interface{}{
1027 "name": "secret2",
1028 "key": "secret-key2",
1029 },
1030 },
1031 },
1032 },
1033 },
1034 },
1035 TFResource: tfResource,
1036 ResourceConfig: resourceConfig,
1037 },
1038 referencedSecrets: []*unstructured.Unstructured{
1039 test.NewSecretUnstructured("secret1", "", map[string]interface{}{"secret-key1": "secret-val1"}),
1040 test.NewSecretUnstructured("secret2", "", map[string]interface{}{"secret-key2": "secret-val2"}),
1041 },
1042 expectedRet: map[string]interface{}{
1043 "imported_field": "imported_val",
1044 "sensitive_field": "sensitive_val",
1045 "nested_object_field": []interface{}{
1046 map[string]interface{}{
1047 "sensitive_field": "nested_sensitive_val",
1048 },
1049 },
1050 },
1051 },
1052 }
1053
1054 for _, tc := range tests {
1055 tc := tc
1056 t.Run(tc.name, func(t *testing.T) {
1057 t.Parallel()
1058 testId := testvariable.NewUniqueId()
1059 c := mgr.GetClient()
1060
1061 testcontroller.EnsureNamespaceExistsT(t, c, testId)
1062 tc.resource.SetNamespace(testId)
1063 for _, obj := range tc.referencedSecrets {
1064 obj.SetNamespace(testId)
1065 }
1066 test.EnsureObjectsExist(t, tc.referencedSecrets, c)
1067
1068
1069 if tc.observedSecretVersions != nil {
1070 secretVersions := make(map[string]string)
1071 for secretName, status := range tc.observedSecretVersions {
1072 if status == NOT_FOUND {
1073 secretVersions[secretName] = "12345"
1074 continue
1075 }
1076 version, err := getResourceVersionOfSecret(secretName, testId, c)
1077 if err != nil {
1078 t.Fatalf("error determining version of Secret %v: %v", secretName, err)
1079 }
1080 switch status {
1081 case UP_TO_DATE:
1082 secretVersions[secretName] = version
1083 case OUTDATED:
1084 secretVersions[secretName] = version + "0"
1085 }
1086 }
1087 b, err := json.Marshal(secretVersions)
1088 if err != nil {
1089 t.Fatalf("error marshalling secret versions map: %v", err)
1090 }
1091 k8s.SetAnnotation(k8s.ObservedSecretVersionsAnnotation, string(b), tc.resource)
1092 }
1093
1094 ret, err := krmtotf.WithFieldsPresetForRead(tc.imported, tc.resource, mgr.GetClient(), nil)
1095 if err != nil {
1096 t.Fatal(err)
1097 }
1098 if !test.Equals(t, tc.expectedRet, ret) {
1099 diff := cmp.Diff(tc.expectedRet, ret)
1100 t.Fatalf("actual result did not match expected result; diff (-expected +actual):\n%v", diff)
1101 }
1102 })
1103 }
1104 }
1105
View as plain text