...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/krmtotf/krmtotf_test.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/krmtotf

     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 krmtotf_test
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  
    21  	corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
    22  	. "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/krmtotf"
    23  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test"
    24  	testcontroller "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/controller"
    25  	testmain "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/main"
    26  	testvariable "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/resourcefixture/variable"
    27  	testservicemappingloader "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/servicemappingloader"
    28  
    29  	"github.com/google/go-cmp/cmp"
    30  	tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    31  	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
    32  	corev1 "k8s.io/api/core/v1"
    33  	v1 "k8s.io/api/core/v1"
    34  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    35  	k8sschema "k8s.io/apimachinery/pkg/runtime/schema"
    36  	"k8s.io/apimachinery/pkg/types"
    37  	"sigs.k8s.io/controller-runtime/pkg/client"
    38  	"sigs.k8s.io/controller-runtime/pkg/manager"
    39  )
    40  
    41  var mgr manager.Manager
    42  
    43  func resourceSkeleton() *Resource {
    44  	return &Resource{
    45  		TFInfo: &terraform.InstanceInfo{},
    46  		TFResource: &tfschema.Resource{
    47  			Schema: map[string]*tfschema.Schema{
    48  				"int_key": {
    49  					Type:     tfschema.TypeInt,
    50  					Optional: true,
    51  				},
    52  				"float_key": {
    53  					Type:     tfschema.TypeFloat,
    54  					Optional: true,
    55  				},
    56  				"string_key": {
    57  					Type:     tfschema.TypeString,
    58  					Optional: true,
    59  				},
    60  				"bool_key": {
    61  					Type:     tfschema.TypeBool,
    62  					Optional: true,
    63  				},
    64  				"nonconfigurable_string_key": {
    65  					Type: tfschema.TypeString,
    66  				},
    67  				"directive_key": {
    68  					Type: tfschema.TypeBool,
    69  				},
    70  				"nested_object_key": {
    71  					Type:     tfschema.TypeList,
    72  					MaxItems: 1,
    73  					Optional: true,
    74  					Elem: &tfschema.Resource{
    75  						Schema: map[string]*tfschema.Schema{
    76  							"nested_float_key": {
    77  								Type:     tfschema.TypeFloat,
    78  								Optional: true,
    79  							},
    80  							"nested_simple_reference_key": {
    81  								Type:     tfschema.TypeString,
    82  								Optional: true,
    83  							},
    84  							"nested_complex_reference_key": {
    85  								Type:     tfschema.TypeString,
    86  								Optional: true,
    87  							},
    88  							"nested_sensitive_field_key": {
    89  								Type:      tfschema.TypeString,
    90  								Optional:  true,
    91  								Sensitive: true,
    92  							},
    93  							"nested_map_key": {
    94  								Type:     tfschema.TypeMap,
    95  								Optional: true,
    96  							},
    97  							"nested_nonconfigurable_key": {
    98  								Type: tfschema.TypeString,
    99  							},
   100  						},
   101  					},
   102  				},
   103  				"list_of_objects_key": {
   104  					Type:     tfschema.TypeList,
   105  					Optional: true,
   106  					Elem: &tfschema.Resource{
   107  						Schema: map[string]*tfschema.Schema{
   108  							"nested_int_key": {
   109  								Type:     tfschema.TypeInt,
   110  								Optional: true,
   111  							},
   112  							"reference_nested_in_list_of_objects_key": {
   113  								Type:     tfschema.TypeString,
   114  								Optional: true,
   115  							},
   116  							"sensitive_field_nested_in_list_of_objects_key": {
   117  								Type:      tfschema.TypeString,
   118  								Optional:  true,
   119  								Sensitive: true,
   120  							},
   121  							"nonconfigurable_field_nested_in_list_of_objects_key": {
   122  								Type: tfschema.TypeString,
   123  							},
   124  						},
   125  					},
   126  				},
   127  				"list_of_primitives_key": {
   128  					Type:     tfschema.TypeList,
   129  					Optional: true,
   130  					Elem: &tfschema.Schema{
   131  						Type: tfschema.TypeString,
   132  					},
   133  				},
   134  				"reference_key": {
   135  					Type:     tfschema.TypeString,
   136  					Optional: true,
   137  				},
   138  				"list_of_references_key": {
   139  					Type:     tfschema.TypeList,
   140  					Optional: true,
   141  					Elem: &tfschema.Schema{
   142  						Type: tfschema.TypeString,
   143  					},
   144  				},
   145  				"complex_set_of_references_key": {
   146  					Type:     tfschema.TypeSet,
   147  					Optional: true,
   148  					Elem: &tfschema.Schema{
   149  						Type: tfschema.TypeString,
   150  					},
   151  				},
   152  				"map_key": {
   153  					Type:     tfschema.TypeMap,
   154  					Optional: true,
   155  				},
   156  				"unused_key": {
   157  					Type:     tfschema.TypeSet,
   158  					Optional: true,
   159  				},
   160  				"primitive_set_key": {
   161  					Type:     tfschema.TypeSet,
   162  					Optional: true,
   163  					Elem: &tfschema.Schema{
   164  						Type: tfschema.TypeString,
   165  					},
   166  				},
   167  				"object_set_key": {
   168  					Type:     tfschema.TypeSet,
   169  					Optional: true,
   170  					Elem: &tfschema.Resource{
   171  						Schema: map[string]*tfschema.Schema{
   172  							"index": {
   173  								Type:     tfschema.TypeInt,
   174  								Optional: true,
   175  							},
   176  							"nested_bool_key": {
   177  								Type:     tfschema.TypeBool,
   178  								Optional: true,
   179  							},
   180  						},
   181  					},
   182  					Set: func(val interface{}) int {
   183  						m := val.(map[string]interface{})
   184  						return m["index"].(int)
   185  					},
   186  				},
   187  				"parent_key": {
   188  					Type:     tfschema.TypeString,
   189  					Optional: true,
   190  				},
   191  				"sensitive_field_key": {
   192  					Type:      tfschema.TypeString,
   193  					Optional:  true,
   194  					Sensitive: true,
   195  				},
   196  				"complex_reference_key": {
   197  					Type:     tfschema.TypeString,
   198  					Optional: true,
   199  				},
   200  			},
   201  		},
   202  		ResourceConfig: corekccv1alpha1.ResourceConfig{
   203  			Name: "google_foo",
   204  			Kind: "Foo",
   205  			ResourceReferences: []corekccv1alpha1.ReferenceConfig{
   206  				{
   207  					TFField: "nested_object_key.nested_simple_reference_key",
   208  					TypeConfig: corekccv1alpha1.TypeConfig{
   209  						Key: "nestedRef",
   210  						GVK: k8sschema.GroupVersionKind{
   211  							Group:   "test1.cnrm.cloud.google.com",
   212  							Version: "v1alpha1",
   213  							Kind:    "Test1Bar",
   214  						},
   215  					},
   216  				},
   217  				{
   218  					TFField: "nested_object_key.nested_complex_reference_key",
   219  					Types: []corekccv1alpha1.TypeConfig{
   220  						{
   221  							Key: "barRef",
   222  							GVK: k8sschema.GroupVersionKind{
   223  								Group:   "test1.cnrm.cloud.google.com",
   224  								Version: "v1alpha1",
   225  								Kind:    "Test1Bar",
   226  							},
   227  						},
   228  						{
   229  							Key:            "value",
   230  							JSONSchemaType: "string",
   231  						},
   232  					},
   233  				},
   234  				{
   235  					TFField: "list_of_objects_key.reference_nested_in_list_of_objects_key",
   236  					TypeConfig: corekccv1alpha1.TypeConfig{
   237  						Key: "nestedInListOfObjectsRef",
   238  						GVK: k8sschema.GroupVersionKind{
   239  							Group:   "test1.cnrm.cloud.google.com",
   240  							Version: "v1alpha1",
   241  							Kind:    "Test1Bar",
   242  						},
   243  					},
   244  				},
   245  				{
   246  					TFField: "reference_key",
   247  					TypeConfig: corekccv1alpha1.TypeConfig{
   248  						Key: "referenceRef",
   249  						GVK: k8sschema.GroupVersionKind{
   250  							Group:   "test1.cnrm.cloud.google.com",
   251  							Version: "v1alpha1",
   252  							Kind:    "Test1Bar",
   253  						},
   254  					},
   255  				},
   256  				{
   257  					TFField: "list_of_references_key",
   258  					TypeConfig: corekccv1alpha1.TypeConfig{
   259  						GVK: k8sschema.GroupVersionKind{
   260  							Group:   "test1.cnrm.cloud.google.com",
   261  							Version: "v1alpha1",
   262  							Kind:    "Test1Bar",
   263  						},
   264  					},
   265  				},
   266  				{
   267  					TFField: "complex_set_of_references_key",
   268  					Types: []corekccv1alpha1.TypeConfig{
   269  						{
   270  							Key: "subKeyRef",
   271  							GVK: k8sschema.GroupVersionKind{
   272  								Group:   "test1.cnrm.cloud.google.com",
   273  								Version: "v1alpha1",
   274  								Kind:    "Test1Bar",
   275  							},
   276  						},
   277  					},
   278  				},
   279  				{
   280  					TFField: "complex_reference_key",
   281  					Types: []corekccv1alpha1.TypeConfig{
   282  						{
   283  							Key:            "value",
   284  							JSONSchemaType: "string",
   285  						},
   286  						{
   287  							Key: "referenceRef",
   288  							GVK: k8sschema.GroupVersionKind{
   289  								Group:   "test1.cnrm.cloud.google.com",
   290  								Version: "v1alpha1",
   291  								Kind:    "Test1Bar",
   292  							},
   293  						},
   294  					},
   295  				},
   296  			},
   297  			Directives: []string{
   298  				"directive_key",
   299  			},
   300  		},
   301  	}
   302  }
   303  
   304  func TestKRMResourceSpecsToTFConfig(t *testing.T) {
   305  	tests := []struct {
   306  		name                  string
   307  		prevSpec              map[string]interface{}
   308  		hasResourceReferences bool
   309  		hasSecretReferences   bool
   310  		expected              map[string]interface{}
   311  	}{
   312  		{
   313  			name: "keys as constant",
   314  			prevSpec: map[string]interface{}{
   315  				"intKey":    "1",
   316  				"floatKey":  "0.5",
   317  				"stringKey": "StringVal",
   318  				"boolKey":   "true",
   319  			},
   320  			expected: map[string]interface{}{
   321  				"int_key":    1,
   322  				"float_key":  0.5,
   323  				"string_key": "StringVal",
   324  				"bool_key":   true,
   325  			},
   326  		},
   327  		{
   328  			name: "list of primitives key",
   329  			prevSpec: map[string]interface{}{
   330  				"listOfPrimitivesKey": []interface{}{
   331  					"myString1",
   332  					"myString2",
   333  				},
   334  			},
   335  			expected: map[string]interface{}{
   336  				"list_of_primitives_key": []interface{}{
   337  					"myString1",
   338  					"myString2",
   339  				},
   340  			},
   341  		},
   342  		{
   343  			name: "map key",
   344  			prevSpec: map[string]interface{}{
   345  				"mapKey": map[string]interface{}{
   346  					"myMapKey1": "MyMapValue1",
   347  					"myMapKey2": "MyMapValue2",
   348  				},
   349  			},
   350  			expected: map[string]interface{}{
   351  				"map_key": map[string]interface{}{
   352  					"myMapKey1": "MyMapValue1",
   353  					"myMapKey2": "MyMapValue2",
   354  				},
   355  			},
   356  		},
   357  		{
   358  			name:                  "nested objects key",
   359  			hasResourceReferences: true,
   360  			prevSpec: map[string]interface{}{
   361  				"nestedObjectKey": map[string]interface{}{
   362  					"nestedFloatKey": "0.5",
   363  					"nestedRef": map[string]interface{}{
   364  						"name": "my-ref1",
   365  					},
   366  					"nestedMapKey": map[string]interface{}{
   367  						"name": "val",
   368  					},
   369  					"nestedComplexReferenceKey": map[string]interface{}{
   370  						"value": "foobar",
   371  					},
   372  				},
   373  			},
   374  			expected: map[string]interface{}{
   375  				"nested_object_key": []interface{}{
   376  					map[string]interface{}{
   377  						"nested_float_key":             0.5,
   378  						"nested_simple_reference_key":  "my-ref1",
   379  						"nested_complex_reference_key": "foobar",
   380  						"nested_map_key": map[string]interface{}{
   381  							"name": "val",
   382  						},
   383  					},
   384  				},
   385  			},
   386  		},
   387  		{
   388  			name:                  "list of objects key",
   389  			hasResourceReferences: true,
   390  			prevSpec: map[string]interface{}{
   391  				"listOfObjectsKey": []interface{}{
   392  					map[string]interface{}{
   393  						"nestedIntKey": "2",
   394  						"nestedInListOfObjectsRef": map[string]interface{}{
   395  							"name": "my-ref1",
   396  						},
   397  					},
   398  					map[string]interface{}{
   399  						"nestedIntKey": "3",
   400  						"nestedInListOfObjectsRef": map[string]interface{}{
   401  							"name": "my-ref2",
   402  						},
   403  					},
   404  				},
   405  			},
   406  			expected: map[string]interface{}{
   407  				"list_of_objects_key": []interface{}{
   408  					map[string]interface{}{
   409  						"nested_int_key": 2,
   410  						"reference_nested_in_list_of_objects_key": "my-ref1",
   411  					},
   412  					map[string]interface{}{
   413  						"nested_int_key": 3,
   414  						"reference_nested_in_list_of_objects_key": "my-ref2",
   415  					},
   416  				},
   417  			},
   418  		},
   419  		{
   420  			name:                  "simple reference key",
   421  			hasResourceReferences: true,
   422  			prevSpec: map[string]interface{}{
   423  				"referenceRef": map[string]interface{}{
   424  					"name": "my-ref1",
   425  				},
   426  			},
   427  			expected: map[string]interface{}{
   428  				"reference_key": "my-ref1",
   429  			},
   430  		},
   431  		{
   432  			name: "sensitive field with simple value",
   433  			prevSpec: map[string]interface{}{
   434  				"sensitiveFieldKey": map[string]interface{}{
   435  					"value": "val",
   436  				},
   437  			},
   438  			expected: map[string]interface{}{
   439  				"sensitive_field_key": "val",
   440  			},
   441  		},
   442  		{
   443  			name:                "sensitive field with value from secret ref",
   444  			hasSecretReferences: true,
   445  			prevSpec: map[string]interface{}{
   446  				"sensitiveFieldKey": map[string]interface{}{
   447  					"valueFrom": map[string]interface{}{
   448  						"secretKeyRef": map[string]interface{}{
   449  							"name": "secret1",
   450  							"key":  "secret-key1",
   451  						},
   452  					},
   453  				},
   454  			},
   455  			expected: map[string]interface{}{
   456  				"sensitive_field_key": "secret-val1",
   457  			},
   458  		},
   459  		{
   460  			name: "nested sensitive field with simple value",
   461  			prevSpec: map[string]interface{}{
   462  				"nestedObjectKey": map[string]interface{}{
   463  					"nestedSensitiveFieldKey": map[string]interface{}{
   464  						"value": "val",
   465  					},
   466  				},
   467  			},
   468  			expected: map[string]interface{}{
   469  				"nested_object_key": []interface{}{
   470  					map[string]interface{}{
   471  						"nested_sensitive_field_key": "val",
   472  					},
   473  				},
   474  			},
   475  		},
   476  		{
   477  			name:                "nested sensitive field with value from secret ref",
   478  			hasSecretReferences: true,
   479  			prevSpec: map[string]interface{}{
   480  				"nestedObjectKey": map[string]interface{}{
   481  					"nestedSensitiveFieldKey": map[string]interface{}{
   482  						"valueFrom": map[string]interface{}{
   483  							"secretKeyRef": map[string]interface{}{
   484  								"name": "secret1",
   485  								"key":  "secret-key1",
   486  							},
   487  						},
   488  					},
   489  				},
   490  			},
   491  			expected: map[string]interface{}{
   492  				"nested_object_key": []interface{}{
   493  					map[string]interface{}{
   494  						"nested_sensitive_field_key": "secret-val1",
   495  					},
   496  				},
   497  			},
   498  		},
   499  		{
   500  			name: "sensitive field nested in list of objects with simple values",
   501  			prevSpec: map[string]interface{}{
   502  				"listOfObjectsKey": []interface{}{
   503  					map[string]interface{}{
   504  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
   505  							"value": "val1",
   506  						},
   507  					},
   508  					map[string]interface{}{
   509  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
   510  							"value": "val2",
   511  						},
   512  					},
   513  				},
   514  			},
   515  			expected: map[string]interface{}{
   516  				"list_of_objects_key": []interface{}{
   517  					map[string]interface{}{
   518  						"sensitive_field_nested_in_list_of_objects_key": "val1",
   519  					},
   520  					map[string]interface{}{
   521  						"sensitive_field_nested_in_list_of_objects_key": "val2",
   522  					},
   523  				},
   524  			},
   525  		},
   526  		{
   527  			name:                "sensitive field nested in list of objects with values from secret refs",
   528  			hasSecretReferences: true,
   529  			prevSpec: map[string]interface{}{
   530  				"listOfObjectsKey": []interface{}{
   531  					map[string]interface{}{
   532  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
   533  							"valueFrom": map[string]interface{}{
   534  								"secretKeyRef": map[string]interface{}{
   535  									"name": "secret1",
   536  									"key":  "secret-key1",
   537  								},
   538  							},
   539  						},
   540  					},
   541  					map[string]interface{}{
   542  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
   543  							"valueFrom": map[string]interface{}{
   544  								"secretKeyRef": map[string]interface{}{
   545  									"name": "secret2",
   546  									"key":  "secret-key2",
   547  								},
   548  							},
   549  						},
   550  					},
   551  				},
   552  			},
   553  			expected: map[string]interface{}{
   554  				"list_of_objects_key": []interface{}{
   555  					map[string]interface{}{
   556  						"sensitive_field_nested_in_list_of_objects_key": "secret-val1",
   557  					},
   558  					map[string]interface{}{
   559  						"sensitive_field_nested_in_list_of_objects_key": "secret-val2",
   560  					},
   561  				},
   562  			},
   563  		},
   564  		{
   565  			name: "drop nonconfigurable fields",
   566  			prevSpec: map[string]interface{}{
   567  				"nonconfigurable_string_key": "true",
   568  			},
   569  			expected: map[string]interface{}{},
   570  		},
   571  		{
   572  			name: "drop nonconfigurable nested fields",
   573  			prevSpec: map[string]interface{}{
   574  				"nested_object_key": []interface{}{
   575  					map[string]interface{}{
   576  						"nested_nonconfigurable_key": "value",
   577  					},
   578  				},
   579  			},
   580  			expected: map[string]interface{}{
   581  				"nested_object_key": []interface{}{
   582  					map[string]interface{}{},
   583  				},
   584  			},
   585  		},
   586  		{
   587  			name: "drop nonconfigurable field nested in list of objects",
   588  			prevSpec: map[string]interface{}{
   589  				"list_of_objects_key": []interface{}{
   590  					map[string]interface{}{
   591  						"nonconfigurable_field_nested_in_list_of_objects_key": "value",
   592  					},
   593  				},
   594  			},
   595  			expected: map[string]interface{}{
   596  				"list_of_objects_key": []interface{}{
   597  					map[string]interface{}{},
   598  				},
   599  			},
   600  		},
   601  	}
   602  	smLoader := testservicemappingloader.NewForUnitTest(t)
   603  	for _, tc := range tests {
   604  		tc := tc
   605  		t.Run(tc.name, func(t *testing.T) {
   606  			t.Parallel()
   607  			testId := testvariable.NewUniqueId()
   608  			c := mgr.GetClient()
   609  			testcontroller.EnsureNamespaceExistsT(t, c, testId)
   610  			r := resourceSkeleton()
   611  			r.SetNamespace(testId)
   612  			r.Spec = tc.prevSpec
   613  			if tc.hasResourceReferences {
   614  				references := []*unstructured.Unstructured{
   615  					test.NewBarUnstructured("my-ref1", testId, corev1.ConditionTrue),
   616  					test.NewBarUnstructured("my-ref2", testId, corev1.ConditionTrue),
   617  				}
   618  				test.EnsureObjectsExist(t, references, c)
   619  			}
   620  			if tc.hasSecretReferences {
   621  				secretsData := []map[string]interface{}{
   622  					{
   623  						"secret-key1": "secret-val1",
   624  					},
   625  					{
   626  						"secret-key2": "secret-val2",
   627  					},
   628  				}
   629  				secrets := []*unstructured.Unstructured{
   630  					test.NewSecretUnstructured("secret1", testId, secretsData[0]),
   631  					test.NewSecretUnstructured("secret2", testId, secretsData[1]),
   632  				}
   633  				test.EnsureObjectsExist(t, secrets, c)
   634  			}
   635  			actual, _, err := KRMResourceToTFResourceConfig(r, c, smLoader)
   636  			if err != nil {
   637  				t.Fatalf("error convert to TF resource config: %v", err)
   638  			}
   639  			if !test.Equals(t, tc.expected, actual.Raw) {
   640  				t.Fatalf("expected: %v, actual %v", tc.expected, actual.Raw)
   641  			}
   642  		})
   643  	}
   644  }
   645  
   646  func TestKRMResourceMetadataToTFConfig(t *testing.T) {
   647  	tests := []struct {
   648  		name           string
   649  		rc             *corekccv1alpha1.ResourceConfig
   650  		annotation     map[string]string
   651  		metadataName   string
   652  		expectedConfig map[string]interface{}
   653  	}{
   654  		{
   655  			name: "directives",
   656  			annotation: map[string]string{
   657  				"cnrm.cloud.google.com/directive-key": "true",
   658  			},
   659  			expectedConfig: map[string]interface{}{
   660  				"directive_key": true,
   661  			},
   662  		},
   663  		{
   664  			name:         "metadata.name",
   665  			metadataName: "my-name",
   666  			expectedConfig: map[string]interface{}{
   667  				"string_key": "my-name",
   668  			},
   669  			rc: &corekccv1alpha1.ResourceConfig{
   670  				MetadataMapping: corekccv1alpha1.MetadataMapping{
   671  					Name: "string_key",
   672  				},
   673  			},
   674  		},
   675  		{
   676  			name:         "metadata.name with value templating",
   677  			metadataName: "my-name",
   678  			expectedConfig: map[string]interface{}{
   679  				"string_key": "resources/my-name",
   680  			},
   681  			rc: &corekccv1alpha1.ResourceConfig{
   682  				MetadataMapping: corekccv1alpha1.MetadataMapping{
   683  					Name:              "string_key",
   684  					NameValueTemplate: "resources/{{value}}",
   685  				},
   686  			},
   687  		},
   688  		{
   689  			// KRMResourceToTFResourceConfig should not error out if the
   690  			// resource doesn't have a metadata.name. This can happen when
   691  			// KRMResourceToTFResourceConfig is called with resource skeletons
   692  			// (e.g. the config-connector CLI calls
   693  			// KRMResourceToTFResourceConfig with Project skeletons that don't
   694  			// have a metadata.name since a Project's status.number can also
   695  			// serve as its identity).
   696  			name:           "empty metadata.name",
   697  			expectedConfig: map[string]interface{}{},
   698  			rc: &corekccv1alpha1.ResourceConfig{
   699  				MetadataMapping: corekccv1alpha1.MetadataMapping{
   700  					Name: "string_key",
   701  				},
   702  			},
   703  		},
   704  	}
   705  	smLoader := testservicemappingloader.NewForUnitTest(t)
   706  	for _, tc := range tests {
   707  		tc := tc
   708  		t.Run(tc.name, func(t *testing.T) {
   709  			t.Parallel()
   710  			testId := testvariable.NewUniqueId()
   711  			c := mgr.GetClient()
   712  			r := resourceSkeleton()
   713  			if tc.rc != nil {
   714  				r.ResourceConfig = *tc.rc
   715  			}
   716  			r.SetName(tc.metadataName)
   717  			r.SetNamespace(testId)
   718  			r.SetAnnotations(tc.annotation)
   719  			actual, _, err := KRMResourceToTFResourceConfig(r, c, smLoader)
   720  			if err != nil {
   721  				t.Fatalf("error convert to TF resource config: %v", err)
   722  			}
   723  			if !test.Equals(t, tc.expectedConfig, actual.Raw) {
   724  				t.Fatalf("expected: %v, actual %v", tc.expectedConfig, actual.Raw)
   725  			}
   726  		})
   727  	}
   728  }
   729  
   730  func TestKRMResourceToTFResourceHierarchicalReferencesAndContainers(t *testing.T) {
   731  	tests := []struct {
   732  		name                 string
   733  		rc                   *corekccv1alpha1.ResourceConfig
   734  		annotations          map[string]string
   735  		spec                 map[string]interface{}
   736  		hasResourceReference bool
   737  		expectedConfig       map[string]interface{}
   738  	}{
   739  		{
   740  			name: "container annotations are mapped to config if hierarchical references are not supported",
   741  			rc: &corekccv1alpha1.ResourceConfig{
   742  				Containers: []corekccv1alpha1.Container{
   743  					{
   744  						Type:    corekccv1alpha1.ContainerTypeProject,
   745  						TFField: "parent_key",
   746  					},
   747  				},
   748  			},
   749  			annotations: map[string]string{
   750  				"cnrm.cloud.google.com/project-id": "project-id-from-annotations",
   751  			},
   752  			expectedConfig: map[string]interface{}{
   753  				"parent_key": "project-id-from-annotations",
   754  			},
   755  		},
   756  		{
   757  			name: "container annotations support value templating",
   758  			rc: &corekccv1alpha1.ResourceConfig{
   759  				Containers: []corekccv1alpha1.Container{
   760  					{
   761  						Type:          corekccv1alpha1.ContainerTypeProject,
   762  						TFField:       "parent_key",
   763  						ValueTemplate: "projects/{{value}}",
   764  					},
   765  				},
   766  			},
   767  			annotations: map[string]string{
   768  				"cnrm.cloud.google.com/project-id": "project-id-from-annotation",
   769  			},
   770  			expectedConfig: map[string]interface{}{
   771  				"parent_key": "projects/project-id-from-annotation",
   772  			},
   773  		},
   774  		{
   775  			name: "hierarchical references are mapped to config over container annotations",
   776  			rc: &corekccv1alpha1.ResourceConfig{
   777  				Containers: []corekccv1alpha1.Container{
   778  					{
   779  						Type:    corekccv1alpha1.ContainerTypeProject,
   780  						TFField: "parent_key",
   781  					},
   782  				},
   783  				HierarchicalReferences: []corekccv1alpha1.HierarchicalReference{
   784  					{
   785  						Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   786  						Key:  "projectRef",
   787  					},
   788  				},
   789  				ResourceReferences: []corekccv1alpha1.ReferenceConfig{
   790  					{
   791  						TFField: "parent_key",
   792  						TypeConfig: corekccv1alpha1.TypeConfig{
   793  							Key: "projectRef",
   794  							GVK: k8sschema.GroupVersionKind{
   795  								Group:   "test1.cnrm.cloud.google.com",
   796  								Version: "v1alpha1",
   797  								Kind:    "Test1Bar",
   798  							},
   799  						},
   800  					},
   801  				},
   802  			},
   803  			annotations: map[string]string{
   804  				"cnrm.cloud.google.com/project-id": "project-id-from-annotations",
   805  			},
   806  			spec: map[string]interface{}{
   807  				"projectRef": map[string]interface{}{
   808  					"name": "my-ref",
   809  				},
   810  			},
   811  			hasResourceReference: true,
   812  			expectedConfig: map[string]interface{}{
   813  				"parent_key": "my-ref",
   814  			},
   815  		},
   816  		{
   817  			name: "external hierarchical references are mapped to config over container annotations",
   818  			rc: &corekccv1alpha1.ResourceConfig{
   819  				Containers: []corekccv1alpha1.Container{
   820  					{
   821  						Type:    corekccv1alpha1.ContainerTypeProject,
   822  						TFField: "parent_key",
   823  					},
   824  				},
   825  				HierarchicalReferences: []corekccv1alpha1.HierarchicalReference{
   826  					{
   827  						Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   828  						Key:  "projectRef",
   829  					},
   830  				},
   831  				ResourceReferences: []corekccv1alpha1.ReferenceConfig{
   832  					{
   833  						TFField: "parent_key",
   834  						TypeConfig: corekccv1alpha1.TypeConfig{
   835  							Key: "projectRef",
   836  							GVK: k8sschema.GroupVersionKind{
   837  								Group:   "test1.cnrm.cloud.google.com",
   838  								Version: "v1alpha1",
   839  								Kind:    "Test1Bar",
   840  							},
   841  						},
   842  					},
   843  				},
   844  			},
   845  			annotations: map[string]string{
   846  				"cnrm.cloud.google.com/project-id": "project-id-from-annotations",
   847  			},
   848  			spec: map[string]interface{}{
   849  				"projectRef": map[string]interface{}{
   850  					"external": "project-id-from-spec",
   851  				},
   852  			},
   853  			expectedConfig: map[string]interface{}{
   854  				"parent_key": "project-id-from-spec",
   855  			},
   856  		},
   857  		{
   858  			name: "hierarchical references are mapped to config for resource that only supports hierarchical references",
   859  			rc: &corekccv1alpha1.ResourceConfig{
   860  				HierarchicalReferences: []corekccv1alpha1.HierarchicalReference{
   861  					{
   862  						Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   863  						Key:  "projectRef",
   864  					},
   865  				},
   866  				ResourceReferences: []corekccv1alpha1.ReferenceConfig{
   867  					{
   868  						TFField: "parent_key",
   869  						TypeConfig: corekccv1alpha1.TypeConfig{
   870  							Key: "projectRef",
   871  							GVK: k8sschema.GroupVersionKind{
   872  								Group:   "test1.cnrm.cloud.google.com",
   873  								Version: "v1alpha1",
   874  								Kind:    "Test1Bar",
   875  							},
   876  						},
   877  					},
   878  				},
   879  			},
   880  			annotations: map[string]string{
   881  				"cnrm.cloud.google.com/project-id": "project-id-from-annotations",
   882  			},
   883  			spec: map[string]interface{}{
   884  				"projectRef": map[string]interface{}{
   885  					"name": "my-ref",
   886  				},
   887  			},
   888  			hasResourceReference: true,
   889  			expectedConfig: map[string]interface{}{
   890  				"parent_key": "my-ref",
   891  			},
   892  		},
   893  		{
   894  			name: "external hierarchical references are mapped to config for resource that only supports hierarchical references",
   895  			rc: &corekccv1alpha1.ResourceConfig{
   896  				HierarchicalReferences: []corekccv1alpha1.HierarchicalReference{
   897  					{
   898  						Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   899  						Key:  "projectRef",
   900  					},
   901  				},
   902  				ResourceReferences: []corekccv1alpha1.ReferenceConfig{
   903  					{
   904  						TFField: "parent_key",
   905  						TypeConfig: corekccv1alpha1.TypeConfig{
   906  							Key: "projectRef",
   907  							GVK: k8sschema.GroupVersionKind{
   908  								Group:   "test1.cnrm.cloud.google.com",
   909  								Version: "v1alpha1",
   910  								Kind:    "Test1Bar",
   911  							},
   912  						},
   913  					},
   914  				},
   915  			},
   916  			annotations: map[string]string{
   917  				"cnrm.cloud.google.com/project-id": "project-id-from-annotations",
   918  			},
   919  			spec: map[string]interface{}{
   920  				"projectRef": map[string]interface{}{
   921  					"external": "project-id-from-spec",
   922  				},
   923  			},
   924  			expectedConfig: map[string]interface{}{
   925  				"parent_key": "project-id-from-spec",
   926  			},
   927  		},
   928  		{
   929  			// KRMResourceToTFResourceConfig should not error out if spec does
   930  			// not contain a hierarchical reference. Even if we can expect the
   931  			// resource to always contain a hierarchical reference if
   932  			// KRMResourceToTFResourceConfig is called from the controller,
   933  			// other components like the config-connector CLI can also call
   934  			// KRMResourceToTFResourceConfig to perform GETs with resource
   935  			// skeletons that may not have a hierarchical reference.
   936  			name: "spec without hierarchical reference does not result in an error",
   937  			rc: &corekccv1alpha1.ResourceConfig{
   938  				HierarchicalReferences: []corekccv1alpha1.HierarchicalReference{
   939  					{
   940  						Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   941  						Key:  "projectRef",
   942  					},
   943  				},
   944  				ResourceReferences: []corekccv1alpha1.ReferenceConfig{
   945  					{
   946  						TFField: "parent_key",
   947  						TypeConfig: corekccv1alpha1.TypeConfig{
   948  							Key: "projectRef",
   949  							GVK: k8sschema.GroupVersionKind{
   950  								Group:   "test1.cnrm.cloud.google.com",
   951  								Version: "v1alpha1",
   952  								Kind:    "Test1Bar",
   953  							},
   954  						},
   955  					},
   956  				},
   957  			},
   958  			spec:           map[string]interface{}{},
   959  			expectedConfig: map[string]interface{}{},
   960  		},
   961  	}
   962  
   963  	smLoader := testservicemappingloader.NewForUnitTest(t)
   964  	for _, tc := range tests {
   965  		tc := tc
   966  		t.Run(tc.name, func(t *testing.T) {
   967  			t.Parallel()
   968  			testId := testvariable.NewUniqueId()
   969  			c := mgr.GetClient()
   970  			testcontroller.EnsureNamespaceExistsT(t, c, testId)
   971  			r := resourceSkeleton()
   972  			if tc.rc != nil {
   973  				r.ResourceConfig = *tc.rc
   974  			}
   975  			r.SetNamespace(testId)
   976  			r.SetAnnotations(tc.annotations)
   977  			r.Spec = tc.spec
   978  			if tc.hasResourceReference {
   979  				references := []*unstructured.Unstructured{
   980  					test.NewBarUnstructured("my-ref", testId, corev1.ConditionTrue),
   981  				}
   982  				test.EnsureObjectsExist(t, references, c)
   983  			}
   984  			actual, _, err := KRMResourceToTFResourceConfig(r, c, smLoader)
   985  			if err != nil {
   986  				t.Fatalf("error converting to TF resource config: %v", err)
   987  			}
   988  			if !test.Equals(t, tc.expectedConfig, actual.Raw) {
   989  				diff := cmp.Diff(tc.expectedConfig, actual.Raw)
   990  				t.Fatalf("actual TF config did not match expected TF config; diff (-want +got):\n%v", diff)
   991  			}
   992  		})
   993  	}
   994  }
   995  
   996  func TestKRMResourceToTFResourceConfigSecretVersions(t *testing.T) {
   997  	tests := []struct {
   998  		name                               string
   999  		spec                               map[string]interface{}
  1000  		referencedSecrets                  []*unstructured.Unstructured
  1001  		secretVersionsShouldContainSecrets []string
  1002  	}{
  1003  		{
  1004  			name: "multiple Secret references",
  1005  			spec: map[string]interface{}{
  1006  				"sensitiveFieldKey": map[string]interface{}{
  1007  					"valueFrom": map[string]interface{}{
  1008  						"secretKeyRef": map[string]interface{}{
  1009  							"name": "secret1",
  1010  							"key":  "secret-key1",
  1011  						},
  1012  					},
  1013  				},
  1014  				"nestedObjectKey": map[string]interface{}{
  1015  					"nestedSensitiveFieldKey": map[string]interface{}{
  1016  						"valueFrom": map[string]interface{}{
  1017  							"secretKeyRef": map[string]interface{}{
  1018  								"name": "secret2",
  1019  								"key":  "secret-key2",
  1020  							},
  1021  						},
  1022  					},
  1023  				},
  1024  				"listOfObjectsKey": []interface{}{
  1025  					map[string]interface{}{
  1026  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
  1027  							"valueFrom": map[string]interface{}{
  1028  								"secretKeyRef": map[string]interface{}{
  1029  									"name": "secret3",
  1030  									"key":  "secret-key3",
  1031  								},
  1032  							},
  1033  						},
  1034  					},
  1035  					map[string]interface{}{
  1036  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
  1037  							"valueFrom": map[string]interface{}{
  1038  								"secretKeyRef": map[string]interface{}{
  1039  									"name": "secret4",
  1040  									"key":  "secret-key4",
  1041  								},
  1042  							},
  1043  						},
  1044  					},
  1045  				},
  1046  			},
  1047  			referencedSecrets: []*unstructured.Unstructured{
  1048  				test.NewSecretUnstructured("secret1", "", map[string]interface{}{"secret-key1": "secret-val1"}),
  1049  				test.NewSecretUnstructured("secret2", "", map[string]interface{}{"secret-key2": "secret-val2"}),
  1050  				test.NewSecretUnstructured("secret3", "", map[string]interface{}{"secret-key3": "secret-val3"}),
  1051  				test.NewSecretUnstructured("secret4", "", map[string]interface{}{"secret-key4": "secret-val4"}),
  1052  			},
  1053  			secretVersionsShouldContainSecrets: []string{
  1054  				"secret1",
  1055  				"secret2",
  1056  				"secret3",
  1057  				"secret4",
  1058  			},
  1059  		},
  1060  		{
  1061  			name: "multiple Secret references, but shared Secret",
  1062  			spec: map[string]interface{}{
  1063  				"sensitiveFieldKey": map[string]interface{}{
  1064  					"valueFrom": map[string]interface{}{
  1065  						"secretKeyRef": map[string]interface{}{
  1066  							"name": "secret1",
  1067  							"key":  "secret-key1",
  1068  						},
  1069  					},
  1070  				},
  1071  				"nestedObjectKey": map[string]interface{}{
  1072  					"nestedSensitiveFieldKey": map[string]interface{}{
  1073  						"valueFrom": map[string]interface{}{
  1074  							"secretKeyRef": map[string]interface{}{
  1075  								"name": "secret1",
  1076  								"key":  "secret-key1",
  1077  							},
  1078  						},
  1079  					},
  1080  				},
  1081  				"listOfObjectsKey": []interface{}{
  1082  					map[string]interface{}{
  1083  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
  1084  							"valueFrom": map[string]interface{}{
  1085  								"secretKeyRef": map[string]interface{}{
  1086  									"name": "secret1",
  1087  									"key":  "secret-key1",
  1088  								},
  1089  							},
  1090  						},
  1091  					},
  1092  					map[string]interface{}{
  1093  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
  1094  							"valueFrom": map[string]interface{}{
  1095  								"secretKeyRef": map[string]interface{}{
  1096  									"name": "secret1",
  1097  									"key":  "secret-key1",
  1098  								},
  1099  							},
  1100  						},
  1101  					},
  1102  				},
  1103  			},
  1104  			referencedSecrets: []*unstructured.Unstructured{
  1105  				test.NewSecretUnstructured("secret1", "", map[string]interface{}{"secret-key1": "secret-val1"}),
  1106  				test.NewSecretUnstructured("secret2", "", map[string]interface{}{"secret-key2": "secret-val2"}),
  1107  				test.NewSecretUnstructured("secret3", "", map[string]interface{}{"secret-key3": "secret-val3"}),
  1108  				test.NewSecretUnstructured("secret4", "", map[string]interface{}{"secret-key4": "secret-val4"}),
  1109  			},
  1110  			secretVersionsShouldContainSecrets: []string{
  1111  				"secret1",
  1112  			},
  1113  		},
  1114  		{
  1115  			name: "no Secret references",
  1116  			spec: map[string]interface{}{
  1117  				"sensitiveFieldKey": map[string]interface{}{
  1118  					"value": "val",
  1119  				},
  1120  				"nestedObjectKey": map[string]interface{}{
  1121  					"nestedSensitiveFieldKey": map[string]interface{}{
  1122  						"value": "val",
  1123  					},
  1124  				},
  1125  				"listOfObjectsKey": []interface{}{
  1126  					map[string]interface{}{
  1127  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
  1128  							"value": "val",
  1129  						},
  1130  					},
  1131  					map[string]interface{}{
  1132  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
  1133  							"value": "val",
  1134  						},
  1135  					},
  1136  				},
  1137  			},
  1138  			referencedSecrets: []*unstructured.Unstructured{
  1139  				test.NewSecretUnstructured("secret1", "", map[string]interface{}{"secret-key1": "secret-val1"}),
  1140  				test.NewSecretUnstructured("secret2", "", map[string]interface{}{"secret-key2": "secret-val2"}),
  1141  				test.NewSecretUnstructured("secret3", "", map[string]interface{}{"secret-key3": "secret-val3"}),
  1142  				test.NewSecretUnstructured("secret4", "", map[string]interface{}{"secret-key4": "secret-val4"}),
  1143  			},
  1144  			secretVersionsShouldContainSecrets: []string{},
  1145  		},
  1146  		{
  1147  			name: "no sensitive fields",
  1148  			spec: map[string]interface{}{
  1149  				"stringKey": "StringVal",
  1150  			},
  1151  			referencedSecrets: []*unstructured.Unstructured{
  1152  				test.NewSecretUnstructured("secret1", "", map[string]interface{}{"secret-key1": "secret-val1"}),
  1153  				test.NewSecretUnstructured("secret2", "", map[string]interface{}{"secret-key2": "secret-val2"}),
  1154  				test.NewSecretUnstructured("secret3", "", map[string]interface{}{"secret-key3": "secret-val3"}),
  1155  				test.NewSecretUnstructured("secret4", "", map[string]interface{}{"secret-key4": "secret-val4"}),
  1156  			},
  1157  			secretVersionsShouldContainSecrets: []string{},
  1158  		},
  1159  	}
  1160  	smLoader := testservicemappingloader.NewForUnitTest(t)
  1161  	for _, tc := range tests {
  1162  		tc := tc
  1163  		t.Run(tc.name, func(t *testing.T) {
  1164  			t.Parallel()
  1165  			testId := testvariable.NewUniqueId()
  1166  			c := mgr.GetClient()
  1167  			testcontroller.EnsureNamespaceExistsT(t, c, testId)
  1168  			r := resourceSkeleton()
  1169  			r.SetNamespace(testId)
  1170  			r.Spec = tc.spec
  1171  			for _, obj := range tc.referencedSecrets {
  1172  				obj.SetNamespace(testId)
  1173  			}
  1174  			test.EnsureObjectsExist(t, tc.referencedSecrets, c)
  1175  			expectedSecretVersions := make(map[string]string)
  1176  			for _, secretName := range tc.secretVersionsShouldContainSecrets {
  1177  				version, err := getResourceVersionOfSecret(secretName, testId, c)
  1178  				if err != nil {
  1179  					t.Fatalf("error determining version of Secret %v: %v", secretName, err)
  1180  				}
  1181  				expectedSecretVersions[secretName] = version
  1182  			}
  1183  			_, secretVersions, err := KRMResourceToTFResourceConfig(r, c, smLoader)
  1184  			if err != nil {
  1185  				t.Fatalf("error converting to TF resource config: %v", err)
  1186  			}
  1187  			if !test.Equals(t, expectedSecretVersions, secretVersions) {
  1188  				t.Fatalf("got: %v, wanted: %v", secretVersions, expectedSecretVersions)
  1189  			}
  1190  		})
  1191  	}
  1192  }
  1193  
  1194  func getResourceVersionOfSecret(name, namespace string, c client.Client) (string, error) {
  1195  	nn := types.NamespacedName{
  1196  		Name:      name,
  1197  		Namespace: namespace,
  1198  	}
  1199  	secret := v1.Secret{}
  1200  	if err := c.Get(context.TODO(), nn, &secret); err != nil {
  1201  		return "", err
  1202  	}
  1203  	return secret.GetResourceVersion(), nil
  1204  }
  1205  
  1206  func TestKRMResourceResourceIDToTFConfig(t *testing.T) {
  1207  	tests := []struct {
  1208  		name           string
  1209  		rc             *corekccv1alpha1.ResourceConfig
  1210  		metadataName   string
  1211  		prevSpec       map[string]interface{}
  1212  		expectedConfig map[string]interface{}
  1213  		hasError       bool
  1214  	}{
  1215  		{
  1216  			name: "nonempty user-specified resource ID",
  1217  			rc: &corekccv1alpha1.ResourceConfig{
  1218  				ResourceID: corekccv1alpha1.ResourceID{
  1219  					TargetField: "string_key",
  1220  				},
  1221  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1222  					Name: "string_key",
  1223  				},
  1224  			},
  1225  			metadataName: "metadata-name",
  1226  			prevSpec: map[string]interface{}{
  1227  				"resourceID": "resource-id",
  1228  			},
  1229  			expectedConfig: map[string]interface{}{
  1230  				"string_key": "resource-id",
  1231  			},
  1232  		},
  1233  		{
  1234  			name: "nonempty server-generated resource ID",
  1235  			rc: &corekccv1alpha1.ResourceConfig{
  1236  				ResourceID: corekccv1alpha1.ResourceID{
  1237  					TargetField: "string_key",
  1238  				},
  1239  				ServerGeneratedIDField: "string_key",
  1240  			},
  1241  			prevSpec: map[string]interface{}{
  1242  				"resourceID": "resource-id",
  1243  			},
  1244  			expectedConfig: map[string]interface{}{},
  1245  		},
  1246  		{
  1247  			name: "resource ID with value template",
  1248  			rc: &corekccv1alpha1.ResourceConfig{
  1249  				ResourceID: corekccv1alpha1.ResourceID{
  1250  					TargetField:   "string_key",
  1251  					ValueTemplate: "values/{{value}}",
  1252  				},
  1253  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1254  					Name:              "string_key",
  1255  					NameValueTemplate: "values/{{value}}",
  1256  				},
  1257  			},
  1258  			metadataName: "metadata-name",
  1259  			prevSpec: map[string]interface{}{
  1260  				"resourceID": "resource-id",
  1261  			},
  1262  			expectedConfig: map[string]interface{}{
  1263  				"string_key": "values/resource-id",
  1264  			},
  1265  		},
  1266  		{
  1267  			name: "empty resource ID",
  1268  			rc: &corekccv1alpha1.ResourceConfig{
  1269  				ResourceID: corekccv1alpha1.ResourceID{
  1270  					TargetField: "string_key",
  1271  				},
  1272  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1273  					Name: "string_key",
  1274  				},
  1275  			},
  1276  			metadataName: "metadata-name",
  1277  			prevSpec: map[string]interface{}{
  1278  				"resourceID": "",
  1279  			},
  1280  			hasError: true,
  1281  		},
  1282  		{
  1283  			name: "unspecified resource ID with non-empty metadata.name",
  1284  			rc: &corekccv1alpha1.ResourceConfig{
  1285  				ResourceID: corekccv1alpha1.ResourceID{
  1286  					TargetField: "string_key",
  1287  				},
  1288  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1289  					Name: "string_key",
  1290  				},
  1291  			},
  1292  			metadataName: "metadata-name",
  1293  			prevSpec:     map[string]interface{}{},
  1294  			expectedConfig: map[string]interface{}{
  1295  				"string_key": "metadata-name",
  1296  			},
  1297  		},
  1298  		{
  1299  			// KRMResourceToTFResourceConfig should not error out if the
  1300  			// resource has neither a metadata.name or spec.resourceID. This
  1301  			// can happen when KRMResourceToTFResourceConfig is called with
  1302  			// resource skeletons (e.g. the config-connector CLI calls
  1303  			// KRMResourceToTFResourceConfig with Project skeletons that don't
  1304  			// have a metadata.name or spec.resourceID since a Project's
  1305  			// status.number can also serve as its identity).
  1306  			name: "unspecified resource ID and unspecified metadata.name",
  1307  			rc: &corekccv1alpha1.ResourceConfig{
  1308  				ResourceID: corekccv1alpha1.ResourceID{
  1309  					TargetField: "string_key",
  1310  				},
  1311  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1312  					Name: "string_key",
  1313  				},
  1314  			},
  1315  			prevSpec:       map[string]interface{}{},
  1316  			expectedConfig: map[string]interface{}{},
  1317  		},
  1318  	}
  1319  	smLoader := testservicemappingloader.NewForUnitTest(t)
  1320  	for _, tc := range tests {
  1321  		tc := tc
  1322  		t.Run(tc.name, func(t *testing.T) {
  1323  			t.Parallel()
  1324  			testId := testvariable.NewUniqueId()
  1325  			c := mgr.GetClient()
  1326  			r := resourceSkeleton()
  1327  			if tc.rc != nil {
  1328  				r.ResourceConfig = *tc.rc
  1329  			}
  1330  			if tc.metadataName != "" {
  1331  				r.SetName(tc.metadataName)
  1332  			}
  1333  			r.SetNamespace(testId)
  1334  			r.Spec = tc.prevSpec
  1335  
  1336  			actual, _, err := KRMResourceToTFResourceConfig(r, c, smLoader)
  1337  			if tc.hasError {
  1338  				if err == nil {
  1339  					t.Fatalf("got nil, want an error")
  1340  				}
  1341  				return
  1342  			} else if err != nil {
  1343  				t.Fatalf("error converting KRM resource to TF "+
  1344  					"resource config: %v", err)
  1345  			}
  1346  
  1347  			if got, want := actual.Raw, tc.expectedConfig; !test.Equals(t, got, want) {
  1348  				t.Fatalf("got: %v, want: %v", got, want)
  1349  			}
  1350  		})
  1351  	}
  1352  }
  1353  
  1354  func TestMain(m *testing.M) {
  1355  	testmain.TestMainForUnitTests(m, &mgr)
  1356  }
  1357  

View as plain text