...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/config/tests/servicemapping/servicemapping_test.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/config/tests/servicemapping

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    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  			// CloudBuildTrigger was added before this test was put in so for historical
    63  			// reasons we ignore CloudBuild service mappings (until we make a fix).
    64  			// CloudIdentity is a resource that we decided should keep the "Cloud"
    65  			// prefix to prevent confusion from a service just being named "Identity".
    66  			// CloudIOT, CloudTasks, CloudAsset, CloudIDS and CloudFunctions2
    67  			// are service names coming from TF types via auto-generation. KCC
    68  			// doesn't manually remove 'Cloud' from service names during
    69  			// auto-generation.
    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  			// TODO (b/221463073): disable ComputeBackendService until
   104  			// ComputeRegionBackendService gets IAM support.
   105  			if rc.Kind == "ComputeBackendService" {
   106  				continue
   107  			}
   108  			t.Run(rc.Kind, func(t *testing.T) {
   109  				t.Parallel()
   110  				// IAMConfig is not supported for the auto-generated v1alpha1 CRDs.
   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  				// Add all the fields that are considered reference fields
   222  				// to this array
   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  				// Check the fields to ensure they're in the schema
   241  				for _, f := range fields {
   242  					if f == "" {
   243  						continue
   244  					}
   245  					if !tfresource.TFResourceHasField(tfResource, f) {
   246  						// TODO(b/278948939): Remove once the unknown fields are cleaned up in google_apigee_addons_config.
   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  				// If no TargetField is specified, then either this is a
   286  				// complex reference or the TargetField is the referenced
   287  				// resource's metadata.name (in which case there is no need to
   288  				// check the referenced resource's Terraform schema)
   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  	// this is needed because there is a resource reference to a Kind that doesn't exist yet (BillingAccount)
   388  	// when a billing service mapping is added, delete to the "end code block" comment
   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  	// end code block
   398  
   399  	// This is needed because there is a resource reference to a Kind that doesn't exist yet (Organization)
   400  	// when an organization resource is added, delete to the "end code block" comment
   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  	// end code block
   415  
   416  	// This list of ignored GVK is to allow certain resources to have
   417  	// external-only resource references (DCL-based resources or unsupported
   418  	// resources).
   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  	// TODO(b/193177782): Delete this if-block once all resources support
   487  	// hierarchical references.
   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  			// Reference is not at the root-level of the spec.
   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  					// The resource contains neither a user-specified ID nor a
   614  					// server-generated ID.
   615  					if rc.MetadataMapping.Name == "" && rc.ServerGeneratedIDField == "" {
   616  						return
   617  					}
   618  
   619  					// The idTemplate should contain either the user-specified
   620  					// ID or the server-generated ID.
   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  	// TODO: Implement IAMPolicy support for:
   679  	//  - BigQueryDataset (b/167223329)
   680  	//  - ComputeDisk (b/168609794)
   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  		// if this exceptional case is valid then manually add an entry in formatAssociatedTerraformIAMPolicyResourceName(...) to return the correct value
   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  		// if this exceptional case is valid then manually add an entry in formatAssociatedTerraformIAMPolicyMemberResourceName(...) to return the correct value
   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  			// if this exceptional case is valid then manually add an entry in formatAssociatedTerraformIAMAuditConfigResourceName(...) to return the correct value
   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  						// Resource ID field is not supported so no test is
  1026  						// needed.
  1027  						return
  1028  					}
  1029  
  1030  					// Empty idTemplate field means that the resource uses
  1031  					// the server-generated ID as the ID template.
  1032  					if rc.IDTemplate == "" {
  1033  						testServerGeneratedResourceID(t, rc)
  1034  						return
  1035  					}
  1036  
  1037  					// If the idTemplate contains the TF field that
  1038  					// metadata.name maps to, then the resource has a
  1039  					// user-specified resource ID.
  1040  					// If the idTemplate contains the TF field that
  1041  					// status.[serverGeneratedIDField] maps to, then the
  1042  					// resource has a server-generated resource ID.
  1043  					// Otherwise, resourceID should not be supported.
  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  // TestUnreadableResourcesShouldHaveZeroReconciliationInterval ensures that all resources that are
  1084  // unreadable have set ReconciliationIntervalInSeconds to 0.
  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  // TestReconciliationIntervalConsistency makes sure the configured reconciliation intervals have
  1109  // the same value for all resource configs mapped to the same GVK.
  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  			// only check for GVKs mapped to multiple resource configs
  1116  			continue
  1117  		}
  1118  		var ri *uint32
  1119  		for _, rc := range rcs {
  1120  			if rc.ReconciliationIntervalInSeconds == nil {
  1121  				// ReconciliationIntervalInSeconds not configured
  1122  				continue
  1123  			}
  1124  			if ri == nil {
  1125  				// first time seeing ReconciliationIntervalInSeconds for this GVK
  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