1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package webhook
16
17 import (
18 "container/list"
19 "fmt"
20 "net/http"
21 "reflect"
22 "testing"
23
24 corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
25 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/iam/v1beta1"
26 dclmetadata "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/metadata"
27 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
28 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
29 testutil "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test"
30 testdclschemaloader "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/dclschemaloader"
31 testservicemetadataloader "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/servicemetadataloader"
32 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
33
34 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
35 "github.com/hashicorp/terraform-provider-google-beta/google-beta"
36 "github.com/nasa9084/go-openapi"
37 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
38 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
39 k8sschema "k8s.io/apimachinery/pkg/runtime/schema"
40 "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
41 )
42
43 func TestChangesOnImmutableFields(t *testing.T) {
44 t.Parallel()
45 p := google.Provider()
46 v := newImmutableFieldsValidatorHandler(t)
47 for _, testcase := range TestCases {
48 t.Run(testcase.Name, func(t *testing.T) {
49 assertImmutableFieldsValidatorResult(t, v, p, testcase)
50 })
51 }
52 }
53
54 func TestChangesOnImmutableFieldsForDCLResource(t *testing.T) {
55 tests := []struct {
56 name string
57 obj *unstructured.Unstructured
58 oldObj *unstructured.Unstructured
59 spec map[string]interface{}
60 oldSpec map[string]interface{}
61 schema *openapi.Schema
62 response admission.Response
63 }{
64 {
65 name: "changes on the base level immutable fields",
66 obj: &unstructured.Unstructured{
67 Object: map[string]interface{}{
68 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
69 "kind": "Test1Bar",
70 "metadata": map[string]interface{}{
71 "annotations": map[string]interface{}{
72 k8s.ProjectIDAnnotation: "my-project-1",
73 },
74 },
75 },
76 },
77 oldObj: &unstructured.Unstructured{
78 Object: map[string]interface{}{
79 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
80 "kind": "Test1Bar",
81 "metadata": map[string]interface{}{
82 "annotations": map[string]interface{}{
83 k8s.ProjectIDAnnotation: "my-project-1",
84 },
85 },
86 },
87 },
88
89 oldSpec: map[string]interface{}{
90 "location": "US",
91 },
92 schema: &openapi.Schema{
93 Type: "object",
94 Properties: map[string]*openapi.Schema{
95 "location": &openapi.Schema{
96 Type: "string",
97 Extension: map[string]interface{}{
98 "x-kubernetes-immutable": true,
99 },
100 },
101 },
102 Extension: map[string]interface{}{
103 "x-dcl-parent-container": "project",
104 },
105 },
106 response: admission.Errored(http.StatusForbidden,
107 k8s.NewImmutableFieldsMutationError([]string{"spec.location"})),
108 },
109 {
110 name: "changes on the resourceID field",
111 obj: &unstructured.Unstructured{
112 Object: map[string]interface{}{
113 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
114 "kind": "Test1Bar",
115 "metadata": map[string]interface{}{
116 "annotations": map[string]interface{}{
117 k8s.ProjectIDAnnotation: "my-project-1",
118 },
119 },
120 },
121 },
122 oldObj: &unstructured.Unstructured{
123 Object: map[string]interface{}{
124 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
125 "kind": "Test1Bar",
126 "metadata": map[string]interface{}{
127 "annotations": map[string]interface{}{
128 k8s.ProjectIDAnnotation: "my-project-1",
129 },
130 },
131 },
132 },
133 spec: map[string]interface{}{
134 "resourceID": "name1",
135 },
136 oldSpec: map[string]interface{}{
137 "resourceID": "name2",
138 },
139 schema: &openapi.Schema{
140 Type: "object",
141 Properties: map[string]*openapi.Schema{
142 "name": &openapi.Schema{
143 Type: "string",
144 },
145 },
146 Extension: map[string]interface{}{
147 "x-dcl-parent-container": "project",
148 },
149 },
150 response: admission.Errored(http.StatusForbidden,
151 k8s.NewImmutableFieldsMutationError([]string{"spec.resourceID"})),
152 },
153 {
154 name: "changes on nested immutable fields",
155 obj: &unstructured.Unstructured{
156 Object: map[string]interface{}{
157 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
158 "kind": "Test1Bar",
159 "metadata": map[string]interface{}{
160 "annotations": map[string]interface{}{
161 k8s.ProjectIDAnnotation: "my-project-1",
162 },
163 },
164 },
165 },
166 oldObj: &unstructured.Unstructured{
167 Object: map[string]interface{}{
168 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
169 "kind": "Test1Bar",
170 "metadata": map[string]interface{}{
171 "annotations": map[string]interface{}{
172 k8s.ProjectIDAnnotation: "my-project-1",
173 },
174 },
175 },
176 },
177 spec: map[string]interface{}{
178 "location": "EU",
179 "nestedObjectKey": map[string]interface{}{
180 "nestedIntField": 1,
181 "nestedStringField": "strval1",
182 },
183 },
184 oldSpec: map[string]interface{}{
185 "location": "EU",
186 "nestedObjectKey": map[string]interface{}{
187 "nestedIntField": 2,
188 "nestedStringField": "strval2",
189 },
190 },
191 schema: &openapi.Schema{
192 Type: "object",
193 Properties: map[string]*openapi.Schema{
194 "location": &openapi.Schema{
195 Type: "string",
196 Extension: map[string]interface{}{
197 "x-kubernetes-immutable": true,
198 },
199 },
200 "nestedObjectKey": &openapi.Schema{
201 Type: "object",
202 Properties: map[string]*openapi.Schema{
203 "nestedIntField": &openapi.Schema{
204 Type: "integer",
205 Extension: map[string]interface{}{
206 "x-kubernetes-immutable": true,
207 },
208 },
209 "nestedStringField": &openapi.Schema{
210 Type: "string",
211 },
212 },
213 },
214 },
215 Extension: map[string]interface{}{
216 "x-dcl-parent-container": "project",
217 },
218 },
219 response: admission.Errored(http.StatusForbidden,
220 k8s.NewImmutableFieldsMutationError([]string{"spec.nestedObjectKey.nestedIntField"})),
221 },
222 {
223 name: "changes on immutable resource reference",
224 obj: &unstructured.Unstructured{
225 Object: map[string]interface{}{
226 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
227 "kind": "Test1Bar",
228 },
229 },
230 oldObj: &unstructured.Unstructured{
231 Object: map[string]interface{}{
232 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
233 "kind": "Test1Bar",
234 },
235 },
236 spec: map[string]interface{}{
237 "referenceKeyRef": map[string]interface{}{
238 "name": "pubsubtopic-sample-1",
239 },
240 },
241 oldSpec: map[string]interface{}{
242 "referenceKeyRef": map[string]interface{}{
243 "name": "pubsubtopic-sample-2",
244 },
245 },
246 schema: &openapi.Schema{
247 Type: "object",
248 Properties: map[string]*openapi.Schema{
249 "referenceKey": {
250 Type: "string",
251 Extension: map[string]interface{}{
252 "x-dcl-references": []interface{}{
253 map[interface{}]interface{}{
254 "resource": "FakeService/FakeKind",
255 "field": "name",
256 },
257 },
258 "x-kubernetes-immutable": true,
259 },
260 },
261 },
262 },
263 response: admission.Errored(http.StatusForbidden,
264 k8s.NewImmutableFieldsMutationError([]string{"spec.referenceKeyRef"})),
265 },
266 {
267 name: "changes on immutable hierarchical reference for single-parent resource",
268 obj: &unstructured.Unstructured{
269 Object: map[string]interface{}{
270 "apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
271 "kind": "Test5ProjectRef",
272 },
273 },
274 oldObj: &unstructured.Unstructured{
275 Object: map[string]interface{}{
276 "apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
277 "kind": "Test5ProjectRef",
278 },
279 },
280 spec: map[string]interface{}{
281 "projectRef": map[string]interface{}{
282 "name": "project-sample-1",
283 },
284 },
285 oldSpec: map[string]interface{}{
286 "projectRef": map[string]interface{}{
287 "name": "project-sample-2",
288 },
289 },
290 schema: &openapi.Schema{
291 Type: "object",
292 Properties: map[string]*openapi.Schema{
293 "project": &openapi.Schema{
294 Type: "string",
295 Extension: map[string]interface{}{
296 "x-dcl-references": []interface{}{
297 map[interface{}]interface{}{
298 "field": "name",
299 "parent": true,
300 "resource": "Cloudresourcemanager/Project",
301 },
302 },
303 "x-kubernetes-immutable": true,
304 },
305 },
306 },
307 },
308 response: admission.Errored(http.StatusForbidden,
309 k8s.NewImmutableFieldsMutationError([]string{"spec.projectRef"})),
310 },
311 {
312 name: "changes on immutable hierarchical reference for multi-parent resource (value change)",
313 obj: &unstructured.Unstructured{
314 Object: map[string]interface{}{
315 "apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
316 "kind": "Test5MultipleRefs",
317 },
318 },
319 oldObj: &unstructured.Unstructured{
320 Object: map[string]interface{}{
321 "apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
322 "kind": "Test5MultipleRefs",
323 },
324 },
325 spec: map[string]interface{}{
326 "folderRef": map[string]interface{}{
327 "name": "folder-sample-1",
328 },
329 },
330 oldSpec: map[string]interface{}{
331 "folderRef": map[string]interface{}{
332 "name": "folder-sample-2",
333 },
334 },
335 schema: &openapi.Schema{
336 Type: "object",
337 Properties: map[string]*openapi.Schema{
338 "parent": &openapi.Schema{
339 Type: "string",
340 Extension: map[string]interface{}{
341 "x-dcl-references": []interface{}{
342 map[interface{}]interface{}{
343 "field": "name",
344 "parent": true,
345 "resource": "Cloudresourcemanager/Project",
346 },
347 map[interface{}]interface{}{
348 "field": "name",
349 "parent": true,
350 "resource": "Cloudresourcemanager/Folder",
351 },
352 map[interface{}]interface{}{
353 "field": "name",
354 "parent": true,
355 "resource": "Cloudresourcemanager/Organization",
356 },
357 },
358 "x-kubernetes-immutable": true,
359 },
360 },
361 },
362 },
363 response: admission.Errored(http.StatusForbidden,
364 k8s.NewImmutableFieldsMutationError([]string{"spec.folderRef"})),
365 },
366 {
367 name: "changes on immutable hierarchical reference for multi-parent resource (key change)",
368 obj: &unstructured.Unstructured{
369 Object: map[string]interface{}{
370 "apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
371 "kind": "Test5MultipleRefs",
372 },
373 },
374 oldObj: &unstructured.Unstructured{
375 Object: map[string]interface{}{
376 "apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
377 "kind": "Test5MultipleRefs",
378 },
379 },
380 spec: map[string]interface{}{
381 "folderRef": map[string]interface{}{
382 "name": "folder-sample-1",
383 },
384 },
385 oldSpec: map[string]interface{}{
386 "organizationRef": map[string]interface{}{
387 "name": "organization-sample-1",
388 },
389 },
390 schema: &openapi.Schema{
391 Type: "object",
392 Properties: map[string]*openapi.Schema{
393 "parent": &openapi.Schema{
394 Type: "string",
395 Extension: map[string]interface{}{
396 "x-dcl-references": []interface{}{
397 map[interface{}]interface{}{
398 "field": "name",
399 "parent": true,
400 "resource": "Cloudresourcemanager/Project",
401 },
402 map[interface{}]interface{}{
403 "field": "name",
404 "parent": true,
405 "resource": "Cloudresourcemanager/Folder",
406 },
407 map[interface{}]interface{}{
408 "field": "name",
409 "parent": true,
410 "resource": "Cloudresourcemanager/Organization",
411 },
412 },
413 "x-kubernetes-immutable": true,
414 },
415 },
416 },
417 },
418 response: admission.Errored(http.StatusForbidden,
419 k8s.NewImmutableFieldsMutationError([]string{"spec.folderRef", "spec.organizationRef"})),
420 },
421 {
422 name: "changes on mutable hierarchical reference for single-parent resource",
423 obj: &unstructured.Unstructured{
424 Object: map[string]interface{}{
425 "apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
426 "kind": "Test5ProjectRef",
427 },
428 },
429 oldObj: &unstructured.Unstructured{
430 Object: map[string]interface{}{
431 "apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
432 "kind": "Test5ProjectRef",
433 },
434 },
435 spec: map[string]interface{}{
436 "projectRef": map[string]interface{}{
437 "name": "project-sample-1",
438 },
439 },
440 oldSpec: map[string]interface{}{
441 "projectRef": map[string]interface{}{
442 "name": "project-sample-2",
443 },
444 },
445 schema: &openapi.Schema{
446 Type: "object",
447 Properties: map[string]*openapi.Schema{
448 "project": &openapi.Schema{
449 Type: "string",
450 Extension: map[string]interface{}{
451 "x-dcl-references": []interface{}{
452 map[interface{}]interface{}{
453 "field": "name",
454 "parent": true,
455 "resource": "Cloudresourcemanager/Project",
456 },
457 },
458 },
459 },
460 },
461 },
462 response: allowedResponse,
463 },
464 {
465 name: "changes on mutable hierarchical reference for multi-parent resource (value change)",
466 obj: &unstructured.Unstructured{
467 Object: map[string]interface{}{
468 "apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
469 "kind": "Test5MultipleRefs",
470 },
471 },
472 oldObj: &unstructured.Unstructured{
473 Object: map[string]interface{}{
474 "apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
475 "kind": "Test5MultipleRefs",
476 },
477 },
478 spec: map[string]interface{}{
479 "folderRef": map[string]interface{}{
480 "name": "folder-sample-1",
481 },
482 },
483 oldSpec: map[string]interface{}{
484 "folderRef": map[string]interface{}{
485 "name": "folder-sample-2",
486 },
487 },
488 schema: &openapi.Schema{
489 Type: "object",
490 Properties: map[string]*openapi.Schema{
491 "parent": &openapi.Schema{
492 Type: "string",
493 Extension: map[string]interface{}{
494 "x-dcl-references": []interface{}{
495 map[interface{}]interface{}{
496 "field": "name",
497 "parent": true,
498 "resource": "Cloudresourcemanager/Project",
499 },
500 map[interface{}]interface{}{
501 "field": "name",
502 "parent": true,
503 "resource": "Cloudresourcemanager/Folder",
504 },
505 map[interface{}]interface{}{
506 "field": "name",
507 "parent": true,
508 "resource": "Cloudresourcemanager/Organization",
509 },
510 },
511 },
512 },
513 },
514 },
515 response: allowedResponse,
516 },
517 {
518 name: "changes on mutable hierarchical reference for multi-parent resource (key change)",
519 obj: &unstructured.Unstructured{
520 Object: map[string]interface{}{
521 "apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
522 "kind": "Test5MultipleRefs",
523 },
524 },
525 oldObj: &unstructured.Unstructured{
526 Object: map[string]interface{}{
527 "apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
528 "kind": "Test5MultipleRefs",
529 },
530 },
531 spec: map[string]interface{}{
532 "folderRef": map[string]interface{}{
533 "name": "folder-sample-1",
534 },
535 },
536 oldSpec: map[string]interface{}{
537 "organizationRef": map[string]interface{}{
538 "name": "organization-sample-1",
539 },
540 },
541 schema: &openapi.Schema{
542 Type: "object",
543 Properties: map[string]*openapi.Schema{
544 "parent": &openapi.Schema{
545 Type: "string",
546 Extension: map[string]interface{}{
547 "x-dcl-references": []interface{}{
548 map[interface{}]interface{}{
549 "field": "name",
550 "parent": true,
551 "resource": "Cloudresourcemanager/Project",
552 },
553 map[interface{}]interface{}{
554 "field": "name",
555 "parent": true,
556 "resource": "Cloudresourcemanager/Folder",
557 },
558 map[interface{}]interface{}{
559 "field": "name",
560 "parent": true,
561 "resource": "Cloudresourcemanager/Organization",
562 },
563 },
564 },
565 },
566 },
567 },
568 response: allowedResponse,
569 },
570 {
571 name: "changes on immutable arrays of primitives",
572 obj: &unstructured.Unstructured{
573 Object: map[string]interface{}{
574 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
575 "kind": "Test1Bar",
576 "metadata": map[string]interface{}{
577 "annotations": map[string]interface{}{
578 k8s.ProjectIDAnnotation: "my-project-1",
579 },
580 },
581 },
582 },
583 oldObj: &unstructured.Unstructured{
584 Object: map[string]interface{}{
585 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
586 "kind": "Test1Bar",
587 "metadata": map[string]interface{}{
588 "annotations": map[string]interface{}{
589 k8s.ProjectIDAnnotation: "my-project-1",
590 },
591 },
592 },
593 },
594 spec: map[string]interface{}{
595 "stringArrayKey": []string{"val1"},
596 },
597 oldSpec: map[string]interface{}{
598 "stringArrayKey": []string{"val1", "val2"},
599 },
600 schema: &openapi.Schema{
601 Type: "object",
602 Properties: map[string]*openapi.Schema{
603 "stringArrayKey": &openapi.Schema{
604 Type: "array",
605 Items: &openapi.Schema{
606 Type: "string",
607 },
608 Extension: map[string]interface{}{
609 "x-kubernetes-immutable": true,
610 },
611 },
612 },
613 Extension: map[string]interface{}{
614 "x-dcl-parent-container": "project",
615 },
616 },
617 response: admission.Errored(http.StatusForbidden,
618 k8s.NewImmutableFieldsMutationError([]string{"spec.stringArrayKey"})),
619 },
620 {
621 name: "changes on immutable maps of primitives",
622 obj: &unstructured.Unstructured{
623 Object: map[string]interface{}{
624 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
625 "kind": "Test1Bar",
626 "metadata": map[string]interface{}{
627 "annotations": map[string]interface{}{
628 k8s.ProjectIDAnnotation: "my-project-1",
629 },
630 },
631 },
632 },
633 oldObj: &unstructured.Unstructured{
634 Object: map[string]interface{}{
635 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
636 "kind": "Test1Bar",
637 "metadata": map[string]interface{}{
638 "annotations": map[string]interface{}{
639 k8s.ProjectIDAnnotation: "my-project-1",
640 },
641 },
642 },
643 },
644 spec: map[string]interface{}{
645 "stringMapKey": map[string]interface{}{
646 "foo": "foo",
647 },
648 },
649 oldSpec: map[string]interface{}{
650 "stringMapKey": map[string]interface{}{
651 "foo": "bar",
652 },
653 },
654 schema: &openapi.Schema{
655 Type: "object",
656 Properties: map[string]*openapi.Schema{
657 "stringMapKey": &openapi.Schema{
658 Type: "object",
659 AdditionalProperties: &openapi.Schema{
660 Type: "string",
661 },
662 Extension: map[string]interface{}{
663 "x-kubernetes-immutable": true,
664 },
665 },
666 },
667 Extension: map[string]interface{}{
668 "x-dcl-parent-container": "project",
669 },
670 },
671 response: admission.Errored(http.StatusForbidden,
672 k8s.NewImmutableFieldsMutationError([]string{"spec.stringMapKey"})),
673 },
674 {
675 name: "no changes to immutable maps of objects",
676 obj: &unstructured.Unstructured{
677 Object: map[string]interface{}{
678 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
679 "kind": "Test1Bar",
680 "metadata": map[string]interface{}{
681 "annotations": map[string]interface{}{
682 k8s.ProjectIDAnnotation: "my-project-1",
683 },
684 },
685 },
686 },
687 oldObj: &unstructured.Unstructured{
688 Object: map[string]interface{}{
689 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
690 "kind": "Test1Bar",
691 "metadata": map[string]interface{}{
692 "annotations": map[string]interface{}{
693 k8s.ProjectIDAnnotation: "my-project-1",
694 },
695 },
696 },
697 },
698 spec: map[string]interface{}{
699 "objectMapKey": map[string]interface{}{
700 "obj1": map[string]interface{}{
701 "objectField": "foo1",
702 },
703 "obj2": map[string]interface{}{
704 "objectField": "foo2",
705 },
706 },
707 },
708 oldSpec: map[string]interface{}{
709 "objectMapKey": map[string]interface{}{
710 "obj1": map[string]interface{}{
711 "objectField": "foo1",
712 },
713 "obj2": map[string]interface{}{
714 "objectField": "foo2",
715 },
716 },
717 },
718 schema: &openapi.Schema{
719 Type: "object",
720 Properties: map[string]*openapi.Schema{
721 "objectMapKey": &openapi.Schema{
722 Type: "object",
723 AdditionalProperties: &openapi.Schema{
724 Type: "object",
725 Properties: map[string]*openapi.Schema{
726 "objectField": &openapi.Schema{
727 Type: "string",
728 },
729 },
730 },
731 Extension: map[string]interface{}{
732 "x-kubernetes-immutable": true,
733 },
734 },
735 },
736 Extension: map[string]interface{}{
737 "x-dcl-parent-container": "project",
738 },
739 },
740 response: allowedResponse,
741 },
742 {
743 name: "changes on immutable maps of objects (changed a value in one of the objects in the map)",
744 obj: &unstructured.Unstructured{
745 Object: map[string]interface{}{
746 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
747 "kind": "Test1Bar",
748 "metadata": map[string]interface{}{
749 "annotations": map[string]interface{}{
750 k8s.ProjectIDAnnotation: "my-project-1",
751 },
752 },
753 },
754 },
755 oldObj: &unstructured.Unstructured{
756 Object: map[string]interface{}{
757 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
758 "kind": "Test1Bar",
759 "metadata": map[string]interface{}{
760 "annotations": map[string]interface{}{
761 k8s.ProjectIDAnnotation: "my-project-1",
762 },
763 },
764 },
765 },
766 spec: map[string]interface{}{
767 "objectMapKey": map[string]interface{}{
768 "obj1": map[string]interface{}{
769 "objectField": "bar1",
770 },
771 "obj2": map[string]interface{}{
772 "objectField": "foo2",
773 },
774 },
775 },
776 oldSpec: map[string]interface{}{
777 "objectMapKey": map[string]interface{}{
778 "obj1": map[string]interface{}{
779 "objectField": "foo1",
780 },
781 "obj2": map[string]interface{}{
782 "objectField": "foo2",
783 },
784 },
785 },
786 schema: &openapi.Schema{
787 Type: "object",
788 Properties: map[string]*openapi.Schema{
789 "objectMapKey": &openapi.Schema{
790 Type: "object",
791 AdditionalProperties: &openapi.Schema{
792 Type: "object",
793 Properties: map[string]*openapi.Schema{
794 "objectField": &openapi.Schema{
795 Type: "string",
796 },
797 },
798 },
799 Extension: map[string]interface{}{
800 "x-kubernetes-immutable": true,
801 },
802 },
803 },
804 Extension: map[string]interface{}{
805 "x-dcl-parent-container": "project",
806 },
807 },
808 response: admission.Errored(http.StatusForbidden,
809 k8s.NewImmutableFieldsMutationError([]string{"spec.objectMapKey"})),
810 },
811 {
812 name: "changes on immutable maps of objects (changed the key of one of the objects in the map)",
813 obj: &unstructured.Unstructured{
814 Object: map[string]interface{}{
815 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
816 "kind": "Test1Bar",
817 "metadata": map[string]interface{}{
818 "annotations": map[string]interface{}{
819 k8s.ProjectIDAnnotation: "my-project-1",
820 },
821 },
822 },
823 },
824 oldObj: &unstructured.Unstructured{
825 Object: map[string]interface{}{
826 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
827 "kind": "Test1Bar",
828 "metadata": map[string]interface{}{
829 "annotations": map[string]interface{}{
830 k8s.ProjectIDAnnotation: "my-project-1",
831 },
832 },
833 },
834 },
835 spec: map[string]interface{}{
836 "objectMapKey": map[string]interface{}{
837 "obj1-new": map[string]interface{}{
838 "objectField": "foo1",
839 },
840 "obj2": map[string]interface{}{
841 "objectField": "foo2",
842 },
843 },
844 },
845 oldSpec: map[string]interface{}{
846 "objectMapKey": map[string]interface{}{
847 "obj1": map[string]interface{}{
848 "objectField": "foo1",
849 },
850 "obj2": map[string]interface{}{
851 "objectField": "foo2",
852 },
853 },
854 },
855 schema: &openapi.Schema{
856 Type: "object",
857 Properties: map[string]*openapi.Schema{
858 "objectMapKey": &openapi.Schema{
859 Type: "object",
860 AdditionalProperties: &openapi.Schema{
861 Type: "object",
862 Properties: map[string]*openapi.Schema{
863 "objectField": &openapi.Schema{
864 Type: "string",
865 },
866 },
867 },
868 Extension: map[string]interface{}{
869 "x-kubernetes-immutable": true,
870 },
871 },
872 },
873 Extension: map[string]interface{}{
874 "x-dcl-parent-container": "project",
875 },
876 },
877 response: admission.Errored(http.StatusForbidden,
878 k8s.NewImmutableFieldsMutationError([]string{"spec.objectMapKey"})),
879 },
880 {
881 name: "changes on immutable maps of objects (added a new object to the map)",
882 obj: &unstructured.Unstructured{
883 Object: map[string]interface{}{
884 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
885 "kind": "Test1Bar",
886 "metadata": map[string]interface{}{
887 "annotations": map[string]interface{}{
888 k8s.ProjectIDAnnotation: "my-project-1",
889 },
890 },
891 },
892 },
893 oldObj: &unstructured.Unstructured{
894 Object: map[string]interface{}{
895 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
896 "kind": "Test1Bar",
897 "metadata": map[string]interface{}{
898 "annotations": map[string]interface{}{
899 k8s.ProjectIDAnnotation: "my-project-1",
900 },
901 },
902 },
903 },
904 spec: map[string]interface{}{
905 "objectMapKey": map[string]interface{}{
906 "obj1": map[string]interface{}{
907 "objectField": "foo1",
908 },
909 "obj2": map[string]interface{}{
910 "objectField": "foo2",
911 },
912 "obj3": map[string]interface{}{
913 "objectField": "foo3",
914 },
915 },
916 },
917 oldSpec: map[string]interface{}{
918 "objectMapKey": map[string]interface{}{
919 "obj1": map[string]interface{}{
920 "objectField": "foo1",
921 },
922 "obj2": map[string]interface{}{
923 "objectField": "foo2",
924 },
925 },
926 },
927 schema: &openapi.Schema{
928 Type: "object",
929 Properties: map[string]*openapi.Schema{
930 "objectMapKey": &openapi.Schema{
931 Type: "object",
932 AdditionalProperties: &openapi.Schema{
933 Type: "object",
934 Properties: map[string]*openapi.Schema{
935 "objectField": &openapi.Schema{
936 Type: "string",
937 },
938 },
939 },
940 Extension: map[string]interface{}{
941 "x-kubernetes-immutable": true,
942 },
943 },
944 },
945 Extension: map[string]interface{}{
946 "x-dcl-parent-container": "project",
947 },
948 },
949 response: admission.Errored(http.StatusForbidden,
950 k8s.NewImmutableFieldsMutationError([]string{"spec.objectMapKey"})),
951 },
952 {
953 name: "changes on immutable maps of objects (deleted an object from the map)",
954 obj: &unstructured.Unstructured{
955 Object: map[string]interface{}{
956 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
957 "kind": "Test1Bar",
958 "metadata": map[string]interface{}{
959 "annotations": map[string]interface{}{
960 k8s.ProjectIDAnnotation: "my-project-1",
961 },
962 },
963 },
964 },
965 oldObj: &unstructured.Unstructured{
966 Object: map[string]interface{}{
967 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
968 "kind": "Test1Bar",
969 "metadata": map[string]interface{}{
970 "annotations": map[string]interface{}{
971 k8s.ProjectIDAnnotation: "my-project-1",
972 },
973 },
974 },
975 },
976 spec: map[string]interface{}{
977 "objectMapKey": map[string]interface{}{
978 "obj1": map[string]interface{}{
979 "objectField": "foo1",
980 },
981 },
982 },
983 oldSpec: map[string]interface{}{
984 "objectMapKey": map[string]interface{}{
985 "obj1": map[string]interface{}{
986 "objectField": "foo1",
987 },
988 "obj2": map[string]interface{}{
989 "objectField": "foo2",
990 },
991 },
992 },
993 schema: &openapi.Schema{
994 Type: "object",
995 Properties: map[string]*openapi.Schema{
996 "objectMapKey": &openapi.Schema{
997 Type: "object",
998 AdditionalProperties: &openapi.Schema{
999 Type: "object",
1000 Properties: map[string]*openapi.Schema{
1001 "objectField": &openapi.Schema{
1002 Type: "string",
1003 },
1004 },
1005 },
1006 Extension: map[string]interface{}{
1007 "x-kubernetes-immutable": true,
1008 },
1009 },
1010 },
1011 Extension: map[string]interface{}{
1012 "x-dcl-parent-container": "project",
1013 },
1014 },
1015 response: admission.Errored(http.StatusForbidden,
1016 k8s.NewImmutableFieldsMutationError([]string{"spec.objectMapKey"})),
1017 },
1018 {
1019 name: "changes on mutable maps of objects (changed a field in one of the objects in the map)",
1020 obj: &unstructured.Unstructured{
1021 Object: map[string]interface{}{
1022 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
1023 "kind": "Test1Bar",
1024 "metadata": map[string]interface{}{
1025 "annotations": map[string]interface{}{
1026 k8s.ProjectIDAnnotation: "my-project-1",
1027 },
1028 },
1029 },
1030 },
1031 oldObj: &unstructured.Unstructured{
1032 Object: map[string]interface{}{
1033 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
1034 "kind": "Test1Bar",
1035 "metadata": map[string]interface{}{
1036 "annotations": map[string]interface{}{
1037 k8s.ProjectIDAnnotation: "my-project-1",
1038 },
1039 },
1040 },
1041 },
1042 spec: map[string]interface{}{
1043 "objectMapKey": map[string]interface{}{
1044 "obj1": map[string]interface{}{
1045 "objectField": "bar1",
1046 },
1047 "obj2": map[string]interface{}{
1048 "objectField": "foo2",
1049 },
1050 },
1051 },
1052 oldSpec: map[string]interface{}{
1053 "objectMapKey": map[string]interface{}{
1054 "obj1": map[string]interface{}{
1055 "objectField": "foo1",
1056 },
1057 "obj2": map[string]interface{}{
1058 "objectField": "foo2",
1059 },
1060 },
1061 },
1062 schema: &openapi.Schema{
1063 Type: "object",
1064 Properties: map[string]*openapi.Schema{
1065 "objectMapKey": &openapi.Schema{
1066 Type: "object",
1067 AdditionalProperties: &openapi.Schema{
1068 Type: "object",
1069 Properties: map[string]*openapi.Schema{
1070 "objectField": &openapi.Schema{
1071 Type: "string",
1072 },
1073 },
1074 },
1075 },
1076 },
1077 Extension: map[string]interface{}{
1078 "x-dcl-parent-container": "project",
1079 },
1080 },
1081 response: allowedResponse,
1082 },
1083 {
1084 name: "no changes to immutable maps of arrays",
1085 obj: &unstructured.Unstructured{
1086 Object: map[string]interface{}{
1087 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
1088 "kind": "Test1Bar",
1089 "metadata": map[string]interface{}{
1090 "annotations": map[string]interface{}{
1091 k8s.ProjectIDAnnotation: "my-project-1",
1092 },
1093 },
1094 },
1095 },
1096 oldObj: &unstructured.Unstructured{
1097 Object: map[string]interface{}{
1098 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
1099 "kind": "Test1Bar",
1100 "metadata": map[string]interface{}{
1101 "annotations": map[string]interface{}{
1102 k8s.ProjectIDAnnotation: "my-project-1",
1103 },
1104 },
1105 },
1106 },
1107 spec: map[string]interface{}{
1108 "arrayMapKey": map[string]interface{}{
1109 "arr1": []interface{}{
1110 "foo1",
1111 },
1112 "arr2": []interface{}{
1113 "foo2",
1114 },
1115 },
1116 },
1117 oldSpec: map[string]interface{}{
1118 "arrayMapKey": map[string]interface{}{
1119 "arr1": []interface{}{
1120 "foo1",
1121 },
1122 "arr2": []interface{}{
1123 "foo2",
1124 },
1125 },
1126 },
1127 schema: &openapi.Schema{
1128 Type: "object",
1129 Properties: map[string]*openapi.Schema{
1130 "arrayMapKey": &openapi.Schema{
1131 Type: "object",
1132 AdditionalProperties: &openapi.Schema{
1133 Type: "array",
1134 Items: &openapi.Schema{
1135 Type: "string",
1136 },
1137 },
1138 Extension: map[string]interface{}{
1139 "x-kubernetes-immutable": true,
1140 },
1141 },
1142 },
1143 Extension: map[string]interface{}{
1144 "x-dcl-parent-container": "project",
1145 },
1146 },
1147 response: allowedResponse,
1148 },
1149 {
1150 name: "changes on immutable maps of arrays (changed a value in one of the arrays in the map)",
1151 obj: &unstructured.Unstructured{
1152 Object: map[string]interface{}{
1153 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
1154 "kind": "Test1Bar",
1155 "metadata": map[string]interface{}{
1156 "annotations": map[string]interface{}{
1157 k8s.ProjectIDAnnotation: "my-project-1",
1158 },
1159 },
1160 },
1161 },
1162 oldObj: &unstructured.Unstructured{
1163 Object: map[string]interface{}{
1164 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
1165 "kind": "Test1Bar",
1166 "metadata": map[string]interface{}{
1167 "annotations": map[string]interface{}{
1168 k8s.ProjectIDAnnotation: "my-project-1",
1169 },
1170 },
1171 },
1172 },
1173 spec: map[string]interface{}{
1174 "arrayMapKey": map[string]interface{}{
1175 "arr1": []interface{}{
1176 "bar1",
1177 },
1178 "arr2": []interface{}{
1179 "foo2",
1180 },
1181 },
1182 },
1183 oldSpec: map[string]interface{}{
1184 "arrayMapKey": map[string]interface{}{
1185 "arr1": []interface{}{
1186 "foo1",
1187 },
1188 "arr2": []interface{}{
1189 "foo2",
1190 },
1191 },
1192 },
1193 schema: &openapi.Schema{
1194 Type: "object",
1195 Properties: map[string]*openapi.Schema{
1196 "arrayMapKey": &openapi.Schema{
1197 Type: "object",
1198 AdditionalProperties: &openapi.Schema{
1199 Type: "array",
1200 Items: &openapi.Schema{
1201 Type: "string",
1202 },
1203 },
1204 Extension: map[string]interface{}{
1205 "x-kubernetes-immutable": true,
1206 },
1207 },
1208 },
1209 Extension: map[string]interface{}{
1210 "x-dcl-parent-container": "project",
1211 },
1212 },
1213 response: admission.Errored(http.StatusForbidden,
1214 k8s.NewImmutableFieldsMutationError([]string{"spec.arrayMapKey"})),
1215 },
1216 {
1217 name: "changes on immutable maps of arrays (added a value in one of the arrays in the map)",
1218 obj: &unstructured.Unstructured{
1219 Object: map[string]interface{}{
1220 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
1221 "kind": "Test1Bar",
1222 "metadata": map[string]interface{}{
1223 "annotations": map[string]interface{}{
1224 k8s.ProjectIDAnnotation: "my-project-1",
1225 },
1226 },
1227 },
1228 },
1229 oldObj: &unstructured.Unstructured{
1230 Object: map[string]interface{}{
1231 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
1232 "kind": "Test1Bar",
1233 "metadata": map[string]interface{}{
1234 "annotations": map[string]interface{}{
1235 k8s.ProjectIDAnnotation: "my-project-1",
1236 },
1237 },
1238 },
1239 },
1240 spec: map[string]interface{}{
1241 "arrayMapKey": map[string]interface{}{
1242 "arr1": []interface{}{
1243 "foo1",
1244 "foo11",
1245 },
1246 "arr2": []interface{}{
1247 "foo2",
1248 },
1249 },
1250 },
1251 oldSpec: map[string]interface{}{
1252 "arrayMapKey": map[string]interface{}{
1253 "arr1": []interface{}{
1254 "foo1",
1255 },
1256 "arr2": []interface{}{
1257 "foo2",
1258 },
1259 },
1260 },
1261 schema: &openapi.Schema{
1262 Type: "object",
1263 Properties: map[string]*openapi.Schema{
1264 "arrayMapKey": &openapi.Schema{
1265 Type: "object",
1266 AdditionalProperties: &openapi.Schema{
1267 Type: "array",
1268 Items: &openapi.Schema{
1269 Type: "string",
1270 },
1271 },
1272 Extension: map[string]interface{}{
1273 "x-kubernetes-immutable": true,
1274 },
1275 },
1276 },
1277 Extension: map[string]interface{}{
1278 "x-dcl-parent-container": "project",
1279 },
1280 },
1281 response: admission.Errored(http.StatusForbidden,
1282 k8s.NewImmutableFieldsMutationError([]string{"spec.arrayMapKey"})),
1283 },
1284 {
1285 name: "changes on immutable maps of arrays (deleted a value from one of the arrays in the map)",
1286 obj: &unstructured.Unstructured{
1287 Object: map[string]interface{}{
1288 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
1289 "kind": "Test1Bar",
1290 "metadata": map[string]interface{}{
1291 "annotations": map[string]interface{}{
1292 k8s.ProjectIDAnnotation: "my-project-1",
1293 },
1294 },
1295 },
1296 },
1297 oldObj: &unstructured.Unstructured{
1298 Object: map[string]interface{}{
1299 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
1300 "kind": "Test1Bar",
1301 "metadata": map[string]interface{}{
1302 "annotations": map[string]interface{}{
1303 k8s.ProjectIDAnnotation: "my-project-1",
1304 },
1305 },
1306 },
1307 },
1308 spec: map[string]interface{}{
1309 "arrayMapKey": map[string]interface{}{
1310 "arr1": []interface{}{
1311 "foo1",
1312 },
1313 "arr2": []interface{}{
1314 "foo2",
1315 "foo22",
1316 },
1317 },
1318 },
1319 oldSpec: map[string]interface{}{
1320 "arrayMapKey": map[string]interface{}{
1321 "arr1": []interface{}{
1322 "foo1",
1323 "foo11",
1324 },
1325 "arr2": []interface{}{
1326 "foo2",
1327 "foo22",
1328 },
1329 },
1330 },
1331 schema: &openapi.Schema{
1332 Type: "object",
1333 Properties: map[string]*openapi.Schema{
1334 "arrayMapKey": &openapi.Schema{
1335 Type: "object",
1336 AdditionalProperties: &openapi.Schema{
1337 Type: "array",
1338 Items: &openapi.Schema{
1339 Type: "string",
1340 },
1341 },
1342 Extension: map[string]interface{}{
1343 "x-kubernetes-immutable": true,
1344 },
1345 },
1346 },
1347 Extension: map[string]interface{}{
1348 "x-dcl-parent-container": "project",
1349 },
1350 },
1351 response: admission.Errored(http.StatusForbidden,
1352 k8s.NewImmutableFieldsMutationError([]string{"spec.arrayMapKey"})),
1353 },
1354 {
1355 name: "changes on mutable maps of arrays (changed a value in one of the arrays in the map)",
1356 obj: &unstructured.Unstructured{
1357 Object: map[string]interface{}{
1358 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
1359 "kind": "Test1Bar",
1360 "metadata": map[string]interface{}{
1361 "annotations": map[string]interface{}{
1362 k8s.ProjectIDAnnotation: "my-project-1",
1363 },
1364 },
1365 },
1366 },
1367 oldObj: &unstructured.Unstructured{
1368 Object: map[string]interface{}{
1369 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
1370 "kind": "Test1Bar",
1371 "metadata": map[string]interface{}{
1372 "annotations": map[string]interface{}{
1373 k8s.ProjectIDAnnotation: "my-project-1",
1374 },
1375 },
1376 },
1377 },
1378 spec: map[string]interface{}{
1379 "arrayMapKey": map[string]interface{}{
1380 "arr1": []interface{}{
1381 "bar1",
1382 },
1383 "arr2": []interface{}{
1384 "foo2",
1385 },
1386 },
1387 },
1388 oldSpec: map[string]interface{}{
1389 "arrayMapKey": map[string]interface{}{
1390 "arr1": []interface{}{
1391 "foo1",
1392 },
1393 "arr2": []interface{}{
1394 "foo2",
1395 },
1396 },
1397 },
1398 schema: &openapi.Schema{
1399 Type: "object",
1400 Properties: map[string]*openapi.Schema{
1401 "arrayMapKey": &openapi.Schema{
1402 Type: "object",
1403 AdditionalProperties: &openapi.Schema{
1404 Type: "array",
1405 Items: &openapi.Schema{
1406 Type: "string",
1407 },
1408 },
1409 },
1410 },
1411 Extension: map[string]interface{}{
1412 "x-dcl-parent-container": "project",
1413 },
1414 },
1415 response: allowedResponse,
1416 },
1417 {
1418 name: "changes the container annotation",
1419 obj: &unstructured.Unstructured{
1420 Object: map[string]interface{}{
1421 "apiVersion": "test4.cnrm.cloud.google.com/v1alpha1",
1422 "kind": "Test4ProjectContainer",
1423 "metadata": map[string]interface{}{
1424 "annotations": map[string]interface{}{
1425 k8s.ProjectIDAnnotation: "my-project-1",
1426 },
1427 },
1428 },
1429 },
1430 oldObj: &unstructured.Unstructured{
1431 Object: map[string]interface{}{
1432 "apiVersion": "test4.cnrm.cloud.google.com/v1alpha1",
1433 "kind": "Test4ProjectContainer",
1434 "metadata": map[string]interface{}{
1435 "annotations": map[string]interface{}{
1436 k8s.ProjectIDAnnotation: "my-project-2",
1437 },
1438 },
1439 },
1440 },
1441 spec: map[string]interface{}{},
1442 oldSpec: map[string]interface{}{},
1443 schema: &openapi.Schema{
1444 Type: "object",
1445 Extension: map[string]interface{}{
1446 "x-dcl-parent-container": "project",
1447 },
1448 },
1449 response: admission.Errored(http.StatusBadRequest,
1450 fmt.Errorf("error validating container annotations: cannot make changes to container annotation cnrm.cloud.google.com/project-id")),
1451 },
1452 {
1453 name: "changes on mutable fields",
1454 obj: &unstructured.Unstructured{
1455 Object: map[string]interface{}{
1456 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
1457 "kind": "Test1Bar",
1458 "metadata": map[string]interface{}{
1459 "annotations": map[string]interface{}{
1460 k8s.ProjectIDAnnotation: "my-project-1",
1461 },
1462 },
1463 },
1464 },
1465 oldObj: &unstructured.Unstructured{
1466 Object: map[string]interface{}{
1467 "apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
1468 "kind": "Test1Bar",
1469 "metadata": map[string]interface{}{
1470 "annotations": map[string]interface{}{
1471 k8s.ProjectIDAnnotation: "my-project-1",
1472 },
1473 },
1474 },
1475 },
1476 spec: map[string]interface{}{
1477 "location": "EU",
1478 "nestedObjectKey": map[string]interface{}{
1479 "nestedIntField": 1,
1480 "nestedStringField": "strval1",
1481 },
1482 },
1483 oldSpec: map[string]interface{}{
1484 "location": "US",
1485 "nestedObjectKey": map[string]interface{}{
1486 "nestedIntField": 2,
1487 "nestedStringField": "strval2",
1488 },
1489 },
1490 schema: &openapi.Schema{
1491 Type: "object",
1492 Properties: map[string]*openapi.Schema{
1493 "location": &openapi.Schema{
1494 Type: "string",
1495 },
1496 "nestedObjectKey": &openapi.Schema{
1497 Type: "object",
1498 Properties: map[string]*openapi.Schema{
1499 "nestedIntField": &openapi.Schema{
1500 Type: "integer",
1501 },
1502 "nestedStringField": &openapi.Schema{
1503 Type: "string",
1504 },
1505 },
1506 },
1507 },
1508 Extension: map[string]interface{}{
1509 "x-dcl-parent-container": "project",
1510 },
1511 },
1512 response: allowedResponse,
1513 },
1514 }
1515
1516 smLoader := dclmetadata.NewFromServiceList(testservicemetadataloader.FakeServiceMetadataWithHierarchicalResources())
1517 for _, tc := range tests {
1518 tc := tc
1519 t.Run(tc.name, func(t *testing.T) {
1520 dclSchemaKey := testdclschemaloader.DCLSchemaKeyForGVK(t, tc.obj.GroupVersionKind(), smLoader)
1521 dclSchemaMap := make(map[string]*openapi.Schema)
1522 dclSchemaMap[dclSchemaKey] = tc.schema
1523 dclSchemaLoader := testdclschemaloader.New(dclSchemaMap)
1524 actual := validateImmutableFieldsForDCLBasedResource(tc.obj, tc.oldObj, tc.spec, tc.oldSpec, dclSchemaLoader, smLoader)
1525 if !testutil.Equals(t, actual, tc.response) {
1526 t.Fatalf("got: %v, but want: %v", actual, tc.response)
1527 }
1528 })
1529 }
1530 }
1531
1532 func assertImmutableFieldsValidatorResult(t *testing.T, v *immutableFieldsValidatorHandler, provider *schema.Provider, testCase TestCase) {
1533 r, ok := provider.ResourcesMap[testCase.TFSchemaName]
1534 if !ok {
1535 t.Errorf("couldn't get the schema for %v", testCase.TFSchemaName)
1536 }
1537 fields := list.New()
1538 compareAndFindChangesOnImmutableFields(testCase.Spec, testCase.OldSpec, r.Schema, "", testCase.ResourceConfig, nil, fields)
1539
1540 res := make([]string, 0)
1541 for e := fields.Front(); e != nil; e = e.Next() {
1542 res = append(res, e.Value.(string))
1543 }
1544 if !reflect.DeepEqual(testCase.ExpectedResult, res) {
1545 t.Errorf("expected to find changes on immutable location field %v, instead get %v", testCase.ExpectedResult, res)
1546 }
1547 }
1548
1549 func TestChangesOnImmutableLocationField(t *testing.T) {
1550 spec := map[string]interface{}{
1551 "location": "us-east1",
1552 }
1553
1554 oldSpec := map[string]interface{}{
1555 "location": "us-west1",
1556 }
1557
1558 rc := &corekccv1alpha1.ResourceConfig{
1559 Locationality: "regional",
1560 }
1561
1562 found := findChangesOnImmutableLocationField(spec, oldSpec, rc)
1563 if !found {
1564 t.Errorf("expected to find changes on immutable location field")
1565 }
1566 }
1567
1568 func TestChangesOnImmutableResourceIDField(t *testing.T) {
1569 tests := []struct {
1570 name string
1571 spec map[string]interface{}
1572 oldSpec map[string]interface{}
1573 rc *corekccv1alpha1.ResourceConfig
1574 expectedResult bool
1575 }{
1576 {
1577 name: "resource ID not changed",
1578 spec: map[string]interface{}{k8s.ResourceIDFieldName: "test-id"},
1579 oldSpec: map[string]interface{}{k8s.ResourceIDFieldName: "test-id"},
1580 rc: &corekccv1alpha1.ResourceConfig{
1581 ResourceID: corekccv1alpha1.ResourceID{
1582 TargetField: "test_field",
1583 },
1584 },
1585 expectedResult: false,
1586 },
1587 {
1588 name: "resource ID changed",
1589 spec: map[string]interface{}{k8s.ResourceIDFieldName: "updated-id"},
1590 oldSpec: map[string]interface{}{k8s.ResourceIDFieldName: "test-id"},
1591 rc: &corekccv1alpha1.ResourceConfig{
1592 ResourceID: corekccv1alpha1.ResourceID{
1593 TargetField: "test_field",
1594 },
1595 },
1596 expectedResult: true,
1597 },
1598 {
1599 name: "resource ID not supported",
1600 spec: map[string]interface{}{},
1601 oldSpec: map[string]interface{}{},
1602 rc: &corekccv1alpha1.ResourceConfig{},
1603 expectedResult: false,
1604 },
1605 }
1606
1607 for _, tc := range tests {
1608 tc := tc
1609 t.Run(tc.name, func(t *testing.T) {
1610 if got, want :=
1611 findChangesOnImmutableResourceIDField(tc.spec, tc.oldSpec, tc.rc),
1612 tc.expectedResult; got != want {
1613 t.Errorf("unexpected result finding changes on %q "+
1614 "field: got %t, want %t", k8s.ResourceIDFieldPath, got, want)
1615 }
1616 })
1617 }
1618 }
1619
1620 func newImmutableFieldsValidatorHandler(t *testing.T) *immutableFieldsValidatorHandler {
1621 t.Helper()
1622 smLoader, err := servicemappingloader.New()
1623 if err != nil {
1624 t.Fatal(err)
1625 }
1626
1627 return NewImmutableFieldsValidatorHandler(smLoader, nil, testservicemetadataloader.NewForUnitTest())
1628 }
1629
1630 func TestValidateContainerAnnotations(t *testing.T) {
1631 tests := []struct {
1632 name string
1633 kind string
1634 old map[string]string
1635 updated map[string]string
1636 containers []corekccv1alpha1.Container
1637 hierarchicalRefs []corekccv1alpha1.HierarchicalReference
1638 shouldErr bool
1639 }{
1640 {
1641 name: "changing project ID is not allowed",
1642 kind: "GenericKind",
1643 old: map[string]string{k8s.ProjectIDAnnotation: "project-1"},
1644 updated: map[string]string{k8s.ProjectIDAnnotation: "project-2"},
1645 containers: []corekccv1alpha1.Container{
1646 {Type: corekccv1alpha1.ContainerTypeProject},
1647 },
1648 shouldErr: true,
1649 },
1650 {
1651 name: "changing folder ID is not allowed (in the general case)",
1652 kind: "GenericKind",
1653 old: map[string]string{k8s.FolderIDAnnotation: "123321"},
1654 updated: map[string]string{k8s.FolderIDAnnotation: "321123"},
1655 containers: []corekccv1alpha1.Container{
1656 {Type: corekccv1alpha1.ContainerTypeFolder},
1657 },
1658 shouldErr: true,
1659 },
1660 {
1661 name: "changing folder ID is allowed for Projects",
1662 kind: "Project",
1663 old: map[string]string{k8s.FolderIDAnnotation: "123321"},
1664 updated: map[string]string{k8s.FolderIDAnnotation: "321123"},
1665 containers: []corekccv1alpha1.Container{
1666 {Type: corekccv1alpha1.ContainerTypeFolder},
1667 },
1668 },
1669 {
1670 name: "changing folder ID is allowed for Folders",
1671 kind: "Folder",
1672 old: map[string]string{k8s.FolderIDAnnotation: "123321"},
1673 updated: map[string]string{k8s.FolderIDAnnotation: "321123"},
1674 containers: []corekccv1alpha1.Container{
1675 {Type: corekccv1alpha1.ContainerTypeFolder},
1676 },
1677 },
1678 {
1679 name: "changing org ID is not allowed (in the general case)",
1680 kind: "GenericKind",
1681 old: map[string]string{k8s.OrgIDAnnotation: "0987654"},
1682 updated: map[string]string{k8s.OrgIDAnnotation: "1234567"},
1683 containers: []corekccv1alpha1.Container{
1684 {Type: corekccv1alpha1.ContainerTypeOrganization},
1685 },
1686 shouldErr: true,
1687 },
1688 {
1689 name: "changing org ID is allowed for Projects",
1690 kind: "Project",
1691 old: map[string]string{k8s.OrgIDAnnotation: "123321"},
1692 updated: map[string]string{k8s.OrgIDAnnotation: "321123"},
1693 containers: []corekccv1alpha1.Container{
1694 {Type: corekccv1alpha1.ContainerTypeOrganization},
1695 },
1696 },
1697 {
1698 name: "changing org ID is allowed for Folders",
1699 kind: "Folder",
1700 old: map[string]string{k8s.OrgIDAnnotation: "123321"},
1701 updated: map[string]string{k8s.OrgIDAnnotation: "321123"},
1702 containers: []corekccv1alpha1.Container{
1703 {Type: corekccv1alpha1.ContainerTypeOrganization},
1704 },
1705 },
1706 {
1707 name: "changing from project ID to folder ID is not allowed",
1708 kind: "GenericKind",
1709 old: map[string]string{k8s.ProjectIDAnnotation: "project-1"},
1710 updated: map[string]string{k8s.FolderIDAnnotation: "123321"},
1711 containers: []corekccv1alpha1.Container{
1712 {Type: corekccv1alpha1.ContainerTypeProject},
1713 {Type: corekccv1alpha1.ContainerTypeFolder},
1714 },
1715 shouldErr: true,
1716 },
1717 {
1718 name: "changing from project ID to org ID is not allowed",
1719 kind: "GenericKind",
1720 old: map[string]string{k8s.ProjectIDAnnotation: "project-1"},
1721 updated: map[string]string{k8s.OrgIDAnnotation: "0987654"},
1722 containers: []corekccv1alpha1.Container{
1723 {Type: corekccv1alpha1.ContainerTypeProject},
1724 {Type: corekccv1alpha1.ContainerTypeOrganization},
1725 },
1726 shouldErr: true,
1727 },
1728 {
1729 name: "changing from folder ID to project ID is not allowed",
1730 kind: "GenericKind",
1731 old: map[string]string{k8s.FolderIDAnnotation: "123321"},
1732 updated: map[string]string{k8s.ProjectIDAnnotation: "project-1"},
1733 containers: []corekccv1alpha1.Container{
1734 {Type: corekccv1alpha1.ContainerTypeProject},
1735 {Type: corekccv1alpha1.ContainerTypeFolder},
1736 },
1737 shouldErr: true,
1738 },
1739 {
1740 name: "changing from folder ID to org ID is not allowed",
1741 kind: "GenericKind",
1742 old: map[string]string{k8s.FolderIDAnnotation: "123321"},
1743 updated: map[string]string{k8s.OrgIDAnnotation: "0987654"},
1744 containers: []corekccv1alpha1.Container{
1745 {Type: corekccv1alpha1.ContainerTypeFolder},
1746 {Type: corekccv1alpha1.ContainerTypeOrganization},
1747 },
1748 shouldErr: true,
1749 },
1750 {
1751 name: "changing from org ID to project ID is not allowed",
1752 kind: "GenericKind",
1753 old: map[string]string{k8s.OrgIDAnnotation: "0987654"},
1754 updated: map[string]string{k8s.ProjectIDAnnotation: "project-1"},
1755 containers: []corekccv1alpha1.Container{
1756 {Type: corekccv1alpha1.ContainerTypeProject},
1757 {Type: corekccv1alpha1.ContainerTypeOrganization},
1758 },
1759 shouldErr: true,
1760 },
1761 {
1762 name: "changing from org ID to folder ID is not allowed",
1763 kind: "GenericKind",
1764 old: map[string]string{k8s.OrgIDAnnotation: "0987654"},
1765 updated: map[string]string{k8s.FolderIDAnnotation: "123321"},
1766 containers: []corekccv1alpha1.Container{
1767 {Type: corekccv1alpha1.ContainerTypeFolder},
1768 {Type: corekccv1alpha1.ContainerTypeOrganization},
1769 },
1770 shouldErr: true,
1771 },
1772 {
1773 name: "changing from folder ID to org ID is not allowed for Projects",
1774 kind: "Project",
1775 old: map[string]string{k8s.FolderIDAnnotation: "123321"},
1776 updated: map[string]string{k8s.OrgIDAnnotation: "0987654"},
1777 containers: []corekccv1alpha1.Container{
1778 {Type: corekccv1alpha1.ContainerTypeFolder},
1779 {Type: corekccv1alpha1.ContainerTypeOrganization},
1780 },
1781 shouldErr: true,
1782 },
1783 {
1784 name: "changing from org ID to folder ID is not allowed for Projects",
1785 kind: "Project",
1786 old: map[string]string{k8s.OrgIDAnnotation: "0987654"},
1787 updated: map[string]string{k8s.FolderIDAnnotation: "123321"},
1788 containers: []corekccv1alpha1.Container{
1789 {Type: corekccv1alpha1.ContainerTypeFolder},
1790 {Type: corekccv1alpha1.ContainerTypeOrganization},
1791 },
1792 shouldErr: true,
1793 },
1794 {
1795 name: "changing from folder ID to org ID is not allowed for Folders",
1796 kind: "Folder",
1797 old: map[string]string{k8s.FolderIDAnnotation: "123321"},
1798 updated: map[string]string{k8s.OrgIDAnnotation: "0987654"},
1799 containers: []corekccv1alpha1.Container{
1800 {Type: corekccv1alpha1.ContainerTypeFolder},
1801 {Type: corekccv1alpha1.ContainerTypeOrganization},
1802 },
1803 shouldErr: true,
1804 },
1805 {
1806 name: "changing from org ID to folder ID is not allowed for Folders",
1807 kind: "Folder",
1808 old: map[string]string{k8s.OrgIDAnnotation: "0987654"},
1809 updated: map[string]string{k8s.FolderIDAnnotation: "123321"},
1810 containers: []corekccv1alpha1.Container{
1811 {Type: corekccv1alpha1.ContainerTypeFolder},
1812 {Type: corekccv1alpha1.ContainerTypeOrganization},
1813 },
1814 shouldErr: true,
1815 },
1816 {
1817 name: "changing project ID is not allowed if if resource supports hierarchical references",
1818 kind: "GenericKind",
1819 old: map[string]string{k8s.ProjectIDAnnotation: "project-1"},
1820 updated: map[string]string{k8s.ProjectIDAnnotation: "project-2"},
1821 containers: []corekccv1alpha1.Container{
1822 {Type: corekccv1alpha1.ContainerTypeProject},
1823 },
1824 hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
1825 {Type: corekccv1alpha1.HierarchicalReferenceTypeProject},
1826 },
1827 shouldErr: true,
1828 },
1829 {
1830 name: "changing folder ID is not allowed if resource supports hierarchical references (in the general case)",
1831 kind: "GenericKind",
1832 old: map[string]string{k8s.FolderIDAnnotation: "123321"},
1833 updated: map[string]string{k8s.FolderIDAnnotation: "321123"},
1834 containers: []corekccv1alpha1.Container{
1835 {Type: corekccv1alpha1.ContainerTypeFolder},
1836 },
1837 hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
1838 {Type: corekccv1alpha1.HierarchicalReferenceTypeFolder},
1839 },
1840 shouldErr: true,
1841 },
1842 {
1843 name: "changing org ID is not allowed if resource supports hierarchical references (in the general case)",
1844 kind: "GenericKind",
1845 old: map[string]string{k8s.OrgIDAnnotation: "0987654"},
1846 updated: map[string]string{k8s.OrgIDAnnotation: "1234567"},
1847 containers: []corekccv1alpha1.Container{
1848 {Type: corekccv1alpha1.ContainerTypeOrganization},
1849 },
1850 hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
1851 {Type: corekccv1alpha1.HierarchicalReferenceTypeOrganization},
1852 },
1853 shouldErr: true,
1854 },
1855 {
1856 name: "changing folder ID is not allowed for Projects once Project supports hierarchical references",
1857 kind: "Project",
1858 old: map[string]string{k8s.FolderIDAnnotation: "123321"},
1859 updated: map[string]string{k8s.FolderIDAnnotation: "321123"},
1860 containers: []corekccv1alpha1.Container{
1861 {Type: corekccv1alpha1.ContainerTypeFolder},
1862 },
1863 hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
1864 {Type: corekccv1alpha1.HierarchicalReferenceTypeFolder},
1865 },
1866 shouldErr: true,
1867 },
1868 {
1869 name: "changing folder ID is not allowed for Folders once Folder supports hierarchical references",
1870 kind: "Folder",
1871 old: map[string]string{k8s.FolderIDAnnotation: "123321"},
1872 updated: map[string]string{k8s.FolderIDAnnotation: "321123"},
1873 containers: []corekccv1alpha1.Container{
1874 {Type: corekccv1alpha1.ContainerTypeFolder},
1875 },
1876 hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
1877 {Type: corekccv1alpha1.HierarchicalReferenceTypeFolder},
1878 },
1879 shouldErr: true,
1880 },
1881 {
1882 name: "changing org ID is not allowed for Projects once Project supports hierarchical references",
1883 kind: "Project",
1884 old: map[string]string{k8s.OrgIDAnnotation: "123321"},
1885 updated: map[string]string{k8s.OrgIDAnnotation: "321123"},
1886 containers: []corekccv1alpha1.Container{
1887 {Type: corekccv1alpha1.ContainerTypeOrganization},
1888 },
1889 hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
1890 {Type: corekccv1alpha1.HierarchicalReferenceTypeOrganization},
1891 },
1892 shouldErr: true,
1893 },
1894 {
1895 name: "changing org ID is not allowed for Folders once Folder supports hierarchical references",
1896 kind: "Folder",
1897 old: map[string]string{k8s.OrgIDAnnotation: "123321"},
1898 updated: map[string]string{k8s.OrgIDAnnotation: "321123"},
1899 containers: []corekccv1alpha1.Container{
1900 {Type: corekccv1alpha1.ContainerTypeOrganization},
1901 },
1902 hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
1903 {Type: corekccv1alpha1.HierarchicalReferenceTypeOrganization},
1904 },
1905 shouldErr: true,
1906 },
1907 {
1908 name: "removing project ID is allowed if resource supports hierarchical references",
1909 kind: "GenericKind",
1910 old: map[string]string{k8s.ProjectIDAnnotation: "project-1"},
1911 updated: map[string]string{},
1912 containers: []corekccv1alpha1.Container{
1913 {Type: corekccv1alpha1.ContainerTypeProject},
1914 },
1915 hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
1916 {Type: corekccv1alpha1.HierarchicalReferenceTypeProject},
1917 },
1918 },
1919 {
1920 name: "removing folder ID is allowed if resource supports hierarchical references",
1921 kind: "GenericKind",
1922 old: map[string]string{k8s.FolderIDAnnotation: "123321"},
1923 updated: map[string]string{},
1924 containers: []corekccv1alpha1.Container{
1925 {Type: corekccv1alpha1.ContainerTypeFolder},
1926 },
1927 hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
1928 {Type: corekccv1alpha1.HierarchicalReferenceTypeFolder},
1929 },
1930 },
1931 {
1932 name: "removing org ID is allowed if resource supports hierarchical references",
1933 kind: "GenericKind",
1934 old: map[string]string{k8s.OrgIDAnnotation: "123321"},
1935 updated: map[string]string{},
1936 containers: []corekccv1alpha1.Container{
1937 {Type: corekccv1alpha1.ContainerTypeOrganization},
1938 },
1939 hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
1940 {Type: corekccv1alpha1.HierarchicalReferenceTypeOrganization},
1941 },
1942 },
1943 {
1944 name: "adding project ID is not allowed if resource supports hierarchical references",
1945 kind: "GenericKind",
1946 old: map[string]string{},
1947 updated: map[string]string{k8s.ProjectIDAnnotation: "project-1"},
1948 containers: []corekccv1alpha1.Container{
1949 {Type: corekccv1alpha1.ContainerTypeProject},
1950 },
1951 hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
1952 {Type: corekccv1alpha1.HierarchicalReferenceTypeProject},
1953 },
1954 shouldErr: true,
1955 },
1956 {
1957 name: "adding folder ID is not allowed if resource supports hierarchical references",
1958 kind: "GenericKind",
1959 old: map[string]string{},
1960 updated: map[string]string{k8s.FolderIDAnnotation: "123321"},
1961 containers: []corekccv1alpha1.Container{
1962 {Type: corekccv1alpha1.ContainerTypeFolder},
1963 },
1964 hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
1965 {Type: corekccv1alpha1.HierarchicalReferenceTypeFolder},
1966 },
1967 shouldErr: true,
1968 },
1969 {
1970 name: "adding org ID is not allowed if resource supports hierarchical references",
1971 kind: "GenericKind",
1972 old: map[string]string{},
1973 updated: map[string]string{k8s.OrgIDAnnotation: "123321"},
1974 containers: []corekccv1alpha1.Container{
1975 {Type: corekccv1alpha1.ContainerTypeOrganization},
1976 },
1977 hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
1978 {Type: corekccv1alpha1.HierarchicalReferenceTypeOrganization},
1979 },
1980 shouldErr: true,
1981 },
1982 {
1983 name: "adding a different annotation is allowed",
1984 kind: "GenericKind",
1985 old: map[string]string{k8s.ProjectIDAnnotation: "project-1"},
1986 updated: map[string]string{
1987 k8s.ProjectIDAnnotation: "project-1",
1988 "key": "value",
1989 },
1990 containers: []corekccv1alpha1.Container{
1991 {Type: corekccv1alpha1.ContainerTypeProject},
1992 },
1993 },
1994 {
1995 name: "changing an unrecognized container annotation is allowed",
1996 kind: "GenericKind",
1997 old: map[string]string{k8s.ProjectIDAnnotation: "project-1"},
1998 updated: map[string]string{k8s.ProjectIDAnnotation: "project-2"},
1999 containers: []corekccv1alpha1.Container{
2000 {Type: corekccv1alpha1.ContainerTypeFolder},
2001 },
2002 },
2003 }
2004 for _, tc := range tests {
2005 tc := tc
2006 t.Run(tc.name, func(t *testing.T) {
2007 err := validateContainerAnnotationsForResource(tc.kind, tc.updated, tc.old, tc.containers, tc.hierarchicalRefs)
2008 if tc.shouldErr && err == nil {
2009 t.Errorf("expected error but there was none")
2010 return
2011 } else if !tc.shouldErr && err != nil {
2012 t.Errorf("got unexpected error: %v", err)
2013 }
2014 })
2015 }
2016 }
2017
2018 func newUnstructuredFromObject(t *testing.T, value interface{}) *unstructured.Unstructured {
2019 var mapResult map[string]interface{}
2020 if err := util.Marshal(value, &mapResult); err != nil {
2021 t.Fatalf("unable to marshal %v to map: %v", reflect.TypeOf(value).Name(), err)
2022 }
2023 return &unstructured.Unstructured{
2024 Object: mapResult,
2025 }
2026 }
2027
2028 type TestCase struct {
2029 Name string
2030 Spec map[string]interface{}
2031 OldSpec map[string]interface{}
2032 TFSchemaName string
2033 ResourceConfig *corekccv1alpha1.ResourceConfig
2034 ExpectedResult []string
2035 }
2036
2037 var TestCases = []TestCase{
2038 {
2039 Name: "changesOnBaseLevelImmutableField",
2040 Spec: map[string]interface{}{
2041 "location": "EU",
2042 },
2043 OldSpec: map[string]interface{}{
2044 "location": "US",
2045 },
2046 TFSchemaName: "google_bigquery_dataset",
2047 ResourceConfig: &corekccv1alpha1.ResourceConfig{},
2048 ExpectedResult: []string{"location"},
2049 },
2050 {
2051 Name: "changesOnNestedImmutableField",
2052 Spec: map[string]interface{}{
2053 "databaseVersion": "MYSQL_5_7",
2054 "replicaConfiguration": map[string]interface{}{
2055 "connectRetryInterval": 2,
2056 },
2057 },
2058 OldSpec: map[string]interface{}{
2059 "databaseVersion": "MYSQL_5_7",
2060 "replicaConfiguration": map[string]interface{}{
2061 "connectRetryInterval": 4,
2062 },
2063 },
2064 TFSchemaName: "google_sql_database_instance",
2065 ResourceConfig: &corekccv1alpha1.ResourceConfig{},
2066 ExpectedResult: []string{"replica_configuration.connect_retry_interval"},
2067 },
2068 {
2069 Name: "changesOnImmutableReferenceSingleton",
2070 Spec: map[string]interface{}{
2071 "topicRef": map[string]interface{}{
2072 "name": "pubsubtopic-sample-1",
2073 },
2074 },
2075 OldSpec: map[string]interface{}{
2076 "topicRef": map[string]interface{}{
2077 "name": "pubsubtopic-sample-2",
2078 },
2079 },
2080 TFSchemaName: "google_pubsub_subscription",
2081 ResourceConfig: &corekccv1alpha1.ResourceConfig{
2082 ResourceReferences: []corekccv1alpha1.ReferenceConfig{
2083 {
2084 TypeConfig: corekccv1alpha1.TypeConfig{
2085 Key: "topicRef",
2086 GVK: k8sschema.GroupVersionKind{
2087 Kind: "PubsubTopic",
2088 },
2089 },
2090 TFField: "topic",
2091 },
2092 },
2093 },
2094 ExpectedResult: []string{"topicRef"},
2095 },
2096 {
2097 Name: "changesOnMutableNestedReference",
2098 Spec: map[string]interface{}{
2099 "peeringConfig": map[string]interface{}{
2100 "targetNetwork": map[string]interface{}{
2101 "networkRef": map[string]interface{}{
2102 "name": "ref1",
2103 },
2104 },
2105 },
2106 },
2107 OldSpec: map[string]interface{}{
2108 "peeringConfig": map[string]interface{}{
2109 "targetNetwork": map[string]interface{}{
2110 "networkRef": map[string]interface{}{
2111 "name": "ref2",
2112 },
2113 },
2114 },
2115 },
2116 TFSchemaName: "google_dns_managed_zone",
2117 ResourceConfig: &corekccv1alpha1.ResourceConfig{
2118 ResourceReferences: []corekccv1alpha1.ReferenceConfig{
2119 {
2120 TypeConfig: corekccv1alpha1.TypeConfig{
2121 Key: "networkRef",
2122 GVK: k8sschema.GroupVersionKind{
2123 Kind: "ComputeNetwork",
2124 },
2125 },
2126 TFField: "peering_config.target_network.network_url",
2127 },
2128 },
2129 },
2130 ExpectedResult: []string{},
2131 },
2132 {
2133 Name: "changesOnImmutableComplexReference",
2134 Spec: map[string]interface{}{
2135 "ipAddress": map[string]interface{}{
2136 "addressRef": map[string]interface{}{
2137 "name": "ref1",
2138 },
2139 },
2140 },
2141 OldSpec: map[string]interface{}{
2142 "ipAddress": map[string]interface{}{
2143 "ip": "8.8.8.8",
2144 },
2145 },
2146 TFSchemaName: "google_compute_forwarding_rule",
2147 ResourceConfig: &corekccv1alpha1.ResourceConfig{
2148 ResourceReferences: []corekccv1alpha1.ReferenceConfig{
2149 {
2150 Types: []corekccv1alpha1.TypeConfig{
2151 {
2152 Key: "addressRef",
2153 },
2154 {
2155 Key: "ip",
2156 },
2157 },
2158 TFField: "ip_address",
2159 },
2160 },
2161 },
2162 ExpectedResult: []string{"ipAddress"},
2163 },
2164
2165 }
2166
2167 func TestUpdateIAMPolicy(t *testing.T) {
2168 policy := v1beta1.IAMPolicy{
2169 TypeMeta: metav1.TypeMeta{
2170 Kind: v1beta1.IAMPolicyGVK.Kind,
2171 APIVersion: v1beta1.IAMPolicyGVK.GroupVersion().String(),
2172 },
2173 Spec: v1beta1.IAMPolicySpec{
2174 ResourceReference: v1beta1.ResourceReference{
2175 Kind: "my-resource-kind",
2176 Namespace: "my-namespace",
2177 Name: "my-pubsub-topic",
2178 APIVersion: "my-api-version",
2179 },
2180 },
2181 }
2182 oldPolicyUnstructred := newUnstructuredFromObject(t, &policy)
2183 newPolicyUnstructured := newUnstructuredFromObject(t, &policy)
2184 assertHandleIAMPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, true)
2185 copyPolicy := policy
2186 copyPolicy.Spec.ResourceReference.Kind = "new-resource-reference-kind"
2187 newPolicyUnstructured = newUnstructuredFromObject(t, ©Policy)
2188 assertHandleIAMPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
2189 copyPolicy = policy
2190 copyPolicy.Spec.ResourceReference.Namespace = "new-resource-reference-namespace"
2191 newPolicyUnstructured = newUnstructuredFromObject(t, ©Policy)
2192 assertHandleIAMPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
2193 copyPolicy = policy
2194 copyPolicy.Spec.ResourceReference.Name = "new-resource-reference-name"
2195 newPolicyUnstructured = newUnstructuredFromObject(t, ©Policy)
2196 assertHandleIAMPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
2197 copyPolicy = policy
2198 copyPolicy.Spec.ResourceReference.APIVersion = "new-resource-reference-apiversion"
2199 newPolicyUnstructured = newUnstructuredFromObject(t, ©Policy)
2200 assertHandleIAMPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
2201 }
2202
2203 func assertHandleIAMPolicy(t *testing.T, old *unstructured.Unstructured, new *unstructured.Unstructured, expectedAllowedValue bool) {
2204 t.Helper()
2205 oldSpec := getSpecFromUnstructed(t, old)
2206 newSpec := getSpecFromUnstructed(t, new)
2207 response := handleIAMPolicy(oldSpec, newSpec)
2208 if response.Allowed != expectedAllowedValue {
2209 t.Fatalf("unexpected value for Allowed: got '%v', want '%v'", response.Allowed, expectedAllowedValue)
2210 }
2211 }
2212
2213 func TestUpdateIAMPartialPolicy(t *testing.T) {
2214 policy := v1beta1.IAMPartialPolicy{
2215 TypeMeta: metav1.TypeMeta{
2216 Kind: v1beta1.IAMPolicyGVK.Kind,
2217 APIVersion: v1beta1.IAMPolicyGVK.GroupVersion().String(),
2218 },
2219 Spec: v1beta1.IAMPartialPolicySpec{
2220 ResourceReference: v1beta1.ResourceReference{
2221 Kind: "my-resource-kind",
2222 Namespace: "my-namespace",
2223 Name: "my-pubsub-topic",
2224 APIVersion: "my-api-version",
2225 },
2226 },
2227 }
2228 oldPolicyUnstructred := newUnstructuredFromObject(t, &policy)
2229 newPolicyUnstructured := newUnstructuredFromObject(t, &policy)
2230 assertHandleIAMPartialPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, true)
2231 copyPolicy := policy
2232 copyPolicy.Spec.ResourceReference.Kind = "new-resource-reference-kind"
2233 newPolicyUnstructured = newUnstructuredFromObject(t, ©Policy)
2234 assertHandleIAMPartialPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
2235 copyPolicy = policy
2236 copyPolicy.Spec.ResourceReference.Namespace = "new-resource-reference-namespace"
2237 newPolicyUnstructured = newUnstructuredFromObject(t, ©Policy)
2238 assertHandleIAMPartialPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
2239 copyPolicy = policy
2240 copyPolicy.Spec.ResourceReference.Name = "new-resource-reference-name"
2241 newPolicyUnstructured = newUnstructuredFromObject(t, ©Policy)
2242 assertHandleIAMPartialPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
2243 copyPolicy = policy
2244 copyPolicy.Spec.ResourceReference.APIVersion = "new-resource-reference-apiversion"
2245 newPolicyUnstructured = newUnstructuredFromObject(t, ©Policy)
2246 assertHandleIAMPartialPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
2247 }
2248
2249 func assertHandleIAMPartialPolicy(t *testing.T, old *unstructured.Unstructured, new *unstructured.Unstructured, expectedAllowedValue bool) {
2250 t.Helper()
2251 oldSpec := getSpecFromUnstructed(t, old)
2252 newSpec := getSpecFromUnstructed(t, new)
2253 response := handleIAMPartialPolicy(oldSpec, newSpec)
2254 if response.Allowed != expectedAllowedValue {
2255 t.Fatalf("unexpected value for Allowed: got '%v', want '%v'", response.Allowed, expectedAllowedValue)
2256 }
2257 }
2258
2259 func TestUpdateIAMPolicyMember(t *testing.T) {
2260 policyMember := v1beta1.IAMPolicyMember{
2261 TypeMeta: metav1.TypeMeta{
2262 Kind: v1beta1.IAMPolicyMemberGVK.Kind,
2263 APIVersion: v1beta1.IAMPolicyMemberGVK.GroupVersion().String(),
2264 },
2265 Spec: v1beta1.IAMPolicyMemberSpec{
2266 Member: "test@google.com",
2267 Role: "roles/editor",
2268 ResourceReference: v1beta1.ResourceReference{
2269 Kind: "my-resource-kind",
2270 Namespace: "my-namespace",
2271 Name: "my-pubsub-topic",
2272 APIVersion: "my-api-version",
2273 },
2274 },
2275 }
2276 oldPolicyMemberUnstructred := newUnstructuredFromObject(t, &policyMember)
2277 newPolicyMemberUnstructured := newUnstructuredFromObject(t, &policyMember)
2278 assertHandleIAMPolicyMember(t, oldPolicyMemberUnstructred, newPolicyMemberUnstructured, true)
2279 copyPolicyMember := policyMember
2280 copyPolicyMember.Spec.Member = "new-member"
2281 newPolicyMemberUnstructured = newUnstructuredFromObject(t, ©PolicyMember)
2282 assertHandleIAMPolicyMember(t, oldPolicyMemberUnstructred, newPolicyMemberUnstructured, false)
2283 copyPolicyMember = policyMember
2284 copyPolicyMember.Spec.Role = "new-role"
2285 newPolicyMemberUnstructured = newUnstructuredFromObject(t, ©PolicyMember)
2286 assertHandleIAMPolicyMember(t, oldPolicyMemberUnstructred, newPolicyMemberUnstructured, false)
2287 copyPolicyMember = policyMember
2288 copyPolicyMember.Spec.ResourceReference.Kind = "new-resource-reference-kind"
2289 newPolicyMemberUnstructured = newUnstructuredFromObject(t, ©PolicyMember)
2290 assertHandleIAMPolicyMember(t, oldPolicyMemberUnstructred, newPolicyMemberUnstructured, false)
2291 copyPolicyMember = policyMember
2292 copyPolicyMember.Spec.ResourceReference.Namespace = "new-resource-reference-namespace"
2293 newPolicyMemberUnstructured = newUnstructuredFromObject(t, ©PolicyMember)
2294 assertHandleIAMPolicyMember(t, oldPolicyMemberUnstructred, newPolicyMemberUnstructured, false)
2295 copyPolicyMember = policyMember
2296 copyPolicyMember.Spec.ResourceReference.Name = "new-resource-reference-name"
2297 newPolicyMemberUnstructured = newUnstructuredFromObject(t, ©PolicyMember)
2298 assertHandleIAMPolicyMember(t, oldPolicyMemberUnstructred, newPolicyMemberUnstructured, false)
2299 copyPolicyMember = policyMember
2300 copyPolicyMember.Spec.ResourceReference.APIVersion = "new-resource-reference-apiversion"
2301 newPolicyMemberUnstructured = newUnstructuredFromObject(t, ©PolicyMember)
2302 assertHandleIAMPolicyMember(t, oldPolicyMemberUnstructred, newPolicyMemberUnstructured, false)
2303 }
2304
2305 func assertHandleIAMPolicyMember(t *testing.T, old *unstructured.Unstructured, new *unstructured.Unstructured, expectedAllowedValue bool) {
2306 t.Helper()
2307 oldSpec := getSpecFromUnstructed(t, old)
2308 newSpec := getSpecFromUnstructed(t, new)
2309 response := handleIAMPolicyMember(oldSpec, newSpec)
2310 if response.Allowed != expectedAllowedValue {
2311 t.Fatalf("unexpected value for Allowed: got '%v', want '%v'", response.Allowed, expectedAllowedValue)
2312 }
2313 }
2314
2315 func TestUpdateIAMAuditConfig(t *testing.T) {
2316 auditConfig := v1beta1.IAMAuditConfig{
2317 TypeMeta: metav1.TypeMeta{
2318 Kind: v1beta1.IAMAuditConfigGVK.Kind,
2319 APIVersion: v1beta1.IAMAuditConfigGVK.GroupVersion().String(),
2320 },
2321 Spec: v1beta1.IAMAuditConfigSpec{
2322 Service: "sampleservice.googleapis.com",
2323 AuditLogConfigs: []v1beta1.AuditLogConfig{
2324 {
2325 LogType: "DATA_READ",
2326 ExemptedMembers: []v1beta1.Member{
2327 "test@google.com",
2328 },
2329 },
2330 },
2331 ResourceReference: v1beta1.ResourceReference{
2332 Kind: "my-resource-kind",
2333 Namespace: "my-namespace",
2334 Name: "my-pubsub-topic",
2335 APIVersion: "my-api-version",
2336 },
2337 },
2338 }
2339 oldAuditConfigUnstructured := newUnstructuredFromObject(t, &auditConfig)
2340 newAuditConfigUnstructured := newUnstructuredFromObject(t, &auditConfig)
2341 assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, true)
2342 copyAuditConfig := auditConfig
2343 copyAuditConfig.Spec.Service = "newservice.googleapis.com"
2344 newAuditConfigUnstructured = newUnstructuredFromObject(t, ©AuditConfig)
2345 assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, false)
2346 copyAuditConfig = auditConfig
2347 newAuditLogConfig := v1beta1.AuditLogConfig{LogType: "DATA_WRITE"}
2348 copyAuditConfig.Spec.AuditLogConfigs = append(copyAuditConfig.Spec.AuditLogConfigs, newAuditLogConfig)
2349 newAuditConfigUnstructured = newUnstructuredFromObject(t, ©AuditConfig)
2350 assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, true)
2351 copyAuditConfig = auditConfig
2352 copyAuditConfig.Spec.AuditLogConfigs[0].LogType = "ADMIN_READ"
2353 newAuditConfigUnstructured = newUnstructuredFromObject(t, ©AuditConfig)
2354 assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, true)
2355 copyAuditConfig = auditConfig
2356 copyAuditConfig.Spec.AuditLogConfigs = nil
2357 newAuditConfigUnstructured = newUnstructuredFromObject(t, ©AuditConfig)
2358 assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, true)
2359 copyAuditConfig = auditConfig
2360 copyAuditConfig.Spec.ResourceReference.Kind = "new-resource-reference-kind"
2361 newAuditConfigUnstructured = newUnstructuredFromObject(t, ©AuditConfig)
2362 assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, false)
2363 copyAuditConfig = auditConfig
2364 copyAuditConfig.Spec.ResourceReference.Namespace = "new-resource-reference-namespace"
2365 newAuditConfigUnstructured = newUnstructuredFromObject(t, ©AuditConfig)
2366 assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, false)
2367 copyAuditConfig = auditConfig
2368 copyAuditConfig.Spec.ResourceReference.Name = "new-resource-reference-name"
2369 newAuditConfigUnstructured = newUnstructuredFromObject(t, ©AuditConfig)
2370 assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, false)
2371 copyAuditConfig = auditConfig
2372 copyAuditConfig.Spec.ResourceReference.APIVersion = "new-resource-reference-apiversion"
2373 newAuditConfigUnstructured = newUnstructuredFromObject(t, ©AuditConfig)
2374 assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, false)
2375 }
2376
2377 func assertHandleIAMAuditConfig(t *testing.T, old *unstructured.Unstructured, new *unstructured.Unstructured, expectedAllowedValue bool) {
2378 t.Helper()
2379 oldSpec := getSpecFromUnstructed(t, old)
2380 newSpec := getSpecFromUnstructed(t, new)
2381 response := handleIAMAuditConfig(oldSpec, newSpec)
2382 if response.Allowed != expectedAllowedValue {
2383 t.Fatalf("unexpected value for Allowed: got '%v', want '%v'", response.Allowed, expectedAllowedValue)
2384 }
2385 }
2386
2387 func getSpecFromUnstructed(t *testing.T, u *unstructured.Unstructured) map[string]interface{} {
2388 spec, ok, err := unstructured.NestedMap(u.Object, "spec")
2389 if err != nil {
2390 t.Fatalf("unexpected error retrieving spec from '%v': %v", u.Object, err)
2391 }
2392 if !ok {
2393 t.Fatalf("unexpected false value for 'ok' when retrieving spec from '%v'", u.Object)
2394 }
2395 return spec
2396 }
2397
View as plain text