1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package servicemapping_test
16
17 import (
18 "fmt"
19 "reflect"
20 "strings"
21 "testing"
22
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/crd/crdloader"
25 dclmetadata "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/metadata"
26 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gvks/supportedgvks"
27 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
28 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/krmtotf"
29 testservicemappingloader "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/servicemappingloader"
30 tfprovider "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/tf/provider"
31 tfresource "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/tf/resource"
32 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util/slice"
33
34 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
35 k8sschema "k8s.io/apimachinery/pkg/runtime/schema"
36 )
37
38 var (
39 emptyTypeConfig = v1alpha1.TypeConfig{}
40 emptyIAMConfig = v1alpha1.IAMConfig{}
41 )
42
43 func TestIDTemplateCanBeUsedToMatchResourceNameShouldHaveValue(t *testing.T) {
44 t.Parallel()
45 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
46 for _, sm := range serviceMappings {
47 for _, rc := range sm.Spec.Resources {
48 if rc.IDTemplateCanBeUsedToMatchResourceName == nil {
49 t.Fatalf("resource config '%v' is missing required field 'IDTemplateCanBeUsedToMatchResourceName'",
50 rc.Name)
51 }
52 }
53 }
54 }
55
56 func TestNamingConventions(t *testing.T) {
57 t.Parallel()
58 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
59 for _, sm := range serviceMappings {
60 switch sm.Spec.Name {
61 case "CloudBuild", "CloudIdentity", "CloudIOT", "CloudTasks", "CloudAsset", "CloudIDS", "CloudFunctions2":
62
63
64
65
66
67
68
69
70 continue
71 }
72 if strings.HasPrefix(sm.Spec.Name, "Cloud") {
73 t.Fatalf("invalid service mapping name '%v': 'Cloud' should be dropped from any service name of which it is not an integral part", sm.Spec.Name)
74 }
75 for _, rc := range sm.Spec.Resources {
76 if strings.HasPrefix(rc.Kind, "Cloud") {
77 t.Fatalf("invalid resource kind '%v': 'Cloud' should be dropped from the service portion of any resource name", rc.Kind)
78 }
79 }
80 }
81 }
82
83 func TestServiceHostName(t *testing.T) {
84 t.Parallel()
85 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
86 for _, sm := range serviceMappings {
87 hostName := sm.Spec.ServiceHostName
88 if hostName == "" {
89 t.Fatalf("unexpected empty value for ServiceHostName for service mapping '%v'", sm.Name)
90 }
91 if !strings.HasSuffix(hostName, "googleapis.com") {
92 t.Fatalf("unexpected empty value for ServiceHostName for service mapping '%v': expected suffix of 'googleapis.com'", sm.Name)
93 }
94 }
95 }
96
97 func TestIAMPolicyMappings(t *testing.T) {
98 t.Parallel()
99 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
100 for _, sm := range serviceMappings {
101 for _, rc := range sm.Spec.Resources {
102 rc := rc
103
104
105 if rc.Kind == "ComputeBackendService" {
106 continue
107 }
108 t.Run(rc.Kind, func(t *testing.T) {
109 t.Parallel()
110
111 if isAutogenAlphaResource(&sm, &rc) {
112 return
113 }
114 testIamPolicyMappings(t, rc)
115 })
116 }
117 }
118 }
119
120 func TestIAMPolicyMappingsForKindsWithMultipleResourceConfigs(t *testing.T) {
121 t.Parallel()
122 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
123 for _, sm := range serviceMappings {
124 sm := sm
125 t.Run(sm.Name, func(t *testing.T) {
126 t.Parallel()
127 kindToRCs := make(map[string][]v1alpha1.ResourceConfig)
128 for _, rc := range sm.Spec.Resources {
129 kindToRCs[rc.Kind] = append(kindToRCs[rc.Kind], rc)
130 }
131 for kind, rcs := range kindToRCs {
132 if len(rcs) < 2 {
133 continue
134 }
135 kind := kind
136 rcs := rcs
137 t.Run(kind, func(t *testing.T) {
138 t.Parallel()
139 assertAllHaveEmptyOrNonEmptyIAMConfigButNotBoth(t, kind, rcs)
140 assertAllHaveSameValueForSupportsConditions(t, kind, rcs)
141 assertAllOrNoneSupportAuditConfigs(t, kind, rcs)
142 })
143 }
144 })
145 }
146 }
147
148 func TestKindsWithMultipleResourceConfigsHaveSameDescriptionsForSameReferences(t *testing.T) {
149 t.Parallel()
150 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
151 for _, sm := range serviceMappings {
152 sm := sm
153 t.Run(sm.Name, func(t *testing.T) {
154 t.Parallel()
155 kindToRCs := make(map[string][]v1alpha1.ResourceConfig)
156 for _, rc := range sm.Spec.Resources {
157 kindToRCs[rc.Kind] = append(kindToRCs[rc.Kind], rc)
158 }
159 for kind, rcs := range kindToRCs {
160 if len(rcs) < 2 {
161 continue
162 }
163 kind := kind
164 rcs := rcs
165 t.Run(kind, func(t *testing.T) {
166 t.Parallel()
167 assertAllHaveSameDescriptionsForSameReferences(t, kind, rcs)
168 })
169 }
170 })
171 }
172 }
173
174 func assertAllHaveSameDescriptionsForSameReferences(t *testing.T, kind string, rcs []v1alpha1.ResourceConfig) {
175 t.Helper()
176 tfFieldToDescription := make(map[string]string)
177 for _, rc := range rcs {
178 for _, ref := range rc.ResourceReferences {
179 if _, ok := tfFieldToDescription[ref.TFField]; !ok {
180 tfFieldToDescription[ref.TFField] = ref.Description
181 continue
182 }
183 description := tfFieldToDescription[ref.TFField]
184 if ref.Description != description {
185 t.Errorf("all ResourceConfigs of kind %v must have the same descriptions "+
186 "for all resource references with the same tfField, but not "+
187 "all resource references with tfField %v have the same descriptions", kind, ref.TFField)
188 }
189 }
190 }
191 }
192
193 func TestResourcesListedAlphabetically(t *testing.T) {
194 t.Parallel()
195 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
196 for _, sm := range serviceMappings {
197 sm := sm
198 t.Run(sm.Name, func(t *testing.T) {
199 t.Parallel()
200 var prev string
201 for _, curr := range sm.Spec.Resources {
202 if prev > curr.Name {
203 t.Errorf("resources not listed alphabetically: %v listed before %v", prev, curr.Name)
204 }
205 prev = curr.Name
206 }
207 })
208 }
209 }
210
211 func TestTerraformFieldsAreInResourceSchema(t *testing.T) {
212 t.Parallel()
213 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
214 provider := tfprovider.NewOrLogFatal(tfprovider.DefaultConfig)
215 for _, sm := range serviceMappings {
216 sm := sm
217 t.Run(sm.Name, func(t *testing.T) {
218 t.Parallel()
219 for _, rc := range sm.Spec.Resources {
220 tfResource := provider.ResourcesMap[rc.Name]
221
222
223 fields := []string{
224 rc.MetadataMapping.Name,
225 rc.MetadataMapping.Labels,
226 rc.ServerGeneratedIDField,
227 }
228 for _, refConfig := range rc.ResourceReferences {
229 fields = append(fields, refConfig.TFField)
230 }
231 for _, d := range rc.Directives {
232 fields = append(fields, d)
233 }
234 for _, f := range rc.IgnoredFields {
235 fields = append(fields, f)
236 }
237 for _, c := range rc.Containers {
238 fields = append(fields, c.TFField)
239 }
240
241 for _, f := range fields {
242 if f == "" {
243 continue
244 }
245 if !tfresource.TFResourceHasField(tfResource, f) {
246
247 if rc.Name == "google_apigee_addons_config" {
248 t.Logf("field '%v' mentioned in ServiceMapping for the auto-generated v1alpha1 resource '%v' but is not found in resource schema", f, rc.Name)
249 } else {
250 t.Errorf("field '%v' mentioned in ServiceMapping for '%v' but is not found in resource schema", f, rc.Name)
251 }
252 }
253 }
254 }
255 })
256 }
257 }
258
259 func TestReferencedTargetFieldsAreInReferencedResourceSchema(t *testing.T) {
260 t.Parallel()
261 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
262 provider := tfprovider.NewOrLogFatal(tfprovider.DefaultConfig)
263 kindToTFResources := createKindToTFResourcesMap(serviceMappings)
264 for _, sm := range serviceMappings {
265 sm := sm
266 t.Run(sm.Name, func(t *testing.T) {
267 t.Parallel()
268 for _, rc := range sm.Spec.Resources {
269 rc := rc
270 t.Run(rc.Kind, func(t *testing.T) {
271 t.Parallel()
272 testReferencedTargetFieldsAreInReferencedResourceSchema(t, rc, provider, kindToTFResources)
273 })
274 }
275 })
276 }
277 fmt.Println(kindToTFResources)
278 }
279
280 func testReferencedTargetFieldsAreInReferencedResourceSchema(t *testing.T, rc v1alpha1.ResourceConfig, provider *schema.Provider, kindToTFResources map[string][]string) {
281 t.Helper()
282 for _, ref := range rc.ResourceReferences {
283 for _, tc := range typeConfigsOf(ref) {
284 if tc.TargetField == "" {
285
286
287
288
289 continue
290 }
291 if tc.GVK.Kind == "" {
292 t.Errorf("kind %v has a resource reference with a targetField specified as %v but has no kind specified", rc.Kind, tc.TargetField)
293 continue
294 }
295 for _, referencedTFResourceName := range kindToTFResources[tc.GVK.Kind] {
296 referencedTFResource := provider.ResourcesMap[referencedTFResourceName]
297 if !tfresource.TFResourceHasField(referencedTFResource, tc.TargetField) {
298 t.Errorf("kind %v has a resource reference with kind %v and targetField %v, "+
299 "but this field does not exist in the Terraform resource %v",
300 rc.Kind, tc.GVK.Kind, tc.TargetField, referencedTFResourceName)
301 }
302 }
303 }
304 }
305 }
306
307 func TestResourceReferencesAreValid(t *testing.T) {
308 t.Parallel()
309 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
310 for _, sm := range serviceMappings {
311 for _, rc := range sm.Spec.Resources {
312 rc := rc
313 t.Run(rc.Kind, func(t *testing.T) {
314 t.Parallel()
315 if isAutogenAlphaResource(&sm, &rc) {
316 return
317 }
318 validateResourceReferences(t, rc)
319 })
320 }
321 }
322 }
323
324 func validateResourceReferences(t *testing.T, rc v1alpha1.ResourceConfig) {
325 if len(rc.ResourceReferences) == 0 {
326 return
327 }
328 assertHasAtMostOneReferenceConfigPerField(t, rc)
329 for _, refConfig := range rc.ResourceReferences {
330 if len(refConfig.Types) == 0 {
331 assertTypeConfig(t, rc, refConfig, refConfig.TypeConfig)
332 } else {
333 if !reflect.DeepEqual(refConfig.TypeConfig, emptyTypeConfig) {
334 t.Errorf("should not fill the inline TypeConfig if Types is specified")
335 }
336 for _, typeConfig := range refConfig.Types {
337 assertTypeConfig(t, rc, refConfig, typeConfig)
338 }
339 for _, typeConfig := range refConfig.Types {
340 if typeConfig.Key == "" {
341 t.Errorf("the ReferenceConfig for tfField %v has multiple types, but not all types have a key specified, like: %+v", refConfig.TFField, typeConfig)
342 }
343 }
344 }
345 }
346 }
347
348 func assertHasAtMostOneReferenceConfigPerField(t *testing.T, rc v1alpha1.ResourceConfig) {
349 t.Helper()
350 tfFields := make(map[string]bool)
351 for _, refConfig := range rc.ResourceReferences {
352 tfField := refConfig.TFField
353 if tfField == "" {
354 t.Errorf("tfField value doesn't exist for the reference config")
355 }
356 if _, ok := tfFields[tfField]; ok {
357 t.Errorf("tfField %v has more than one reference config", tfField)
358 }
359 tfFields[tfField] = true
360 }
361 }
362
363 func assertTypeConfig(t *testing.T, rc v1alpha1.ResourceConfig, ref v1alpha1.ReferenceConfig, tc v1alpha1.TypeConfig) {
364 gvkUnspecified := tc.GVK.Group == "" && tc.GVK.Version == "" && tc.GVK.Kind == ""
365 if gvkUnspecified && tc.JSONSchemaType == "" {
366 t.Errorf("the TypeConfig for tfField %v doesn't have either a GVK or a JSONSchemaType", ref.TFField)
367 }
368 if !gvkUnspecified && tc.JSONSchemaType != "" {
369 t.Errorf("the TypeConfig for tfField %v has both GVK and JSONSchemaType defined; they should be mutually exclusive", ref.TFField)
370 }
371 if !gvkUnspecified {
372 validateTypeConfigGVK(t, rc, ref, tc)
373 }
374 }
375
376 func validateTypeConfigGVK(t *testing.T, rc v1alpha1.ResourceConfig, ref v1alpha1.ReferenceConfig, tc v1alpha1.TypeConfig) {
377 gvk := tc.GVK
378 if gvk.Kind == "" {
379 t.Fatalf("invalid resource reference '%v' on resource '%v' with key '%v': the field 'kind' must have a value", ref.TFField, rc.Kind, tc.Key)
380 }
381 if gvk.Group == "" {
382 t.Fatalf("invalid resource reference '%v' on resource '%v' with key '%v': the field 'group' must have a value", ref.TFField, rc.Kind, tc.Key)
383 }
384 if gvk.Version == "" {
385 t.Fatalf("invalid resource reference '%v' on resource '%v' with key '%v': the field 'version' must have a value", ref.TFField, rc.Kind, tc.Key)
386 }
387
388
389 billingGroup := "billing.cnrm.cloud.google.com"
390 if gvk.Group == billingGroup {
391 _, err := testservicemappingloader.New(t).GetServiceMapping(billingGroup)
392 if err == nil {
393 t.Fatalf("a service mapping for billing has been added -- delete this code block (see comment above)")
394 }
395 return
396 }
397
398
399
400
401 resourceManagerGroup := "resourcemanager.cnrm.cloud.google.com"
402 if gvk.Group == resourceManagerGroup {
403 sm, err := testservicemappingloader.New(t).GetServiceMapping(resourceManagerGroup)
404 if err != nil {
405 t.Fatalf("expected resource manager service mapping but there was none")
406 }
407 for _, r := range sm.Spec.Resources {
408 if r.Kind == "Organization" {
409 t.Fatalf("a resource for organizations has been added -- delete this code block (see comment above)")
410 }
411 }
412 return
413 }
414
415
416
417
418
419 ignoredGVKList := []k8sschema.GroupVersionKind{
420 {
421 Group: "networksecurity.cnrm.cloud.google.com",
422 Version: "v1beta1",
423 Kind: "NetworkSecurityClientTLSPolicy",
424 },
425 {
426 Group: "certificatemanager.cnrm.cloud.google.com",
427 Version: "v1beta1",
428 Kind: "CertificateManagerCertificateMap",
429 },
430 {
431 Group: "cloudbuild.cnrm.cloud.google.com",
432 Version: "v1beta1",
433 Kind: "CloudBuildGithubEnterpriseConfig",
434 },
435 {
436 Group: "cloudbuild.cnrm.cloud.google.com",
437 Version: "v1beta1",
438 Kind: "CloudBuildBitbucketServerConfig",
439 },
440 {
441 Group: "cloudbuild.cnrm.cloud.google.com",
442 Version: "v1beta1",
443 Kind: "CloudBuildV2Repository",
444 },
445 }
446 for _, g := range ignoredGVKList {
447 if gvk == g {
448 return
449 }
450 }
451
452 crd, err := crdloader.GetCRD(gvk.Group, gvk.Version, gvk.Kind)
453 if err != nil {
454 t.Fatalf("bad resource reference '%v' on resource '%v': error getting crd: %v", ref.TFField, rc.Kind, err)
455 }
456 crdGvk := k8sschema.GroupVersionKind{
457 Group: crd.Spec.Group,
458 Version: k8s.GetVersionFromCRD(crd),
459 Kind: crd.Spec.Names.Kind,
460 }
461 if gvk != crdGvk {
462 t.Fatalf("crd and service mappings reference mismatch for reference '%v' on resource '%v' with key '%v': service mappings '%v', crd '%v'",
463 ref.TFField, rc.Kind, tc.Key, gvk, crdGvk)
464 }
465 }
466
467 func TestHierarchicalReferences(t *testing.T) {
468 t.Parallel()
469 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
470 for _, sm := range serviceMappings {
471 sm := sm
472 t.Run(sm.Name, func(t *testing.T) {
473 t.Parallel()
474 for _, rc := range sm.Spec.Resources {
475 rc := rc
476 t.Run(rc.Kind, func(t *testing.T) {
477 t.Parallel()
478 testHierarchicalReferences(t, rc)
479 })
480 }
481 })
482 }
483 }
484
485 func testHierarchicalReferences(t *testing.T, rc v1alpha1.ResourceConfig) {
486
487
488 if !krmtotf.SupportsHierarchicalReferences(&rc) {
489 return
490 }
491 assertHasAtMostOneOfEachHierarchicalReferenceType(t, rc)
492 for _, hierarchicalRef := range rc.HierarchicalReferences {
493 assertHasRootLevelResourceReference(t, rc, hierarchicalRef.Key)
494 }
495 for _, container := range rc.Containers {
496 assertHasHierarchicalReferenceForContainerType(t, rc, container.Type)
497 }
498 }
499
500 func assertHasAtMostOneOfEachHierarchicalReferenceType(t *testing.T, rc v1alpha1.ResourceConfig) {
501 t.Helper()
502 supportedTypes := make(map[v1alpha1.HierarchicalReferenceType]bool)
503 for _, hierarchicalRef := range rc.HierarchicalReferences {
504 if _, ok := supportedTypes[hierarchicalRef.Type]; ok {
505 t.Fatalf("kind %v has more than one hierarchical reference with type %v", rc.Kind, hierarchicalRef.Type)
506 }
507 supportedTypes[hierarchicalRef.Type] = true
508 }
509 }
510
511 func assertHasRootLevelResourceReference(t *testing.T, rc v1alpha1.ResourceConfig, key string) {
512 t.Helper()
513 if strings.Contains(key, ".") {
514 t.Fatalf("key %v is a path, not a field", key)
515 }
516 for _, ref := range rc.ResourceReferences {
517 if strings.Contains(ref.TFField, ".") {
518
519 continue
520 }
521 if krmtotf.GetKeyForReferenceField(&ref) == key {
522 return
523 }
524 }
525 t.Fatalf("kind %v does not have a root-level resource reference with key %v", rc.Kind, key)
526 }
527
528 func assertHasHierarchicalReferenceForContainerType(t *testing.T, rc v1alpha1.ResourceConfig, containerType v1alpha1.ContainerType) {
529 t.Helper()
530 hierarchicalType := k8s.HierarchicalReferenceTypeFor(containerType)
531 for _, hierarchicalRef := range rc.HierarchicalReferences {
532 if hierarchicalRef.Type == hierarchicalType {
533 return
534 }
535 }
536 t.Fatalf("kind %v has a container of type %v, but no hierarchical reference of type %v", rc.Kind, containerType, hierarchicalType)
537 }
538
539 func TestHierarchicalReferencesForKindsWithMultipleResourceConfigs(t *testing.T) {
540 t.Parallel()
541 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
542 for _, sm := range serviceMappings {
543 sm := sm
544 t.Run(sm.Name, func(t *testing.T) {
545 t.Parallel()
546 kindToRCs := make(map[string][]v1alpha1.ResourceConfig)
547 for _, rc := range sm.Spec.Resources {
548 kindToRCs[rc.Kind] = append(kindToRCs[rc.Kind], rc)
549 }
550 for kind, rcs := range kindToRCs {
551 if len(rcs) < 2 {
552 continue
553 }
554 kind := kind
555 rcs := rcs
556 t.Run(kind, func(t *testing.T) {
557 t.Parallel()
558 assertAllHaveSameHierarchicalReferences(t, kind, rcs)
559 })
560 }
561 })
562 }
563 }
564
565 func assertAllHaveSameHierarchicalReferences(t *testing.T, kind string, rcs []v1alpha1.ResourceConfig) {
566 t.Helper()
567 if len(rcs) == 0 {
568 return
569 }
570 hierarchicalRefs := rcs[0].HierarchicalReferences
571 for _, rc := range rcs {
572 if !reflect.DeepEqual(rc.HierarchicalReferences, hierarchicalRefs) {
573 t.Errorf("not all ResourceConfigs of kind %v have the same HierarchicalReferences configuration", kind)
574 }
575 }
576 }
577
578 func TestMustHaveIDTemplateOrServerGeneratedId(t *testing.T) {
579 t.Parallel()
580 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
581 for _, sm := range serviceMappings {
582 for _, rc := range sm.Spec.Resources {
583 rc := rc
584 t.Run(rc.Kind, func(t *testing.T) {
585 t.Parallel()
586 assertIDTemplateOrServerGeneratedId(t, rc)
587 })
588 }
589 }
590 }
591
592 func assertIDTemplateOrServerGeneratedId(t *testing.T, rc v1alpha1.ResourceConfig) {
593 if rc.IDTemplate == "" && rc.ServerGeneratedIDField == "" {
594 t.Fatalf("resource kind '%v' with name '%v' has neither id template or server generated ID defined: at least one must be present", rc.Kind, rc.Name)
595 }
596 }
597
598 func TestIDTemplate(t *testing.T) {
599 t.Parallel()
600 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
601 for _, sm := range serviceMappings {
602 sm := sm
603 t.Run(sm.Name, func(t *testing.T) {
604 t.Parallel()
605 for _, rc := range sm.Spec.Resources {
606 rc := rc
607 t.Run(rc.Kind, func(t *testing.T) {
608 t.Parallel()
609 if rc.IDTemplate == "" {
610 return
611 }
612
613
614
615 if rc.MetadataMapping.Name == "" && rc.ServerGeneratedIDField == "" {
616 return
617 }
618
619
620
621 if (IDTemplateContainsMetadataName(t, rc) &&
622 !IDTemplateContainsServerGeneratedIDField(t, rc)) ||
623 (!IDTemplateContainsMetadataName(t, rc) &&
624 IDTemplateContainsServerGeneratedIDField(t, rc)) {
625 return
626 }
627
628 t.Fatalf("idTemplate of resource kind '%v' with name "+
629 "'%v' contains 0 or 2 field names defined in "+
630 "'metadata.name' and 'serverGeneratedIDField': "+
631 "exactly one should be contained", rc.Kind, rc.Name)
632 })
633 }
634 })
635 }
636 }
637
638 func IDTemplateContainsMetadataName(t *testing.T, rc v1alpha1.ResourceConfig) bool {
639 return strings.Contains(rc.IDTemplate,
640 fmt.Sprintf("{{%v}}", rc.MetadataMapping.Name))
641 }
642
643 func IDTemplateContainsServerGeneratedIDField(t *testing.T, rc v1alpha1.ResourceConfig) bool {
644 return strings.Contains(rc.IDTemplate,
645 fmt.Sprintf("{{%v}}", rc.ServerGeneratedIDField))
646 }
647
648 func TestMutableButUnreadableFields(t *testing.T) {
649 t.Parallel()
650 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
651 provider := tfprovider.NewOrLogFatal(tfprovider.DefaultConfig)
652 for _, sm := range serviceMappings {
653 sm := sm
654 t.Run(sm.Name, func(t *testing.T) {
655 t.Parallel()
656 for _, rc := range sm.Spec.Resources {
657 rc := rc
658 t.Run(rc.Kind, func(t *testing.T) {
659 t.Parallel()
660 testMutableButUnreadableFields(t, rc, provider)
661 })
662 }
663 })
664 }
665 }
666
667 func testIamPolicyMappings(t *testing.T, rc v1alpha1.ResourceConfig) {
668 if rc.IAMConfig.PolicyName == "" {
669 assertIAMConfigIsEmpty(t, rc)
670 assertIAMConfigShouldBeEmpty(t, rc)
671 } else {
672 assertIAMConfigValueIsValid(t, rc)
673 }
674 }
675
676 func assertIAMConfigShouldBeEmpty(t *testing.T, rc v1alpha1.ResourceConfig) {
677 t.Helper()
678
679
680
681 switch rc.Name {
682 case "google_bigquery_dataset", "google_compute_region_disk", "google_compute_disk":
683 return
684 }
685 tfIamPolicyResourceName, tfIamPolicyResource := getAssociatedTerraformIAMPolicyResource(rc)
686 if tfIamPolicyResource != nil {
687 t.Errorf("kind '%v' is missing a valid IAMConfig, but a valid terraform IAM Policy resource '%v' exists",
688 tfIamPolicyResourceName, tfIamPolicyResourceName)
689 }
690 }
691
692 func assertAllHaveEmptyOrNonEmptyIAMConfigButNotBoth(t *testing.T, kind string, rcs []v1alpha1.ResourceConfig) {
693 t.Helper()
694 if len(rcs) == 0 {
695 return
696 }
697 hasEmptyIAM := hasEmptyIAMConfig(rcs[0])
698 for _, rc := range rcs {
699 if hasEmptyIAMConfig(rc) != hasEmptyIAM {
700 t.Errorf("all ResourceConfigs of kind %v must all have an empty or non-empty iamConfig, but not a mixture of both", kind)
701 }
702 }
703 }
704
705 func assertIAMConfigIsEmpty(t *testing.T, rc v1alpha1.ResourceConfig) {
706 t.Helper()
707 if !hasEmptyIAMConfig(rc) {
708 t.Errorf("invalid argument, iamConfig for resource '%v' is non-empty", rc.Kind)
709 }
710 }
711
712 func assertIAMConfigValueIsValid(t *testing.T, rc v1alpha1.ResourceConfig) {
713 t.Helper()
714 if rc.IAMConfig.ReferenceField.Name == "" {
715 t.Errorf("invalid value for Name: value must be present")
716 }
717
718 tfIamPolicyResourceName, tfIamPolicyResource := getAssociatedTerraformIAMPolicyResource(rc)
719 if rc.IAMConfig.PolicyName != tfIamPolicyResourceName {
720
721 t.Fatalf("tf iampolicy name mismatch for kind '%v': value of '%v' does not match expected value of '%v'",
722 rc.Kind, rc.IAMConfig.PolicyName, tfIamPolicyResourceName)
723 }
724 _, ok := tfIamPolicyResource.Schema[rc.IAMConfig.ReferenceField.Name]
725 if !ok {
726 t.Errorf("kind '%v' has an invalid value for ReferenceFieldName '%v': the terraform resource '%v' does not contain any field with that"+
727 " name", rc.Kind, rc.IAMConfig.ReferenceField.Name, tfIamPolicyResourceName)
728 }
729
730 tfIamPolicyMemberResourceName, tfIamPolicyMemberResource := getAssociatedTerraformIAMPolicyMemberResource(rc)
731 if rc.IAMConfig.PolicyMemberName != tfIamPolicyMemberResourceName {
732
733 t.Fatalf("tf iampolicy member name mismatch for kind '%v': value of '%v' does not match expected value of '%v'",
734 rc.Kind, rc.IAMConfig.PolicyMemberName, tfIamPolicyMemberResourceName)
735 }
736 _, ok = tfIamPolicyMemberResource.Schema[rc.IAMConfig.ReferenceField.Name]
737 if !ok {
738 t.Errorf("kind '%v' has an invalid value for ReferenceFieldName '%v': the terraform resource '%v' does not contain any field with that"+
739 " name", rc.Kind, rc.IAMConfig.ReferenceField.Name, tfIamPolicyMemberResourceName)
740 }
741
742 if rc.IAMConfig.AuditConfigName != "" {
743 tfIamAuditConfigResourceName, tfIamAuditConfigResource := getAssociatedTerraformIAMAuditConfigResource(rc)
744 if rc.IAMConfig.AuditConfigName != tfIamAuditConfigResourceName {
745
746 t.Fatalf("tf auditconfig name mismatch for kind '%v': value of '%v' does not match expected value of '%v'",
747 rc.Kind, rc.IAMConfig.AuditConfigName, tfIamAuditConfigResourceName)
748 }
749 _, ok = tfIamAuditConfigResource.Schema[rc.IAMConfig.ReferenceField.Name]
750 if !ok {
751 t.Errorf("kind '%v' has an invalid value for ReferenceFieldName '%v': the terraform resource '%v' does not contain any field with that"+
752 " name", rc.Kind, rc.IAMConfig.ReferenceField.Name, tfIamAuditConfigResourceName)
753 }
754 }
755
756 assertValidAndUsableIAMReferenceValueType(t, rc)
757 }
758
759 func assertValidAndUsableIAMReferenceValueType(t *testing.T, rc v1alpha1.ResourceConfig) {
760 t.Helper()
761 value := rc.IAMConfig.ReferenceField.Type
762 switch value {
763 case v1alpha1.IAMReferenceTypeName:
764 case v1alpha1.IAMReferenceTypeId:
765 if rc.IDTemplate == "" && rc.ServerGeneratedIDField == "" {
766 msg := "to use this value type, either the IDTemplate or ServerGeneratedIDField fields must contain a value"
767 t.Errorf("invalid usage of reference value type '%v': %v", value, msg)
768 }
769 default:
770 t.Errorf("unknown value type value: %v", value)
771 }
772 }
773
774 func assertAllHaveSameValueForSupportsConditions(t *testing.T, kind string, rcs []v1alpha1.ResourceConfig) {
775 t.Helper()
776 if len(rcs) == 0 {
777 return
778 }
779 supportsConditions := rcs[0].IAMConfig.SupportsConditions
780 for _, rc := range rcs {
781 if rc.IAMConfig.SupportsConditions != supportsConditions {
782 t.Errorf("not all ResourceConfigs of kind %v have the same value for iamConfig.supportsConditions", kind)
783 }
784 }
785 }
786
787 func assertAllOrNoneSupportAuditConfigs(t *testing.T, kind string, rcs []v1alpha1.ResourceConfig) {
788 t.Helper()
789 if len(rcs) == 0 {
790 return
791 }
792 supportsAuditConfigs := rcs[0].IAMConfig.AuditConfigName != ""
793 for _, rc := range rcs {
794 rcSupportsAuditConfigs := rc.IAMConfig.AuditConfigName != ""
795 if rcSupportsAuditConfigs != supportsAuditConfigs {
796 t.Errorf("all ResourceConfigs of kind %v must support or not support IAM audit configs, but not a mixture of both", kind)
797 }
798 }
799 }
800
801 func getAssociatedTerraformIAMPolicyResource(rc v1alpha1.ResourceConfig) (string, *schema.Resource) {
802 schemaProvider := tfprovider.NewOrLogFatal(tfprovider.DefaultConfig)
803 tfIamPolicyResourceName := formatAssociatedTerraformIAMPolicyResourceName(rc)
804 return tfIamPolicyResourceName, schemaProvider.ResourcesMap[tfIamPolicyResourceName]
805 }
806
807 func formatAssociatedTerraformIAMPolicyResourceName(rc v1alpha1.ResourceConfig) string {
808 switch rc.Name {
809 case "google_compute_instance_from_template":
810 return "google_compute_instance_iam_policy"
811 default:
812 return fmt.Sprintf("%v_iam_policy", rc.Name)
813
814 }
815 }
816
817 func getAssociatedTerraformIAMPolicyMemberResource(rc v1alpha1.ResourceConfig) (string, *schema.Resource) {
818 schemaProvider := tfprovider.NewOrLogFatal(tfprovider.DefaultConfig)
819 tfIamPolicyResourceName := formatAssociatedTerraformIAMPolicyMemberResourceName(rc)
820 return tfIamPolicyResourceName, schemaProvider.ResourcesMap[tfIamPolicyResourceName]
821 }
822
823 func formatAssociatedTerraformIAMPolicyMemberResourceName(rc v1alpha1.ResourceConfig) string {
824 switch rc.Name {
825 case "google_compute_instance_from_template":
826 return "google_compute_instance_iam_member"
827 default:
828 return fmt.Sprintf("%v_iam_member", rc.Name)
829 }
830 }
831
832 func getAssociatedTerraformIAMAuditConfigResource(rc v1alpha1.ResourceConfig) (string, *schema.Resource) {
833 schemaProvider := tfprovider.NewOrLogFatal(tfprovider.DefaultConfig)
834 tfIamAuditConfigResourceName := formatAssociatedTerraformIAMAuditConfigResourceName(rc)
835 return tfIamAuditConfigResourceName, schemaProvider.ResourcesMap[tfIamAuditConfigResourceName]
836 }
837
838 func formatAssociatedTerraformIAMAuditConfigResourceName(rc v1alpha1.ResourceConfig) string {
839 return fmt.Sprintf("%v_iam_audit_config", rc.Name)
840 }
841
842 func hasEmptyIAMConfig(rc v1alpha1.ResourceConfig) bool {
843 return reflect.DeepEqual(rc.IAMConfig, emptyIAMConfig)
844 }
845
846 func createKindToTFResourcesMap(sms []v1alpha1.ServiceMapping) map[string][]string {
847 kindToTFResources := make(map[string][]string)
848 for _, sm := range sms {
849 for _, rc := range sm.Spec.Resources {
850 if _, ok := kindToTFResources[rc.Kind]; !ok {
851 kindToTFResources[rc.Kind] = make([]string, 0)
852 }
853 kindToTFResources[rc.Kind] = slice.IncludeString(kindToTFResources[rc.Kind], rc.Name)
854 }
855 }
856 return kindToTFResources
857 }
858
859 func TestIAMMemberReferenceConfig(t *testing.T) {
860 t.Parallel()
861 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
862 provider := tfprovider.NewOrLogFatal(tfprovider.DefaultConfig)
863 for _, sm := range serviceMappings {
864 sm := sm
865 t.Run(sm.Name, func(t *testing.T) {
866 t.Parallel()
867 for _, rc := range sm.Spec.Resources {
868 rc := rc
869 t.Run(rc.Kind, func(t *testing.T) {
870 t.Parallel()
871 iamMemberRefConfig := rc.IAMMemberReferenceConfig
872 if iamMemberRefConfig.TargetField != "" {
873 testIAMMemberReferenceConfig(t, rc, provider)
874 }
875 })
876 }
877 })
878 }
879 }
880
881 func testIAMMemberReferenceConfig(t *testing.T, rc v1alpha1.ResourceConfig, provider *schema.Provider) {
882 tfResource := provider.ResourcesMap[rc.Name]
883 targetField := rc.IAMMemberReferenceConfig.TargetField
884 if !tfresource.TFResourceHasField(tfResource, targetField) {
885 t.Errorf("kind %v has its iamMemberReference.targetField set to %v, "+
886 "but no such field exists in the Terraform resource %v",
887 rc.Kind, targetField, rc.Name)
888 }
889 }
890
891 func TestResourceIDForKindsWithMultipleResourceConfigs(t *testing.T) {
892 t.Parallel()
893 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
894 for _, sm := range serviceMappings {
895 sm := sm
896 t.Run(sm.Name, func(t *testing.T) {
897 t.Parallel()
898 kindToRCs := make(map[string][]v1alpha1.ResourceConfig)
899 for _, rc := range sm.Spec.Resources {
900 kindToRCs[rc.Kind] = append(kindToRCs[rc.Kind], rc)
901 }
902 for kind, rcs := range kindToRCs {
903 if len(rcs) < 2 {
904 continue
905 }
906 kind := kind
907 rcs := rcs
908 t.Run(kind, func(t *testing.T) {
909 t.Parallel()
910 assertAllHaveSameResourceIDConfigs(t, kind, rcs)
911 })
912 }
913 })
914 }
915 }
916
917 func assertAllHaveSameResourceIDConfigs(t *testing.T, kind string, rcs []v1alpha1.ResourceConfig) {
918 t.Helper()
919 if len(rcs) == 0 {
920 return
921 }
922
923 targetField := rcs[0].ResourceID.TargetField
924 valueTemplate := rcs[0].ResourceID.ValueTemplate
925 for _, rc := range rcs {
926 if rc.ResourceID.TargetField != targetField || rc.ResourceID.ValueTemplate != valueTemplate {
927 t.Fatalf("not all ResourceConfigs of kind %v have the same value for resourceID.targetField or resourceID.valueTemplate", kind)
928 }
929 }
930 }
931
932 func TestVersionForKindsWithMultipleResourceConfigs(t *testing.T) {
933 t.Parallel()
934 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
935 for _, sm := range serviceMappings {
936 sm := sm
937 t.Run(sm.Name, func(t *testing.T) {
938 t.Parallel()
939 kindToRCs := make(map[string][]v1alpha1.ResourceConfig)
940 for _, rc := range sm.Spec.Resources {
941 kindToRCs[rc.Kind] = append(kindToRCs[rc.Kind], rc)
942 }
943 for kind, rcs := range kindToRCs {
944 if len(rcs) < 2 {
945 continue
946 }
947 kind := kind
948 rcs := rcs
949 t.Run(kind, func(t *testing.T) {
950 t.Parallel()
951 assertAllHaveSameVersion(t, kind, rcs, &sm)
952 })
953 }
954 })
955 }
956 }
957
958 func assertAllHaveSameVersion(t *testing.T, kind string, rcs []v1alpha1.ResourceConfig, sm *v1alpha1.ServiceMapping) {
959 t.Helper()
960 if len(rcs) == 0 {
961 return
962 }
963
964 version := sm.GetVersionFor(&rcs[0])
965
966 for _, rc := range rcs {
967 if newVersion := sm.GetVersionFor(&rc); newVersion != version {
968 t.Fatalf("ResourceConfigs of kind %v have more than one version: %v, %v", kind, version, newVersion)
969 }
970 }
971 }
972
973 func testMutableButUnreadableFields(t *testing.T, rc v1alpha1.ResourceConfig, provider *schema.Provider) {
974 tfResource := provider.ResourcesMap[rc.Name]
975 for _, field := range rc.MutableButUnreadableFields {
976 tfSchema, err := tfresource.GetTFSchemaForField(tfResource, field)
977 if err != nil {
978 t.Fatalf("error getting Terraform schema for field '%v': %v", field, err)
979 }
980 if !tfresource.IsConfigurableField(tfSchema) {
981 t.Fatalf("'%v' is marked mutable-but-unreadable, but cannot have mutable-but-unreadable fields that are not configurable", field)
982 }
983 if tfSchema.ForceNew {
984 t.Fatalf("'%v' is marked mutable-but-unreadable, but cannot have mutable-but-unreadable fields that are marked immutable", field)
985 }
986 if tfresource.IsFieldNestedInList(tfResource, field) {
987 t.Fatalf("'%v' is marked mutable-but-unreadable, but cannot have mutable-but-unreadable fields that are nested in lists", field)
988 }
989 if slice.StringSliceContains(rc.IgnoredFields, field) {
990 t.Fatalf("'%v' is marked mutable-but-unreadable, but cannot have mutable-but-unreadable fields that are marked as ignored fields", field)
991 }
992 if slice.StringSliceContains(rc.Directives, field) {
993 t.Fatalf("'%v' is marked mutable-but-unreadable, but cannot have mutable-but-unreadable fields that are marked as directives", field)
994 }
995 if field == rc.MetadataMapping.Name || field == rc.MetadataMapping.Labels {
996 t.Fatalf("'%v' is marked mutable-but-unreadable, but cannot have mutable-but-unreadable fields that are marked as metadata fields", field)
997 }
998 for _, resourceRef := range rc.ResourceReferences {
999 if field == resourceRef.TFField {
1000 t.Fatalf("'%v' is marked mutable-but-unreadable, but cannot have mutable-but-unreadable fields that are marked as resource references", field)
1001 }
1002 }
1003 }
1004 }
1005
1006 func typeConfigsOf(resourceRef v1alpha1.ReferenceConfig) []v1alpha1.TypeConfig {
1007 if len(resourceRef.Types) == 0 {
1008 return []v1alpha1.TypeConfig{resourceRef.TypeConfig}
1009 }
1010 return resourceRef.Types
1011 }
1012
1013 func TestResourceID(t *testing.T) {
1014 t.Parallel()
1015 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
1016 for _, sm := range serviceMappings {
1017 sm := sm
1018 t.Run(sm.Name, func(t *testing.T) {
1019 t.Parallel()
1020 for _, rc := range sm.Spec.Resources {
1021 rc := rc
1022 t.Run(rc.Kind, func(t *testing.T) {
1023 t.Parallel()
1024 if rc.ResourceID.TargetField == "" {
1025
1026
1027 return
1028 }
1029
1030
1031
1032 if rc.IDTemplate == "" {
1033 testServerGeneratedResourceID(t, rc)
1034 return
1035 }
1036
1037
1038
1039
1040
1041
1042
1043
1044 if strings.Contains(rc.IDTemplate,
1045 fmt.Sprintf("{{%v}}", rc.MetadataMapping.Name)) {
1046 testUserSpecifiedResourceID(t, rc)
1047 } else if strings.Contains(rc.IDTemplate,
1048 fmt.Sprintf("{{%v}}", rc.ServerGeneratedIDField)) {
1049 testServerGeneratedResourceID(t, rc)
1050 } else {
1051 t.Fatalf("resourceID in ResourceConfig %s shouldn't "+
1052 "be supported if the resource has neither a "+
1053 "user-specified ID nor a server-generated ID",
1054 rc.Name)
1055 }
1056 })
1057 }
1058 })
1059 }
1060 }
1061
1062 func testUserSpecifiedResourceID(t *testing.T, rc v1alpha1.ResourceConfig) {
1063 if rc.ResourceID.TargetField != rc.MetadataMapping.Name {
1064 t.Fatalf("targetField of user-specified resourceID in "+
1065 "ResourceConfig %s is different from value of "+
1066 "metadataMapping.name", rc.Name)
1067 }
1068 if rc.ResourceID.ValueTemplate != rc.MetadataMapping.NameValueTemplate {
1069 t.Fatalf("valueTemplate of user-specified resourceID in "+
1070 "ResourceConfig %s is different from value of "+
1071 "metadataMapping.nameValueTemplate", rc.Name)
1072 }
1073 }
1074
1075 func testServerGeneratedResourceID(t *testing.T, rc v1alpha1.ResourceConfig) {
1076 if rc.ResourceID.TargetField != rc.ServerGeneratedIDField {
1077 t.Fatalf("targetField of server-generated resourceID in "+
1078 "ResourceConfig %s is different from value of "+
1079 "serverGeneratedIDField", rc.Name)
1080 }
1081 }
1082
1083
1084
1085 func TestUnreadableResourcesShouldHaveZeroReconciliationInterval(t *testing.T) {
1086 t.Parallel()
1087 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
1088 for _, sm := range serviceMappings {
1089 sm := sm
1090 t.Run(sm.Name, func(t *testing.T) {
1091 t.Parallel()
1092 for _, rc := range sm.Spec.Resources {
1093 rc := rc
1094 t.Run(rc.Kind, func(t *testing.T) {
1095 t.Parallel()
1096 if rc.Unreadable == nil || *rc.Unreadable == false {
1097 return
1098 }
1099 if rc.ReconciliationIntervalInSeconds == nil || *rc.ReconciliationIntervalInSeconds != 0 {
1100 t.Fatalf("resource config '%v' is marked 'Unreadable', but field 'ReconciliationIntervalInSeconds' is not set to 0", rc.Name)
1101 }
1102 })
1103 }
1104 })
1105 }
1106 }
1107
1108
1109
1110 func TestReconciliationIntervalConsistency(t *testing.T) {
1111 smLoader := testservicemappingloader.New(t)
1112 for _, gvk := range supportedgvks.BasedOnAllServiceMappings(smLoader) {
1113 rcs, err := smLoader.GetResourceConfigs(gvk)
1114 if err != nil || len(rcs) < 2 {
1115
1116 continue
1117 }
1118 var ri *uint32
1119 for _, rc := range rcs {
1120 if rc.ReconciliationIntervalInSeconds == nil {
1121
1122 continue
1123 }
1124 if ri == nil {
1125
1126 ri = new(uint32)
1127 *ri = *rc.ReconciliationIntervalInSeconds
1128 continue
1129 }
1130 if *ri != *rc.ReconciliationIntervalInSeconds {
1131 t.Errorf("the configured reconciliation intervals "+
1132 "should have the same value for all resource configs "+
1133 "mapped to GVK %v", gvk)
1134 }
1135 }
1136 }
1137 }
1138
1139 func isAutogenAlphaResource(sm *v1alpha1.ServiceMapping, rc *v1alpha1.ResourceConfig) bool {
1140 if sm.GetVersionFor(rc) == k8s.KCCAPIVersionV1Alpha1 && rc.AutoGenerated {
1141 return true
1142 }
1143 return false
1144 }
1145
1146 func TestDCLBasedResourceIsTrueIFFIsDCLBasedResource(t *testing.T) {
1147 t.Parallel()
1148 serviceMappings := testservicemappingloader.New(t).GetServiceMappings()
1149 referencedDCLResources := make([]k8sschema.GroupVersionKind, 0)
1150 referencedTFResources := make([]k8sschema.GroupVersionKind, 0)
1151 for _, sm := range serviceMappings {
1152 for _, r := range sm.Spec.Resources {
1153 if r.AutoGenerated {
1154 continue
1155 }
1156 for _, rr := range r.ResourceReferences {
1157 if rr.DCLBasedResource {
1158 referencedDCLResources = append(referencedDCLResources, rr.GVK)
1159 } else {
1160 referencedTFResources = append(referencedTFResources, rr.GVK)
1161 }
1162 }
1163 }
1164 }
1165 smLoader := dclmetadata.New()
1166 for _, gvk := range referencedDCLResources {
1167 r, found := smLoader.GetResourceWithGVK(gvk)
1168 if !found || !r.Releasable {
1169 t.Errorf("%v is listed in servicemappings as a resource reference with "+
1170 "`DCLBasedResource: true`, but it is not a DCL-based resource", gvk)
1171 }
1172 }
1173 for _, gvk := range referencedTFResources {
1174 r, found := smLoader.GetResourceWithGVK(gvk)
1175 if found && r.Releasable {
1176 t.Errorf("%v is listed in servicemappings as a resource reference with "+
1177 "`DCLBasedResource: false`, but it is a DCL-based resource", gvk)
1178 }
1179 }
1180 }
1181
View as plain text