...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/krmtotf/tftokrm_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  	"reflect"
    19  	"testing"
    20  
    21  	corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
    22  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
    23  	. "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/krmtotf"
    24  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test"
    25  	testk8s "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/k8s"
    26  
    27  	tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    28  	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
    29  	k8sschema "k8s.io/apimachinery/pkg/runtime/schema"
    30  	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
    31  )
    32  
    33  func TestConvertTFObjToKCCObj(t *testing.T) {
    34  	tests := []struct {
    35  		name           string
    36  		rc             *corekccv1alpha1.ResourceConfig
    37  		state          map[string]interface{}
    38  		schemaOverride map[string]*tfschema.Schema
    39  		prevSpec       map[string]interface{}
    40  		managedFields  *fieldpath.Set
    41  		expected       map[string]interface{}
    42  	}{
    43  		{
    44  			name: "defaulted non-zero primitive values are set",
    45  			state: map[string]interface{}{
    46  				"int_key":    float64(1),
    47  				"float_key":  float64(0.5),
    48  				"string_key": "my-string",
    49  				"bool_key":   true,
    50  				"map_key": map[string]interface{}{
    51  					"foo": "bar",
    52  				},
    53  				"list_of_primitives_key": []interface{}{
    54  					"element_1",
    55  					"element_2",
    56  				},
    57  			},
    58  			prevSpec: map[string]interface{}{},
    59  			expected: map[string]interface{}{
    60  				"intKey":    float64(1),
    61  				"floatKey":  float64(0.5),
    62  				"stringKey": "my-string",
    63  				"boolKey":   true,
    64  				"mapKey": map[string]interface{}{
    65  					"foo": "bar",
    66  				},
    67  				"listOfPrimitivesKey": []interface{}{
    68  					"element_1",
    69  					"element_2",
    70  				},
    71  			},
    72  		},
    73  		{
    74  			name: "defaulted zero-value data structures are pruned",
    75  			state: map[string]interface{}{
    76  				"map_key":                map[string]interface{}{},
    77  				"list_of_primitives_key": []interface{}{},
    78  				"nested_object_key":      []interface{}{},
    79  				"list_of_objects_key":    []interface{}{},
    80  			},
    81  			prevSpec: nil,
    82  			expected: nil,
    83  		},
    84  		// handle an edge case where some values are not zero-value by default
    85  		// if these are not read in the resource could be accidentally be re-applied
    86  		// and change state (e.g. false value omitted will be defaulted to true)
    87  		{
    88  			name: "non-string primitives are not pruned if differ from default value",
    89  			state: map[string]interface{}{
    90  				"int_key":    float64(0),
    91  				"float_key":  float64(0.0),
    92  				"string_key": "",
    93  				"bool_key":   false,
    94  			},
    95  			prevSpec: nil,
    96  			expected: map[string]interface{}{
    97  				"intKey":   float64(0),
    98  				"floatKey": float64(0.0),
    99  				"boolKey":  false,
   100  			},
   101  			schemaOverride: map[string]*tfschema.Schema{
   102  				"int_key": {
   103  					Type:     tfschema.TypeInt,
   104  					Optional: true,
   105  					Default:  1,
   106  				},
   107  				"float_key": {
   108  					Type:     tfschema.TypeFloat,
   109  					Optional: true,
   110  					Default:  1,
   111  				},
   112  				"string_key": {
   113  					Type:     tfschema.TypeString,
   114  					Optional: true,
   115  					Default:  "foo",
   116  				},
   117  				"bool_key": {
   118  					Type:     tfschema.TypeBool,
   119  					Optional: true,
   120  					Default:  true,
   121  				},
   122  			},
   123  		},
   124  		{
   125  			name: "lists of objects are set",
   126  			state: map[string]interface{}{
   127  				"list_of_objects_key": []interface{}{
   128  					map[string]interface{}{
   129  						"nested_int_key": float64(1),
   130  					},
   131  				},
   132  			},
   133  			prevSpec: map[string]interface{}{},
   134  			expected: map[string]interface{}{
   135  				"listOfObjectsKey": []interface{}{
   136  					map[string]interface{}{
   137  						"nestedIntKey": float64(1),
   138  					},
   139  				},
   140  			},
   141  		},
   142  		{
   143  			name: "nested objects are converted to maps and set",
   144  			state: map[string]interface{}{
   145  				"nested_object_key": []interface{}{
   146  					map[string]interface{}{
   147  						"nested_float_key": float64(0.5),
   148  					},
   149  				},
   150  			},
   151  			prevSpec: map[string]interface{}{},
   152  			expected: map[string]interface{}{
   153  				"nestedObjectKey": map[string]interface{}{
   154  					"nestedFloatKey": float64(0.5),
   155  				},
   156  			},
   157  		},
   158  		{
   159  			name: "individual resource references are preserved",
   160  			state: map[string]interface{}{
   161  				"reference_key": "ref-val",
   162  			},
   163  			prevSpec: map[string]interface{}{
   164  				"referenceRef": map[string]interface{}{
   165  					"name": "my-reference",
   166  				},
   167  			},
   168  			expected: map[string]interface{}{
   169  				"referenceRef": map[string]interface{}{
   170  					"name": "my-reference",
   171  				},
   172  			},
   173  		},
   174  		{
   175  			name: "lists of resource references are preserved",
   176  			state: map[string]interface{}{
   177  				"list_of_references_key": []interface{}{
   178  					"ref1",
   179  					"ref2",
   180  				},
   181  			},
   182  			prevSpec: map[string]interface{}{
   183  				"listOfReferencesKey": []interface{}{
   184  					map[string]interface{}{
   185  						"name": "my-reference1",
   186  					},
   187  					map[string]interface{}{
   188  						"name": "my-reference2",
   189  					},
   190  				},
   191  			},
   192  			expected: map[string]interface{}{
   193  				"listOfReferencesKey": []interface{}{
   194  					map[string]interface{}{
   195  						"name": "my-reference1",
   196  					},
   197  					map[string]interface{}{
   198  						"name": "my-reference2",
   199  					},
   200  				},
   201  			},
   202  		},
   203  		{
   204  			name: "resource references nested in lists of objects are preserved",
   205  			state: map[string]interface{}{
   206  				"list_of_objects_key": []interface{}{
   207  					map[string]interface{}{
   208  						"reference_nested_in_list_of_objects_key": "ref-val1",
   209  					},
   210  					map[string]interface{}{
   211  						"reference_nested_in_list_of_objects_key": "ref-val2",
   212  					},
   213  				},
   214  			},
   215  			prevSpec: map[string]interface{}{
   216  				"listOfObjectsKey": []interface{}{
   217  					map[string]interface{}{
   218  						"nestedInListOfObjectsRef": map[string]interface{}{
   219  							"name": "my-reference1",
   220  						},
   221  					},
   222  					map[string]interface{}{
   223  						"nestedInListOfObjectsRef": map[string]interface{}{
   224  							"name": "my-reference2",
   225  						},
   226  					},
   227  				},
   228  			},
   229  			expected: map[string]interface{}{
   230  				"listOfObjectsKey": []interface{}{
   231  					map[string]interface{}{
   232  						"nestedInListOfObjectsRef": map[string]interface{}{
   233  							"name": "my-reference1",
   234  						},
   235  					},
   236  					map[string]interface{}{
   237  						"nestedInListOfObjectsRef": map[string]interface{}{
   238  							"name": "my-reference2",
   239  						},
   240  					},
   241  				},
   242  			},
   243  		},
   244  		{
   245  			name: "external resource references are preserved",
   246  			state: map[string]interface{}{
   247  				"reference_key": "ref-val",
   248  			},
   249  			prevSpec: map[string]interface{}{
   250  				"referenceRef": map[string]interface{}{
   251  					"external": "my-reference",
   252  				},
   253  			},
   254  			expected: map[string]interface{}{
   255  				"referenceRef": map[string]interface{}{
   256  					"external": "my-reference",
   257  				},
   258  			},
   259  		},
   260  		{
   261  			name: "external resource reference set if no reference defined by spec",
   262  			state: map[string]interface{}{
   263  				"reference_key": "ref-val",
   264  			},
   265  			prevSpec: map[string]interface{}{},
   266  			expected: map[string]interface{}{
   267  				"referenceRef": map[string]interface{}{
   268  					"external": "ref-val",
   269  				},
   270  			},
   271  		},
   272  		{
   273  			name: "list of external resource references set if no list defined by spec",
   274  			state: map[string]interface{}{
   275  				"list_of_references_key": []interface{}{
   276  					"ref-val-1",
   277  					"ref-val-2",
   278  				},
   279  			},
   280  			prevSpec: map[string]interface{}{},
   281  			expected: map[string]interface{}{
   282  				"listOfReferencesKey": []interface{}{
   283  					map[string]interface{}{
   284  						"external": "ref-val-1",
   285  					},
   286  					map[string]interface{}{
   287  						"external": "ref-val-2",
   288  					},
   289  				},
   290  			},
   291  		},
   292  		{
   293  			name: "set of external resource references with complex key set if no set defined by spec",
   294  			state: map[string]interface{}{
   295  				"complex_set_of_references_key": []interface{}{
   296  					"ref-val-1",
   297  					"ref-val-2",
   298  				},
   299  			},
   300  			prevSpec: map[string]interface{}{},
   301  			expected: map[string]interface{}{
   302  				"complexSetOfReferencesKey": []interface{}{
   303  					map[string]interface{}{
   304  						"subKeyRef": map[string]interface{}{
   305  							"external": "ref-val-1",
   306  						},
   307  					},
   308  					map[string]interface{}{
   309  						"subKeyRef": map[string]interface{}{
   310  							"external": "ref-val-2",
   311  						},
   312  					},
   313  				},
   314  			},
   315  		},
   316  		{
   317  			name: "spec-defined values are preserved",
   318  			state: map[string]interface{}{
   319  				"string_key": "fully-expanded-string-value",
   320  			},
   321  			prevSpec: map[string]interface{}{
   322  				"stringKey": "short-string-val",
   323  			},
   324  			expected: map[string]interface{}{
   325  				"stringKey": "short-string-val",
   326  			},
   327  		},
   328  		{
   329  			name: "primitive set ordering is kept consistent",
   330  			state: map[string]interface{}{
   331  				"primitive_set_key": []interface{}{
   332  					"a",
   333  					"b",
   334  					"c",
   335  					"d",
   336  				},
   337  			},
   338  			prevSpec: map[string]interface{}{
   339  				"primitiveSetKey": []interface{}{
   340  					"b",
   341  					"a",
   342  					"c",
   343  				},
   344  			},
   345  			expected: map[string]interface{}{
   346  				"primitiveSetKey": []interface{}{
   347  					"b",
   348  					"a",
   349  					"c",
   350  					"d",
   351  				},
   352  			},
   353  		},
   354  		{
   355  			name: "object set ordering is kept consistent",
   356  			state: map[string]interface{}{
   357  				"object_set_key": []interface{}{
   358  					map[string]interface{}{
   359  						"index": float64(0),
   360  					},
   361  					map[string]interface{}{
   362  						"index": float64(1),
   363  					},
   364  					map[string]interface{}{
   365  						"index": float64(3),
   366  					},
   367  				},
   368  			},
   369  			prevSpec: map[string]interface{}{
   370  				"objectSetKey": []interface{}{
   371  					map[string]interface{}{
   372  						"index": float64(1),
   373  					},
   374  					map[string]interface{}{
   375  						"index": float64(0),
   376  					},
   377  				},
   378  			},
   379  			expected: map[string]interface{}{
   380  				"objectSetKey": []interface{}{
   381  					map[string]interface{}{
   382  						"index": float64(1),
   383  					},
   384  					map[string]interface{}{
   385  						"index": float64(0),
   386  					},
   387  					map[string]interface{}{
   388  						"index": float64(3),
   389  					},
   390  				},
   391  			},
   392  		},
   393  		{
   394  			name: "defaulting is applied to the correct object in the set",
   395  			state: map[string]interface{}{
   396  				"object_set_key": []interface{}{
   397  					map[string]interface{}{
   398  						"index":           float64(0),
   399  						"nested_bool_key": true,
   400  					},
   401  					map[string]interface{}{
   402  						"index":           float64(1),
   403  						"nested_bool_key": false,
   404  					},
   405  				},
   406  			},
   407  			prevSpec: map[string]interface{}{
   408  				"objectSetKey": []interface{}{
   409  					map[string]interface{}{
   410  						"index": float64(1),
   411  					},
   412  					map[string]interface{}{
   413  						"index": float64(0),
   414  					},
   415  				},
   416  			},
   417  			expected: map[string]interface{}{
   418  				"objectSetKey": []interface{}{
   419  					map[string]interface{}{
   420  						"index": float64(1),
   421  					},
   422  					map[string]interface{}{
   423  						"index":         float64(0),
   424  						"nestedBoolKey": true,
   425  					},
   426  				},
   427  			},
   428  		},
   429  		{
   430  			name: "defaulting is applied to correct complex resource reference type",
   431  			state: map[string]interface{}{
   432  				"complex_reference_key": "ref-val",
   433  			},
   434  			prevSpec: map[string]interface{}{},
   435  			expected: map[string]interface{}{
   436  				"complexReferenceKey": map[string]interface{}{
   437  					"value": "ref-val",
   438  				},
   439  			},
   440  		},
   441  		{
   442  			name: "parent values are filtered out of result if resource only supports container annotations",
   443  			rc: &corekccv1alpha1.ResourceConfig{
   444  				Containers: []corekccv1alpha1.Container{
   445  					{
   446  						Type:    corekccv1alpha1.ContainerTypeFolder,
   447  						TFField: "parent_key",
   448  					},
   449  				},
   450  			},
   451  			state: map[string]interface{}{
   452  				"parent_key": "project-id-from-tf-state",
   453  				"string_key": "string-val",
   454  			},
   455  			prevSpec: map[string]interface{}{
   456  				"stringKey": "string-val",
   457  			},
   458  			expected: map[string]interface{}{
   459  				"stringKey": "string-val",
   460  			},
   461  		},
   462  		{
   463  			name: "parent values are set as external hierarchical references if resource supports hierarchical references",
   464  			rc: &corekccv1alpha1.ResourceConfig{
   465  				Containers: []corekccv1alpha1.Container{
   466  					{
   467  						Type:    corekccv1alpha1.ContainerTypeProject,
   468  						TFField: "parent_key",
   469  					},
   470  				},
   471  				HierarchicalReferences: []corekccv1alpha1.HierarchicalReference{
   472  					{
   473  						Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   474  						Key:  "projectRef",
   475  					},
   476  				},
   477  				ResourceReferences: []corekccv1alpha1.ReferenceConfig{
   478  					{
   479  						TFField: "parent_key",
   480  						TypeConfig: corekccv1alpha1.TypeConfig{
   481  							Key: "projectRef",
   482  							GVK: k8sschema.GroupVersionKind{
   483  								Group:   "test1.cnrm.cloud.google.com",
   484  								Version: "v1alpha1",
   485  								Kind:    "Test1Bar",
   486  							},
   487  						},
   488  					},
   489  				},
   490  			},
   491  			state: map[string]interface{}{
   492  				"parent_key": "project-id-from-tf-state",
   493  				"string_key": "string-val",
   494  			},
   495  			prevSpec: map[string]interface{}{
   496  				"stringKey": "string-val",
   497  			},
   498  			expected: map[string]interface{}{
   499  				"stringKey": "string-val",
   500  				"projectRef": map[string]interface{}{
   501  					"external": "project-id-from-tf-state",
   502  				},
   503  			},
   504  		},
   505  		{
   506  			name: "parent values are set as external hierarchical references if resource only supports hierarchical references",
   507  			rc: &corekccv1alpha1.ResourceConfig{
   508  				HierarchicalReferences: []corekccv1alpha1.HierarchicalReference{
   509  					{
   510  						Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   511  						Key:  "projectRef",
   512  					},
   513  				},
   514  				ResourceReferences: []corekccv1alpha1.ReferenceConfig{
   515  					{
   516  						TFField: "parent_key",
   517  						TypeConfig: corekccv1alpha1.TypeConfig{
   518  							Key: "projectRef",
   519  							GVK: k8sschema.GroupVersionKind{
   520  								Group:   "test1.cnrm.cloud.google.com",
   521  								Version: "v1alpha1",
   522  								Kind:    "Test1Bar",
   523  							},
   524  						},
   525  					},
   526  				},
   527  			},
   528  			state: map[string]interface{}{
   529  				"parent_key": "project-id-from-tf-state",
   530  				"string_key": "string-val",
   531  			},
   532  			prevSpec: map[string]interface{}{
   533  				"stringKey": "string-val",
   534  			},
   535  			expected: map[string]interface{}{
   536  				"stringKey": "string-val",
   537  				"projectRef": map[string]interface{}{
   538  					"external": "project-id-from-tf-state",
   539  				},
   540  			},
   541  		},
   542  		{
   543  			name: "hierarchical references are preserved",
   544  			rc: &corekccv1alpha1.ResourceConfig{
   545  				Containers: []corekccv1alpha1.Container{
   546  					{
   547  						Type:    corekccv1alpha1.ContainerTypeProject,
   548  						TFField: "parent_key",
   549  					},
   550  				},
   551  				HierarchicalReferences: []corekccv1alpha1.HierarchicalReference{
   552  					{
   553  						Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   554  						Key:  "projectRef",
   555  					},
   556  				},
   557  				ResourceReferences: []corekccv1alpha1.ReferenceConfig{
   558  					{
   559  						TFField: "parent_key",
   560  						TypeConfig: corekccv1alpha1.TypeConfig{
   561  							Key: "projectRef",
   562  							GVK: k8sschema.GroupVersionKind{
   563  								Group:   "test1.cnrm.cloud.google.com",
   564  								Version: "v1alpha1",
   565  								Kind:    "Test1Bar",
   566  							},
   567  						},
   568  					},
   569  				},
   570  			},
   571  			state: map[string]interface{}{
   572  				"parent_key": "project-id-from-tf-state",
   573  				"string_key": "string-val",
   574  			},
   575  			prevSpec: map[string]interface{}{
   576  				"stringKey": "string-val",
   577  				"projectRef": map[string]interface{}{
   578  					"name": "my-ref",
   579  				},
   580  			},
   581  			expected: map[string]interface{}{
   582  				"stringKey": "string-val",
   583  				"projectRef": map[string]interface{}{
   584  					"name": "my-ref",
   585  				},
   586  			},
   587  		},
   588  		{
   589  			name: "external hierarchical references are preserved",
   590  			rc: &corekccv1alpha1.ResourceConfig{
   591  				Containers: []corekccv1alpha1.Container{
   592  					{
   593  						Type:    corekccv1alpha1.ContainerTypeProject,
   594  						TFField: "parent_key",
   595  					},
   596  				},
   597  				HierarchicalReferences: []corekccv1alpha1.HierarchicalReference{
   598  					{
   599  						Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   600  						Key:  "projectRef",
   601  					},
   602  				},
   603  				ResourceReferences: []corekccv1alpha1.ReferenceConfig{
   604  					{
   605  						TFField: "parent_key",
   606  						TypeConfig: corekccv1alpha1.TypeConfig{
   607  							Key: "projectRef",
   608  							GVK: k8sschema.GroupVersionKind{
   609  								Group:   "test1.cnrm.cloud.google.com",
   610  								Version: "v1alpha1",
   611  								Kind:    "Test1Bar",
   612  							},
   613  						},
   614  					},
   615  				},
   616  			},
   617  			state: map[string]interface{}{
   618  				"parent_key": "project-id-from-tf-state",
   619  				"string_key": "string-val",
   620  			},
   621  			prevSpec: map[string]interface{}{
   622  				"stringKey": "string-val",
   623  				"projectRef": map[string]interface{}{
   624  					"external": "my-ref",
   625  				},
   626  			},
   627  			expected: map[string]interface{}{
   628  				"stringKey": "string-val",
   629  				"projectRef": map[string]interface{}{
   630  					"external": "my-ref",
   631  				},
   632  			},
   633  		},
   634  		{
   635  			name: "sensitive fields with simple values are preserved",
   636  			state: map[string]interface{}{
   637  				"sensitive_field_key": "val",
   638  			},
   639  			prevSpec: map[string]interface{}{
   640  				"sensitiveFieldKey": map[string]interface{}{
   641  					"value": "old-val",
   642  				},
   643  			},
   644  			expected: map[string]interface{}{
   645  				"sensitiveFieldKey": map[string]interface{}{
   646  					"value": "old-val",
   647  				},
   648  			},
   649  		},
   650  		{
   651  			name: "sensitive fields with values from secret refs are preserved",
   652  			state: map[string]interface{}{
   653  				"sensitive_field_key": "val",
   654  			},
   655  			prevSpec: map[string]interface{}{
   656  				"sensitiveFieldKey": map[string]interface{}{
   657  					"valueFrom": map[string]interface{}{
   658  						"secretKeyRef": map[string]interface{}{
   659  							"name": "secret1",
   660  							"key":  "key1",
   661  						},
   662  					},
   663  				},
   664  			},
   665  			expected: map[string]interface{}{
   666  				"sensitiveFieldKey": map[string]interface{}{
   667  					"valueFrom": map[string]interface{}{
   668  						"secretKeyRef": map[string]interface{}{
   669  							"name": "secret1",
   670  							"key":  "key1",
   671  						},
   672  					},
   673  				},
   674  			},
   675  		},
   676  		{
   677  			name: "sensitive fields nested in lists of objects are preserved",
   678  			state: map[string]interface{}{
   679  				"list_of_objects_key": []interface{}{
   680  					map[string]interface{}{
   681  						"sensitive_field_nested_in_list_of_objects_key": "val1",
   682  					},
   683  					map[string]interface{}{
   684  						"sensitive_field_nested_in_list_of_objects_key": "val2",
   685  					},
   686  				},
   687  			},
   688  			prevSpec: map[string]interface{}{
   689  				"listOfObjectsKey": []interface{}{
   690  					map[string]interface{}{
   691  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
   692  							"valueFrom": map[string]interface{}{
   693  								"secretKeyRef": map[string]interface{}{
   694  									"name": "secret1",
   695  									"key":  "key1",
   696  								},
   697  							},
   698  						},
   699  					},
   700  					map[string]interface{}{
   701  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
   702  							"valueFrom": map[string]interface{}{
   703  								"secretKeyRef": map[string]interface{}{
   704  									"name": "secret2",
   705  									"key":  "key2",
   706  								},
   707  							},
   708  						},
   709  					},
   710  				},
   711  			},
   712  			expected: map[string]interface{}{
   713  				"listOfObjectsKey": []interface{}{
   714  					map[string]interface{}{
   715  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
   716  							"valueFrom": map[string]interface{}{
   717  								"secretKeyRef": map[string]interface{}{
   718  									"name": "secret1",
   719  									"key":  "key1",
   720  								},
   721  							},
   722  						},
   723  					},
   724  					map[string]interface{}{
   725  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
   726  							"valueFrom": map[string]interface{}{
   727  								"secretKeyRef": map[string]interface{}{
   728  									"name": "secret2",
   729  									"key":  "key2",
   730  								},
   731  							},
   732  						},
   733  					},
   734  				},
   735  			},
   736  		},
   737  		{
   738  			name: "sensitive fields nested in objects are preserved",
   739  			state: map[string]interface{}{
   740  				"nested_object_key": []interface{}{
   741  					map[string]interface{}{
   742  						"nested_sensitive_field_key": "val",
   743  					},
   744  				},
   745  			},
   746  			prevSpec: map[string]interface{}{
   747  				"nestedObjectKey": map[string]interface{}{
   748  					"nestedSensitiveFieldKey": map[string]interface{}{
   749  						"valueFrom": map[string]interface{}{
   750  							"secretKeyRef": map[string]interface{}{
   751  								"name": "secret1",
   752  								"key":  "key1",
   753  							},
   754  						},
   755  					},
   756  				},
   757  			},
   758  			expected: map[string]interface{}{
   759  				"nestedObjectKey": map[string]interface{}{
   760  					"nestedSensitiveFieldKey": map[string]interface{}{
   761  						"valueFrom": map[string]interface{}{
   762  							"secretKeyRef": map[string]interface{}{
   763  								"name": "secret1",
   764  								"key":  "key1",
   765  							},
   766  						},
   767  					},
   768  				},
   769  			},
   770  		},
   771  		{
   772  			name: "sensitive fields set with simple value if not specified",
   773  			state: map[string]interface{}{
   774  				"sensitive_field_key": "val",
   775  			},
   776  			prevSpec: map[string]interface{}{},
   777  			expected: map[string]interface{}{
   778  				"sensitiveFieldKey": map[string]interface{}{
   779  					"value": "val",
   780  				},
   781  			},
   782  		},
   783  		{
   784  			name: "sensitive fields nested in lists of objects set with simple value if not specified",
   785  			state: map[string]interface{}{
   786  				"list_of_objects_key": []interface{}{
   787  					map[string]interface{}{
   788  						"sensitive_field_nested_in_list_of_objects_key": "val1",
   789  					},
   790  					map[string]interface{}{
   791  						"sensitive_field_nested_in_list_of_objects_key": "val2",
   792  					},
   793  				},
   794  			},
   795  			prevSpec: map[string]interface{}{},
   796  			expected: map[string]interface{}{
   797  				"listOfObjectsKey": []interface{}{
   798  					map[string]interface{}{
   799  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
   800  							"value": "val1",
   801  						},
   802  					},
   803  					map[string]interface{}{
   804  						"sensitiveFieldNestedInListOfObjectsKey": map[string]interface{}{
   805  							"value": "val2",
   806  						},
   807  					},
   808  				},
   809  			},
   810  		},
   811  		{
   812  			name: "sensitive fields nested in objects set with simple value if not specified",
   813  			state: map[string]interface{}{
   814  				"nested_object_key": []interface{}{
   815  					map[string]interface{}{
   816  						"nested_sensitive_field_key": "val",
   817  					},
   818  				},
   819  			},
   820  			prevSpec: map[string]interface{}{},
   821  			expected: map[string]interface{}{
   822  				"nestedObjectKey": map[string]interface{}{
   823  					"nestedSensitiveFieldKey": map[string]interface{}{
   824  						"value": "val",
   825  					},
   826  				},
   827  			},
   828  		},
   829  		{
   830  			name: "maps are treated as atomic when specified by user",
   831  			state: map[string]interface{}{
   832  				"map_key": map[string]interface{}{
   833  					"foo": "bar",
   834  					"baz": "abc",
   835  				},
   836  			},
   837  			prevSpec: map[string]interface{}{
   838  				"mapKey": map[string]interface{}{
   839  					"foo": "bar",
   840  				},
   841  			},
   842  			expected: map[string]interface{}{
   843  				"mapKey": map[string]interface{}{
   844  					"foo": "bar",
   845  				},
   846  			},
   847  		},
   848  		// Tests surrounding managed fields
   849  		{
   850  			name: "values are sourced from live state when not in managed fields set",
   851  			state: map[string]interface{}{
   852  				"int_key":    float64(2),
   853  				"float_key":  float64(1.5),
   854  				"string_key": "external",
   855  				"bool_key":   true,
   856  				"nested_object_key": []interface{}{
   857  					map[string]interface{}{
   858  						"nested_float_key": float64(1.5),
   859  					},
   860  				},
   861  			},
   862  			prevSpec: map[string]interface{}{
   863  				"intKey":    float64(1),
   864  				"floatKey":  float64(0.5),
   865  				"stringKey": "k8s",
   866  				"boolKey":   false,
   867  				"nestedObjectKey": map[string]interface{}{
   868  					"nestedFloatKey": float64(0.5),
   869  				},
   870  			},
   871  			managedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
   872  				"f:unrelated": emptyObject,
   873  			}),
   874  			expected: map[string]interface{}{
   875  				"intKey":    float64(2),
   876  				"floatKey":  float64(1.5),
   877  				"stringKey": "external",
   878  				"boolKey":   true,
   879  				"nestedObjectKey": map[string]interface{}{
   880  					"nestedFloatKey": float64(1.5),
   881  				},
   882  			},
   883  		},
   884  		{
   885  			name: "values are sourced from spec when in managed fields set",
   886  			state: map[string]interface{}{
   887  				"int_key":    float64(2),
   888  				"float_key":  float64(1.5),
   889  				"string_key": "external",
   890  				"bool_key":   true,
   891  				"nested_object_key": []interface{}{
   892  					map[string]interface{}{
   893  						"nested_float_key": float64(1.5),
   894  					},
   895  				},
   896  			},
   897  			prevSpec: map[string]interface{}{
   898  				"intKey":    float64(1),
   899  				"floatKey":  float64(0.5),
   900  				"stringKey": "k8s",
   901  				"boolKey":   false,
   902  				"nestedObjectKey": map[string]interface{}{
   903  					"nestedFloatKey": float64(0.5),
   904  				},
   905  			},
   906  			managedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
   907  				"f:intKey":    emptyObject,
   908  				"f:floatKey":  emptyObject,
   909  				"f:stringKey": emptyObject,
   910  				"f:boolKey":   emptyObject,
   911  				"f:nestedObjectKey": map[string]interface{}{
   912  					"f:nestedFloatKey": emptyObject,
   913  				},
   914  			}),
   915  			expected: map[string]interface{}{
   916  				"intKey":    float64(1),
   917  				"floatKey":  float64(0.5),
   918  				"stringKey": "k8s",
   919  				"boolKey":   false,
   920  				"nestedObjectKey": map[string]interface{}{
   921  					"nestedFloatKey": float64(0.5),
   922  				},
   923  			},
   924  		},
   925  		{
   926  			name: "maps are treated as atomic when k8s-managed",
   927  			state: map[string]interface{}{
   928  				"map_key": map[string]interface{}{
   929  					"foo": "bar",
   930  					"baz": "abc",
   931  				},
   932  			},
   933  			prevSpec: map[string]interface{}{
   934  				"mapKey": map[string]interface{}{
   935  					"foo": "bar",
   936  				},
   937  			},
   938  			managedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
   939  				"f:mapKey": map[string]interface{}{
   940  					"f:foo": emptyObject,
   941  				},
   942  			}),
   943  			expected: map[string]interface{}{
   944  				"mapKey": map[string]interface{}{
   945  					"foo": "bar",
   946  				},
   947  			},
   948  		},
   949  		// TODO(kcc-eng): The following list behavior is required today to keep
   950  		//  consistent with the existing behavior that defaults values in lists.
   951  		//  This will be modified to be more advanced as part of the externally-
   952  		//  managed list merging implementation.
   953  		{
   954  			name: "values in lists of objects ignore managed fields",
   955  			state: map[string]interface{}{
   956  				"list_of_objects_key": []interface{}{
   957  					map[string]interface{}{
   958  						"nested_int_key": float64(1),
   959  					},
   960  				},
   961  			},
   962  			prevSpec: map[string]interface{}{
   963  				"listOfObjectsKey": []interface{}{
   964  					map[string]interface{}{
   965  						"nestedIntKey": float64(2),
   966  					},
   967  				},
   968  			},
   969  			managedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
   970  				"f:unrelated": emptyObject,
   971  			}),
   972  			expected: map[string]interface{}{
   973  				// reflects the traditional fully-k8s-managed overlay of
   974  				// the spec list on the live state list
   975  				"listOfObjectsKey": []interface{}{
   976  					map[string]interface{}{
   977  						"nestedIntKey": float64(2),
   978  					},
   979  				},
   980  			},
   981  		},
   982  		{
   983  			name: "values in primitive lists ignore managed fields",
   984  			state: map[string]interface{}{
   985  				"list_of_primitives_key": []interface{}{
   986  					"element_1",
   987  					"element_2",
   988  				},
   989  			},
   990  			prevSpec: map[string]interface{}{
   991  				"listOfPrimitivesKey": []interface{}{
   992  					"element1",
   993  				},
   994  			},
   995  			managedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
   996  				"f:listOfPrimitivesKey": emptyObject,
   997  			}),
   998  			expected: map[string]interface{}{
   999  				// reflects solely the live state
  1000  				"listOfPrimitivesKey": []interface{}{
  1001  					"element_1",
  1002  					"element_2",
  1003  				},
  1004  			},
  1005  		},
  1006  	}
  1007  
  1008  	for _, tc := range tests {
  1009  		tc := tc
  1010  		t.Run(tc.name, func(t *testing.T) {
  1011  			t.Parallel()
  1012  			r := resourceSkeleton()
  1013  			if tc.rc != nil {
  1014  				r.ResourceConfig = *tc.rc
  1015  			}
  1016  			if tc.schemaOverride != nil {
  1017  				r.TFResource.Schema = tc.schemaOverride
  1018  			}
  1019  			r.SetNamespace(test.Namespace)
  1020  			actual := ConvertTFObjToKCCObj(tc.state, tc.prevSpec, r.TFResource.Schema, &r.ResourceConfig, "", tc.managedFields)
  1021  			if !reflect.DeepEqual(tc.expected, actual) {
  1022  				t.Fatalf("expected: %v, actual: %v", tc.expected, actual)
  1023  			}
  1024  		})
  1025  
  1026  	}
  1027  }
  1028  
  1029  func TestGetLabelsFromState(t *testing.T) {
  1030  	tests := []struct {
  1031  		name         string
  1032  		rc           *corekccv1alpha1.ResourceConfig
  1033  		tfAttributes map[string]string
  1034  		expected     map[string]string
  1035  	}{
  1036  		{
  1037  			name: "empty labels should resolve",
  1038  			rc: &corekccv1alpha1.ResourceConfig{
  1039  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1040  					Labels: "map_key",
  1041  				},
  1042  			},
  1043  			tfAttributes: map[string]string{
  1044  				"map_key.%": "0",
  1045  			},
  1046  			expected: map[string]string{},
  1047  		},
  1048  		{
  1049  			name: "simple labels should resolve",
  1050  			rc: &corekccv1alpha1.ResourceConfig{
  1051  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1052  					Labels: "map_key",
  1053  				},
  1054  			},
  1055  			tfAttributes: map[string]string{
  1056  				"map_key.%":    "2",
  1057  				"map_key.key1": "val1",
  1058  				"map_key.key2": "val2",
  1059  			},
  1060  			expected: map[string]string{
  1061  				"key1": "val1",
  1062  				"key2": "val2",
  1063  			},
  1064  		},
  1065  		{
  1066  			name: "nested labels should resolve",
  1067  			rc: &corekccv1alpha1.ResourceConfig{
  1068  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1069  					Labels: "nested_object_key.nested_map_key",
  1070  				},
  1071  			},
  1072  			tfAttributes: map[string]string{
  1073  				"nested_object_key.#":                     "1",
  1074  				"nested_object_key.0.nested_map_key.%":    "2",
  1075  				"nested_object_key.0.nested_map_key.key1": "val1",
  1076  				"nested_object_key.0.nested_map_key.key2": "val2",
  1077  			},
  1078  			expected: map[string]string{
  1079  				"key1": "val1",
  1080  				"key2": "val2",
  1081  			},
  1082  		},
  1083  		{
  1084  			name: "nested labels with nil should resolve",
  1085  			rc: &corekccv1alpha1.ResourceConfig{
  1086  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1087  					Labels: "nested_object_key.nested_map_key",
  1088  				},
  1089  			},
  1090  			tfAttributes: map[string]string{
  1091  				"nested_object_key.#": "1",
  1092  			},
  1093  			expected: map[string]string{},
  1094  		},
  1095  	}
  1096  	for _, tc := range tests {
  1097  		t.Run(tc.name, func(t *testing.T) {
  1098  			tc := tc
  1099  			t.Parallel()
  1100  			r := resourceSkeleton()
  1101  			if tc.rc != nil {
  1102  				r.ResourceConfig = *tc.rc
  1103  			}
  1104  			rawState := terraform.InstanceState{
  1105  				Attributes: tc.tfAttributes,
  1106  			}
  1107  			labels := GetLabelsFromState(r, &rawState)
  1108  			if !reflect.DeepEqual(tc.expected, labels) {
  1109  				t.Fatalf("expected: %v, actual: %v", tc.expected, labels)
  1110  			}
  1111  		})
  1112  	}
  1113  }
  1114  
  1115  func TestResolveSpecAndStatusWithResourceID_WithDesiredStateInSpecAndObservedStateInStatus(t *testing.T) {
  1116  	tests := []struct {
  1117  		name           string
  1118  		rc             *corekccv1alpha1.ResourceConfig
  1119  		metadataName   string
  1120  		prevSpec       map[string]interface{}
  1121  		prevStatus     map[string]interface{}
  1122  		tfResource     *tfschema.Resource
  1123  		tfAttributes   map[string]string
  1124  		expectedSpec   map[string]interface{}
  1125  		expectedStatus map[string]interface{}
  1126  		managedFields  *fieldpath.Set
  1127  	}{
  1128  		{
  1129  			name: "only persist specified fields in spec",
  1130  			rc: &corekccv1alpha1.ResourceConfig{
  1131  				ResourceID: corekccv1alpha1.ResourceID{
  1132  					TargetField: "test_field",
  1133  				},
  1134  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1135  					Name: "test_field",
  1136  				},
  1137  				HierarchicalReferences: []corekccv1alpha1.HierarchicalReference{
  1138  					{
  1139  						Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
  1140  						Key:  "projectRef",
  1141  					},
  1142  				},
  1143  				ResourceReferences: []corekccv1alpha1.ReferenceConfig{
  1144  					{
  1145  						TFField: "parent_key",
  1146  						TypeConfig: corekccv1alpha1.TypeConfig{
  1147  							Key: "projectRef",
  1148  							GVK: k8sschema.GroupVersionKind{
  1149  								Group:   "test1.cnrm.cloud.google.com",
  1150  								Version: "v1alpha1",
  1151  								Kind:    "Test1Bar",
  1152  							},
  1153  						},
  1154  					},
  1155  				},
  1156  			},
  1157  			prevSpec: map[string]interface{}{
  1158  				"resourceID": "resource-id",
  1159  				"intKey":     int64(1),
  1160  				"floatKey":   0.5,
  1161  				"projectRef": map[string]interface{}{
  1162  					"name": "my-ref",
  1163  				},
  1164  				"sensitiveFieldKey": map[string]interface{}{
  1165  					"valueFrom": map[string]interface{}{
  1166  						"secretKeyRef": map[string]interface{}{
  1167  							"name": "secret1",
  1168  							"key":  "key1",
  1169  						},
  1170  					},
  1171  				},
  1172  			},
  1173  			prevStatus: map[string]interface{}{},
  1174  			tfResource: &tfschema.Resource{
  1175  				Schema: map[string]*tfschema.Schema{
  1176  					"test_field": {
  1177  						Type:     tfschema.TypeString,
  1178  						Required: true,
  1179  					},
  1180  					"parent_key": {
  1181  						Type:     tfschema.TypeString,
  1182  						Required: true,
  1183  					},
  1184  					"int_key": {
  1185  						Type:     tfschema.TypeInt,
  1186  						Optional: true,
  1187  					},
  1188  					"float_key": {
  1189  						Type:     tfschema.TypeFloat,
  1190  						Optional: true,
  1191  					},
  1192  					"bool_key": {
  1193  						Type:     tfschema.TypeBool,
  1194  						Optional: true,
  1195  					},
  1196  					"sensitive_field_key": {
  1197  						Type:      tfschema.TypeString,
  1198  						Required:  true,
  1199  						Sensitive: true,
  1200  					},
  1201  				},
  1202  			},
  1203  			tfAttributes: map[string]string{
  1204  				"test_field":          "resource-id",
  1205  				"int_key":             "1",
  1206  				"float_key":           "0.5",
  1207  				"bool_key":            "false",
  1208  				"parent_key":          "project-id-from-tf-state",
  1209  				"sensitive_field_key": "val",
  1210  			},
  1211  			expectedSpec: map[string]interface{}{
  1212  				"resourceID": "resource-id",
  1213  				"intKey":     int64(1),
  1214  				"floatKey":   0.5,
  1215  				"projectRef": map[string]interface{}{
  1216  					"name": "my-ref",
  1217  				},
  1218  				"sensitiveFieldKey": map[string]interface{}{
  1219  					"valueFrom": map[string]interface{}{
  1220  						"secretKeyRef": map[string]interface{}{
  1221  							"name": "secret1",
  1222  							"key":  "key1",
  1223  						},
  1224  					},
  1225  				},
  1226  			},
  1227  			expectedStatus: nil,
  1228  		},
  1229  		{
  1230  			name:       "observed state for output-only fields are persisted in status",
  1231  			prevSpec:   map[string]interface{}{},
  1232  			prevStatus: map[string]interface{}{},
  1233  			tfResource: &tfschema.Resource{
  1234  				Schema: map[string]*tfschema.Schema{
  1235  					"status_field": {
  1236  						Type:     tfschema.TypeString,
  1237  						Computed: true,
  1238  					},
  1239  				},
  1240  			},
  1241  			tfAttributes: map[string]string{
  1242  				"status_field": "strVal",
  1243  			},
  1244  			expectedSpec: map[string]interface{}{},
  1245  			expectedStatus: map[string]interface{}{
  1246  				"statusField": "strVal",
  1247  			},
  1248  		},
  1249  		{
  1250  			name: "persist desired state in spec and output-only observed state in status",
  1251  			rc: &corekccv1alpha1.ResourceConfig{
  1252  				ResourceID: corekccv1alpha1.ResourceID{
  1253  					TargetField: "test_field",
  1254  				},
  1255  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1256  					Name: "test_field",
  1257  				},
  1258  			},
  1259  			prevSpec: map[string]interface{}{
  1260  				"resourceID": "resource-id",
  1261  				"intKey":     int64(1),
  1262  				"floatKey":   0.5,
  1263  			},
  1264  			prevStatus: map[string]interface{}{},
  1265  			tfResource: &tfschema.Resource{
  1266  				Schema: map[string]*tfschema.Schema{
  1267  					"test_field": {
  1268  						Type:     tfschema.TypeString,
  1269  						Required: true,
  1270  					},
  1271  					"int_key": {
  1272  						Type:     tfschema.TypeInt,
  1273  						Optional: true,
  1274  					},
  1275  					"float_key": {
  1276  						Type:     tfschema.TypeFloat,
  1277  						Optional: true,
  1278  					},
  1279  					"bool_key": {
  1280  						Type:     tfschema.TypeBool,
  1281  						Optional: true,
  1282  					},
  1283  					"status_field": {
  1284  						Type:     tfschema.TypeString,
  1285  						Computed: true,
  1286  					},
  1287  				},
  1288  			},
  1289  			tfAttributes: map[string]string{
  1290  				"test_field":   "resource-id",
  1291  				"int_key":      "1",
  1292  				"float_key":    "0.5",
  1293  				"bool_key":     "false",
  1294  				"status_field": "strVal",
  1295  			},
  1296  			expectedSpec: map[string]interface{}{
  1297  				"resourceID": "resource-id",
  1298  				"intKey":     int64(1),
  1299  				"floatKey":   0.5,
  1300  			},
  1301  			expectedStatus: map[string]interface{}{
  1302  				"statusField": "strVal",
  1303  			},
  1304  		},
  1305  		{
  1306  			name: "only persist specified nested fields in spec",
  1307  			rc: &corekccv1alpha1.ResourceConfig{
  1308  				ResourceID: corekccv1alpha1.ResourceID{
  1309  					TargetField: "test_field",
  1310  				},
  1311  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1312  					Name: "test_field",
  1313  				},
  1314  			},
  1315  			prevSpec: map[string]interface{}{
  1316  				"resourceID": "resource-id",
  1317  				"nestedObjectKey": map[string]interface{}{
  1318  					"nestedKey1": "val1",
  1319  				},
  1320  			},
  1321  			prevStatus: map[string]interface{}{},
  1322  			tfResource: &tfschema.Resource{
  1323  				Schema: map[string]*tfschema.Schema{
  1324  					"test_field": {
  1325  						Type:     tfschema.TypeString,
  1326  						Required: true,
  1327  					},
  1328  					"nested_object_key": {
  1329  						Type:     tfschema.TypeList,
  1330  						MaxItems: 1,
  1331  						Optional: true,
  1332  						Elem: &tfschema.Resource{
  1333  							Schema: map[string]*tfschema.Schema{
  1334  								"nested_key1": {
  1335  									Type:     tfschema.TypeString,
  1336  									Optional: true,
  1337  								},
  1338  								"nested_key2": {
  1339  									Type:     tfschema.TypeString,
  1340  									Optional: true,
  1341  								},
  1342  							},
  1343  						},
  1344  					},
  1345  				},
  1346  			},
  1347  			tfAttributes: map[string]string{
  1348  				"test_field":                      "resource-id",
  1349  				"nested_object_key.#":             "1",
  1350  				"nested_object_key.0.nested_key1": "val1",
  1351  				"nested_object_key.0.nested_key2": "val2",
  1352  			},
  1353  			expectedSpec: map[string]interface{}{
  1354  				"resourceID": "resource-id",
  1355  				"nestedObjectKey": map[string]interface{}{
  1356  					"nestedKey1": "val1",
  1357  				},
  1358  			},
  1359  		},
  1360  		{
  1361  			name: "preserve lists of objects unmodified in spec if specified",
  1362  			rc: &corekccv1alpha1.ResourceConfig{
  1363  				ResourceID: corekccv1alpha1.ResourceID{
  1364  					TargetField: "test_field",
  1365  				},
  1366  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1367  					Name: "test_field",
  1368  				},
  1369  			},
  1370  			prevSpec: map[string]interface{}{
  1371  				"resourceID": "resource-id",
  1372  				"listOfObjectsKey": []interface{}{
  1373  					map[string]interface{}{
  1374  						"field1": 0.5,
  1375  						"field2": "strval1",
  1376  					},
  1377  					map[string]interface{}{
  1378  						"field1": 0.7,
  1379  					},
  1380  				},
  1381  			},
  1382  			prevStatus: map[string]interface{}{},
  1383  			tfResource: &tfschema.Resource{
  1384  				Schema: map[string]*tfschema.Schema{
  1385  					"test_field": {
  1386  						Type:     tfschema.TypeString,
  1387  						Required: true,
  1388  					},
  1389  					"list_of_objects_key": {
  1390  						Type:     tfschema.TypeList,
  1391  						Optional: true,
  1392  						Elem: &tfschema.Resource{
  1393  							Schema: map[string]*tfschema.Schema{
  1394  								"field1": {
  1395  									Type:     tfschema.TypeFloat,
  1396  									Optional: true,
  1397  								},
  1398  								"field2": {
  1399  									Type:     tfschema.TypeString,
  1400  									Optional: true,
  1401  								},
  1402  							},
  1403  						},
  1404  					},
  1405  				},
  1406  			},
  1407  			tfAttributes: map[string]string{
  1408  				"test_field":                   "resource-id",
  1409  				"list_of_objects_key.#":        "2",
  1410  				"list_of_objects_key.0.field1": "0.5",
  1411  				"list_of_objects_key.0.field2": "strval1",
  1412  				"list_of_objects_key.1.field1": "0.7",
  1413  				"list_of_objects_key.1.field2": "strval2",
  1414  			},
  1415  			expectedSpec: map[string]interface{}{
  1416  				"resourceID": "resource-id",
  1417  				"listOfObjectsKey": []interface{}{
  1418  					map[string]interface{}{
  1419  						"field1": 0.5,
  1420  						"field2": "strval1",
  1421  					},
  1422  					map[string]interface{}{
  1423  						"field1": 0.7,
  1424  					},
  1425  				},
  1426  			},
  1427  		},
  1428  		{
  1429  			name: "primitive lists are preserved with specified values",
  1430  			rc: &corekccv1alpha1.ResourceConfig{
  1431  				ResourceID: corekccv1alpha1.ResourceID{
  1432  					TargetField: "test_field",
  1433  				},
  1434  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1435  					Name: "test_field",
  1436  				},
  1437  			},
  1438  			prevSpec: map[string]interface{}{
  1439  				"resourceID": "resource-id",
  1440  				"listOfPrimitivesKey": []interface{}{
  1441  					"element_1",
  1442  				},
  1443  			},
  1444  			prevStatus: map[string]interface{}{},
  1445  			tfResource: &tfschema.Resource{
  1446  				Schema: map[string]*tfschema.Schema{
  1447  					"test_field": {
  1448  						Type:     tfschema.TypeString,
  1449  						Required: true,
  1450  					},
  1451  					"list_of_primitives_key": {
  1452  						Type:     tfschema.TypeList,
  1453  						Optional: true,
  1454  						Elem: &tfschema.Schema{
  1455  							Type: tfschema.TypeString,
  1456  						},
  1457  					},
  1458  				},
  1459  			},
  1460  			tfAttributes: map[string]string{
  1461  				"test_field":               "resource-id",
  1462  				"list_of_primitives_key.#": "2",
  1463  				"list_of_primitives_key.0": "element_1",
  1464  				"list_of_primitives_key.1": "element_2",
  1465  			},
  1466  			expectedSpec: map[string]interface{}{
  1467  				"resourceID": "resource-id",
  1468  				"listOfPrimitivesKey": []interface{}{
  1469  					"element_1",
  1470  				},
  1471  			},
  1472  		},
  1473  		{
  1474  			name: "server-generated id is retrieved from state and persisted",
  1475  			rc: &corekccv1alpha1.ResourceConfig{
  1476  				ResourceID: corekccv1alpha1.ResourceID{
  1477  					TargetField: "test_field",
  1478  				},
  1479  				ServerGeneratedIDField: "test_field",
  1480  			},
  1481  			prevSpec:   map[string]interface{}{},
  1482  			prevStatus: map[string]interface{}{},
  1483  			tfResource: &tfschema.Resource{
  1484  				Schema: map[string]*tfschema.Schema{
  1485  					"test_field": {
  1486  						Type:     tfschema.TypeString,
  1487  						Computed: true,
  1488  					},
  1489  				},
  1490  			},
  1491  			tfAttributes: map[string]string{
  1492  				"test_field": "new-server-generated-id",
  1493  			},
  1494  			expectedSpec: map[string]interface{}{
  1495  				"resourceID": "new-server-generated-id",
  1496  			},
  1497  			expectedStatus: map[string]interface{}{
  1498  				"testField": "new-server-generated-id",
  1499  			},
  1500  		},
  1501  		{
  1502  			name: "fields in spec are persisted even if they not in managed fields set",
  1503  			rc: &corekccv1alpha1.ResourceConfig{
  1504  				ResourceID: corekccv1alpha1.ResourceID{
  1505  					TargetField: "test_field",
  1506  				},
  1507  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1508  					Name: "test_field",
  1509  				},
  1510  			},
  1511  			prevSpec: map[string]interface{}{
  1512  				"resourceID": "resource-id",
  1513  				"intKey":     int64(1),
  1514  				"floatKey":   0.5,
  1515  				"boolKey":    false,
  1516  			},
  1517  			prevStatus: map[string]interface{}{},
  1518  			tfResource: &tfschema.Resource{
  1519  				Schema: map[string]*tfschema.Schema{
  1520  					"test_field": {
  1521  						Type:     tfschema.TypeString,
  1522  						Required: true,
  1523  					},
  1524  					"int_key": {
  1525  						Type:     tfschema.TypeInt,
  1526  						Optional: true,
  1527  					},
  1528  					"float_key": {
  1529  						Type:     tfschema.TypeFloat,
  1530  						Optional: true,
  1531  					},
  1532  					"bool_key": {
  1533  						Type:     tfschema.TypeBool,
  1534  						Optional: true,
  1535  					},
  1536  					"list_of_objects_key": {
  1537  						Type:     tfschema.TypeList,
  1538  						Optional: true,
  1539  						Elem: &tfschema.Resource{
  1540  							Schema: map[string]*tfschema.Schema{
  1541  								"field1": {
  1542  									Type:     tfschema.TypeFloat,
  1543  									Optional: true,
  1544  								},
  1545  								"field2": {
  1546  									Type:     tfschema.TypeString,
  1547  									Optional: true,
  1548  								},
  1549  							},
  1550  						},
  1551  					},
  1552  				},
  1553  			},
  1554  			tfAttributes: map[string]string{
  1555  				"test_field":                   "resource-id",
  1556  				"int_key":                      "1",
  1557  				"float_key":                    "0.5",
  1558  				"bool_key":                     "false",
  1559  				"list_of_objects_key.#":        "2",
  1560  				"list_of_objects_key.0.field1": "0.5",
  1561  				"list_of_objects_key.0.field2": "strval1",
  1562  				"list_of_objects_key.1.field1": "0.7",
  1563  				"list_of_objects_key.1.field2": "strval2",
  1564  			},
  1565  			expectedSpec: map[string]interface{}{
  1566  				"resourceID": "resource-id",
  1567  				"intKey":     int64(1),
  1568  				"floatKey":   0.5,
  1569  				"boolKey":    false,
  1570  			},
  1571  			managedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1572  				"f:unrelated": emptyObject,
  1573  			}),
  1574  		},
  1575  	}
  1576  
  1577  	for _, tc := range tests {
  1578  		t.Run(tc.name, func(t *testing.T) {
  1579  			tc := tc
  1580  			t.Parallel()
  1581  			r := resourceSkeleton()
  1582  			if tc.metadataName != "" {
  1583  				r.SetName(tc.metadataName)
  1584  			}
  1585  			r.Spec = tc.prevSpec
  1586  			r.Status = tc.prevStatus
  1587  			r.TFResource = tc.tfResource
  1588  			r.ManagedFields = tc.managedFields
  1589  			if tc.rc != nil {
  1590  				r.ResourceConfig = *tc.rc
  1591  			}
  1592  			state := terraform.InstanceState{
  1593  				Attributes: tc.tfAttributes,
  1594  			}
  1595  			k8s.SetAnnotation(k8s.StateIntoSpecAnnotation, k8s.StateAbsentInSpec, r)
  1596  			spec, status := ResolveSpecAndStatusWithResourceID(r, &state)
  1597  			if got, want := spec, tc.expectedSpec; !reflect.DeepEqual(got, want) {
  1598  				t.Fatalf("got: %v, want: %v", got, want)
  1599  			}
  1600  			if got, want := status, tc.expectedStatus; !reflect.DeepEqual(got, want) {
  1601  				t.Fatalf("got: %v, want: %v", got, want)
  1602  			}
  1603  		})
  1604  	}
  1605  }
  1606  
  1607  func TestResolveSpecAndStatusWithResourceID(t *testing.T) {
  1608  	tests := []struct {
  1609  		name           string
  1610  		rc             *corekccv1alpha1.ResourceConfig
  1611  		metadataName   string
  1612  		prevSpec       map[string]interface{}
  1613  		prevStatus     map[string]interface{}
  1614  		tfResource     *tfschema.Resource
  1615  		tfAttributes   map[string]string
  1616  		expectedSpec   map[string]interface{}
  1617  		expectedStatus map[string]interface{}
  1618  	}{
  1619  		{
  1620  			name: "with existing user-specified resource ID",
  1621  			rc: &corekccv1alpha1.ResourceConfig{
  1622  				ResourceID: corekccv1alpha1.ResourceID{
  1623  					TargetField: "test_field",
  1624  				},
  1625  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1626  					Name: "test_field",
  1627  				},
  1628  			},
  1629  			prevSpec: map[string]interface{}{
  1630  				"resourceID": "resource-id",
  1631  			},
  1632  			prevStatus: map[string]interface{}{},
  1633  			tfResource: &tfschema.Resource{
  1634  				Schema: map[string]*tfschema.Schema{
  1635  					"test_field": {
  1636  						Type:     tfschema.TypeString,
  1637  						Required: true,
  1638  					},
  1639  				},
  1640  			},
  1641  			tfAttributes: map[string]string{
  1642  				"test_field": "resource-id",
  1643  			},
  1644  			expectedSpec: map[string]interface{}{
  1645  				"resourceID": "resource-id",
  1646  			},
  1647  			expectedStatus: nil,
  1648  		},
  1649  		{
  1650  			name: "with empty user-specified resource ID",
  1651  			rc: &corekccv1alpha1.ResourceConfig{
  1652  				ResourceID: corekccv1alpha1.ResourceID{
  1653  					TargetField: "test_field",
  1654  				},
  1655  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1656  					Name: "test_field",
  1657  				},
  1658  			},
  1659  			prevSpec: map[string]interface{}{
  1660  				"resourceID": "",
  1661  			},
  1662  			prevStatus: map[string]interface{}{},
  1663  			tfResource: &tfschema.Resource{
  1664  				Schema: map[string]*tfschema.Schema{
  1665  					"test_field": {
  1666  						Type:     tfschema.TypeString,
  1667  						Required: true,
  1668  					},
  1669  				},
  1670  			},
  1671  			tfAttributes: map[string]string{
  1672  				"test_field": "metadata-name-value",
  1673  			},
  1674  			expectedSpec: map[string]interface{}{
  1675  				"resourceID": "",
  1676  			},
  1677  			expectedStatus: nil,
  1678  		},
  1679  		{
  1680  			name: "with user-specified resource ID unset and metadata.name set",
  1681  			rc: &corekccv1alpha1.ResourceConfig{
  1682  				ResourceID: corekccv1alpha1.ResourceID{
  1683  					TargetField: "test_field",
  1684  				},
  1685  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1686  					Name: "test_field",
  1687  				},
  1688  			},
  1689  			metadataName: "default-id",
  1690  			prevSpec:     map[string]interface{}{},
  1691  			prevStatus:   map[string]interface{}{},
  1692  			tfResource: &tfschema.Resource{
  1693  				Schema: map[string]*tfschema.Schema{
  1694  					"test_field": {
  1695  						Type:     tfschema.TypeString,
  1696  						Required: true,
  1697  					},
  1698  				},
  1699  			},
  1700  			tfAttributes: map[string]string{
  1701  				"test_field": "metadata-name-value",
  1702  			},
  1703  			expectedSpec: map[string]interface{}{
  1704  				"resourceID": "default-id",
  1705  			},
  1706  			expectedStatus: nil,
  1707  		},
  1708  		{
  1709  			name: "specifying server-generated resource ID for the first time",
  1710  			rc: &corekccv1alpha1.ResourceConfig{
  1711  				ResourceID: corekccv1alpha1.ResourceID{
  1712  					TargetField: "test_field",
  1713  				},
  1714  				ServerGeneratedIDField: "test_field",
  1715  			},
  1716  			prevSpec:   map[string]interface{}{},
  1717  			prevStatus: map[string]interface{}{},
  1718  			tfResource: &tfschema.Resource{
  1719  				Schema: map[string]*tfschema.Schema{
  1720  					"test_field": {
  1721  						Type:     tfschema.TypeString,
  1722  						Computed: true,
  1723  					},
  1724  				},
  1725  			},
  1726  			tfAttributes: map[string]string{
  1727  				"test_field": "new-server-generated-id",
  1728  			},
  1729  			expectedSpec: map[string]interface{}{
  1730  				"resourceID": "new-server-generated-id",
  1731  			},
  1732  			expectedStatus: map[string]interface{}{
  1733  				"testField": "new-server-generated-id",
  1734  			},
  1735  		},
  1736  		{
  1737  			name: "specifying server-generated resource ID with a value " +
  1738  				"template for the first time",
  1739  			rc: &corekccv1alpha1.ResourceConfig{
  1740  				ResourceID: corekccv1alpha1.ResourceID{
  1741  					TargetField:   "test_field",
  1742  					ValueTemplate: "id/{{value}}",
  1743  				},
  1744  				ServerGeneratedIDField: "test_field",
  1745  			},
  1746  			prevSpec:   map[string]interface{}{},
  1747  			prevStatus: map[string]interface{}{},
  1748  			tfResource: &tfschema.Resource{
  1749  				Schema: map[string]*tfschema.Schema{
  1750  					"test_field": {
  1751  						Type:     tfschema.TypeString,
  1752  						Computed: true,
  1753  					},
  1754  				},
  1755  			},
  1756  			tfAttributes: map[string]string{
  1757  				"test_field": "id/id-with-value-template",
  1758  			},
  1759  			expectedSpec: map[string]interface{}{
  1760  				"resourceID": "id-with-value-template",
  1761  			},
  1762  			expectedStatus: map[string]interface{}{
  1763  				"testField": "id/id-with-value-template",
  1764  			},
  1765  		},
  1766  		{
  1767  			name: "specifying server-generated resource ID after it is " +
  1768  				"supported in resource config",
  1769  			rc: &corekccv1alpha1.ResourceConfig{
  1770  				ResourceID: corekccv1alpha1.ResourceID{
  1771  					TargetField: "test_field",
  1772  				},
  1773  				ServerGeneratedIDField: "test_field",
  1774  			},
  1775  			prevSpec: map[string]interface{}{},
  1776  			prevStatus: map[string]interface{}{
  1777  				"testField": "existing-server-generated-id",
  1778  			},
  1779  			tfResource: &tfschema.Resource{
  1780  				Schema: map[string]*tfschema.Schema{
  1781  					"test_field": {
  1782  						Type:     tfschema.TypeString,
  1783  						Computed: true,
  1784  					},
  1785  				},
  1786  			},
  1787  			tfAttributes: map[string]string{
  1788  				"test_field": "existing-server-generated-id",
  1789  			},
  1790  			expectedSpec: map[string]interface{}{
  1791  				"resourceID": "existing-server-generated-id",
  1792  			},
  1793  			expectedStatus: map[string]interface{}{
  1794  				"testField": "existing-server-generated-id",
  1795  			},
  1796  		},
  1797  		{
  1798  			name: "with server-generated resource ID already set",
  1799  			rc: &corekccv1alpha1.ResourceConfig{
  1800  				ResourceID: corekccv1alpha1.ResourceID{
  1801  					TargetField: "test_field",
  1802  				},
  1803  				ServerGeneratedIDField: "test_field",
  1804  			},
  1805  			prevSpec: map[string]interface{}{
  1806  				"resourceID": "existing-server-generated-id",
  1807  			},
  1808  			prevStatus: map[string]interface{}{},
  1809  			tfResource: &tfschema.Resource{
  1810  				Schema: map[string]*tfschema.Schema{
  1811  					"test_field": {
  1812  						Type:     tfschema.TypeString,
  1813  						Computed: true,
  1814  					},
  1815  				},
  1816  			},
  1817  			tfAttributes: map[string]string{
  1818  				"test_field": "existing-server-generated-id",
  1819  			},
  1820  			expectedSpec: map[string]interface{}{
  1821  				"resourceID": "existing-server-generated-id",
  1822  			},
  1823  			expectedStatus: map[string]interface{}{
  1824  				"testField": "existing-server-generated-id",
  1825  			},
  1826  		},
  1827  		{
  1828  			name: "with resource ID not supported",
  1829  			rc:   &corekccv1alpha1.ResourceConfig{},
  1830  			prevSpec: map[string]interface{}{
  1831  				"testField": "testValue",
  1832  			},
  1833  			prevStatus: map[string]interface{}{},
  1834  			tfResource: &tfschema.Resource{
  1835  				Schema: map[string]*tfschema.Schema{
  1836  					"test_field": {
  1837  						Type:     tfschema.TypeString,
  1838  						Optional: true,
  1839  					},
  1840  				},
  1841  			},
  1842  			tfAttributes: map[string]string{
  1843  				"test_field": "testValue",
  1844  			},
  1845  			expectedSpec: map[string]interface{}{
  1846  				"testField": "testValue",
  1847  			},
  1848  			expectedStatus: nil,
  1849  		},
  1850  	}
  1851  
  1852  	for _, tc := range tests {
  1853  		t.Run(tc.name, func(t *testing.T) {
  1854  			tc := tc
  1855  			t.Parallel()
  1856  			r := resourceSkeleton()
  1857  			if tc.metadataName != "" {
  1858  				r.SetName(tc.metadataName)
  1859  			}
  1860  			r.Spec = tc.prevSpec
  1861  			r.Status = tc.prevStatus
  1862  			r.TFResource = tc.tfResource
  1863  			if tc.rc != nil {
  1864  				r.ResourceConfig = *tc.rc
  1865  			}
  1866  			state := terraform.InstanceState{
  1867  				Attributes: tc.tfAttributes,
  1868  			}
  1869  			spec, status := ResolveSpecAndStatusWithResourceID(r, &state)
  1870  			if got, want := spec, tc.expectedSpec; !reflect.DeepEqual(got, want) {
  1871  				t.Fatalf("got: %v, want: %v", got, want)
  1872  			}
  1873  			if got, want := status, tc.expectedStatus; !reflect.DeepEqual(got, want) {
  1874  				t.Fatalf("got: %v, want: %v", got, want)
  1875  			}
  1876  		})
  1877  	}
  1878  }
  1879  
  1880  func TestResolveSpecAndStatusWithFieldRenaming(t *testing.T) {
  1881  	tests := []struct {
  1882  		name           string
  1883  		rc             *corekccv1alpha1.ResourceConfig
  1884  		tfResource     *tfschema.Resource
  1885  		tfAttributes   map[string]string
  1886  		expectedSpec   map[string]interface{}
  1887  		expectedStatus map[string]interface{}
  1888  	}{
  1889  		{
  1890  			name: "status fields that collide with reserved status fields are renamed",
  1891  			rc: &corekccv1alpha1.ResourceConfig{
  1892  				Name: "test-tf-resource-name",
  1893  			},
  1894  			tfResource: &tfschema.Resource{
  1895  				Schema: map[string]*tfschema.Schema{
  1896  					"generation": { // computed field maps to KRM status field
  1897  						Type:     tfschema.TypeString,
  1898  						Computed: true,
  1899  					},
  1900  				},
  1901  			},
  1902  			tfAttributes: map[string]string{
  1903  				"generation": "testValue1",
  1904  			},
  1905  			expectedStatus: map[string]interface{}{
  1906  				"resourceGeneration": "testValue1",
  1907  			},
  1908  		},
  1909  		{
  1910  			name: "spec fields that collide with reserved status fields are not renamed",
  1911  			rc: &corekccv1alpha1.ResourceConfig{
  1912  				Name: "test-tf-resource-name",
  1913  			},
  1914  			tfResource: &tfschema.Resource{
  1915  				Schema: map[string]*tfschema.Schema{
  1916  					"generation": {
  1917  						Type:     tfschema.TypeString,
  1918  						Optional: true,
  1919  					},
  1920  				},
  1921  			},
  1922  			tfAttributes: map[string]string{
  1923  				"generation": "testValue1",
  1924  			},
  1925  			expectedSpec: map[string]interface{}{
  1926  				"generation": "testValue1",
  1927  			},
  1928  		},
  1929  		{
  1930  			name: "status fields that collide with reserved status fields are not renamed if resource is in the exclude list",
  1931  			rc: &corekccv1alpha1.ResourceConfig{
  1932  				Name: "google_storage_default_object_access_control", // this TF resource is in the exclude list
  1933  			},
  1934  			tfResource: &tfschema.Resource{
  1935  				Schema: map[string]*tfschema.Schema{
  1936  					"generation": {
  1937  						Type:     tfschema.TypeString,
  1938  						Computed: true,
  1939  					},
  1940  				},
  1941  			},
  1942  			tfAttributes: map[string]string{
  1943  				"generation": "testValue1",
  1944  			},
  1945  			expectedStatus: map[string]interface{}{
  1946  				"generation": "testValue1",
  1947  			},
  1948  		},
  1949  	}
  1950  
  1951  	for _, tc := range tests {
  1952  		t.Run(tc.name, func(t *testing.T) {
  1953  			tc := tc
  1954  			t.Parallel()
  1955  			r := resourceSkeleton()
  1956  			r.TFResource = tc.tfResource
  1957  			if tc.rc != nil {
  1958  				r.ResourceConfig = *tc.rc
  1959  			}
  1960  			state := terraform.InstanceState{
  1961  				Attributes: tc.tfAttributes,
  1962  			}
  1963  			spec, status := ResolveSpecAndStatus(r, &state)
  1964  			t.Logf("spec = %v\nstatus = %v\n", spec, status)
  1965  			if got, want := spec, tc.expectedSpec; !reflect.DeepEqual(got, want) {
  1966  				t.Fatalf("got: %v, want: %v", got, want)
  1967  			}
  1968  			if got, want := status, tc.expectedStatus; !reflect.DeepEqual(got, want) {
  1969  				t.Fatalf("got: %v, want: %v", got, want)
  1970  			}
  1971  		})
  1972  	}
  1973  }
  1974  
  1975  func TestResolveSpecAndStatusWithResourceIDPanic(t *testing.T) {
  1976  	tests := []struct {
  1977  		name         string
  1978  		rc           *corekccv1alpha1.ResourceConfig
  1979  		metadataName string
  1980  		prevSpec     map[string]interface{}
  1981  		prevStatus   map[string]interface{}
  1982  		tfResource   *tfschema.Resource
  1983  		tfAttributes map[string]string
  1984  	}{
  1985  		{
  1986  			name: "with user-specified resource ID unset and metadata.name unset",
  1987  			rc: &corekccv1alpha1.ResourceConfig{
  1988  				ResourceID: corekccv1alpha1.ResourceID{
  1989  					TargetField: "test_field",
  1990  				},
  1991  				MetadataMapping: corekccv1alpha1.MetadataMapping{
  1992  					Name: "test_field",
  1993  				},
  1994  			},
  1995  			prevSpec:   map[string]interface{}{},
  1996  			prevStatus: map[string]interface{}{},
  1997  			tfResource: &tfschema.Resource{
  1998  				Schema: map[string]*tfschema.Schema{
  1999  					"test_field": {
  2000  						Type:     tfschema.TypeString,
  2001  						Required: true,
  2002  					},
  2003  				},
  2004  			},
  2005  			tfAttributes: map[string]string{
  2006  				"test_field": "metadata-name-value",
  2007  			},
  2008  		},
  2009  		{
  2010  			name: "with server-generated resource ID not found",
  2011  			rc: &corekccv1alpha1.ResourceConfig{
  2012  				ResourceID: corekccv1alpha1.ResourceID{
  2013  					TargetField: "test_field",
  2014  				},
  2015  				ServerGeneratedIDField: "test_field",
  2016  			},
  2017  			prevSpec:   map[string]interface{}{},
  2018  			prevStatus: map[string]interface{}{},
  2019  			tfResource: &tfschema.Resource{
  2020  				Schema: map[string]*tfschema.Schema{
  2021  					"test_field": {
  2022  						Type:     tfschema.TypeString,
  2023  						Computed: true,
  2024  					},
  2025  				},
  2026  			},
  2027  			tfAttributes: map[string]string{
  2028  				"different_field": "new-server-generated-id",
  2029  			},
  2030  		},
  2031  	}
  2032  
  2033  	for _, tc := range tests {
  2034  		t.Run(tc.name, func(t *testing.T) {
  2035  			tc := tc
  2036  			t.Parallel()
  2037  			r := resourceSkeleton()
  2038  			r.Spec = tc.prevSpec
  2039  			r.Status = tc.prevStatus
  2040  			r.TFResource = tc.tfResource
  2041  			if tc.rc != nil {
  2042  				r.ResourceConfig = *tc.rc
  2043  			}
  2044  			state := terraform.InstanceState{
  2045  				Attributes: tc.tfAttributes,
  2046  			}
  2047  
  2048  			assertGetSpecAndStatusFromStateWithResourceIDPanic(t, r, &state)
  2049  		})
  2050  	}
  2051  }
  2052  
  2053  func assertGetSpecAndStatusFromStateWithResourceIDPanic(t *testing.T, resource *Resource, state *terraform.InstanceState) {
  2054  	defer func() {
  2055  		if r := recover(); r == nil {
  2056  			t.Fatalf("GetSpecAndStatusFromState should have panicked")
  2057  		}
  2058  	}()
  2059  	ResolveSpecAndStatusWithResourceID(resource, state)
  2060  }
  2061  

View as plain text