...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/krmtotf/resource_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  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
    22  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gcp"
    23  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
    24  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test"
    25  	testcontroller "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/controller"
    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  	corev1 "k8s.io/api/core/v1"
    30  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    31  	k8sschema "k8s.io/apimachinery/pkg/runtime/schema"
    32  )
    33  
    34  func TestResource_GetImportID(t *testing.T) {
    35  	tests := []struct {
    36  		name                 string
    37  		rc                   *v1alpha1.ResourceConfig
    38  		metadataName         string
    39  		spec                 map[string]interface{}
    40  		referencedResources  []*unstructured.Unstructured
    41  		expected             string
    42  		assertGotExpectedErr func(t *testing.T, err error)
    43  	}{
    44  		{
    45  			name: "with fields from spec and status",
    46  			rc: &v1alpha1.ResourceConfig{
    47  				IDTemplate: "{{spec_field}}/{{status_field}}",
    48  			},
    49  			expected: "abc123/foobar",
    50  		},
    51  		{
    52  			name: "with field from container",
    53  			rc: &v1alpha1.ResourceConfig{
    54  				IDTemplate: "project/{{project}}",
    55  				Containers: []v1alpha1.Container{
    56  					{
    57  						Type:    v1alpha1.ContainerTypeProject,
    58  						TFField: "project",
    59  					},
    60  				},
    61  			},
    62  			expected: "project/my-project-1",
    63  		},
    64  		{
    65  			name: "with reference's ID",
    66  			rc: &v1alpha1.ResourceConfig{
    67  				IDTemplate: "bar/{{bar_ref}}",
    68  				ResourceReferences: []v1alpha1.ReferenceConfig{
    69  					{
    70  						TFField: "bar_ref",
    71  						TypeConfig: v1alpha1.TypeConfig{
    72  							Key: "barRef",
    73  							GVK: k8sschema.GroupVersionKind{
    74  								Group:   "test1.cnrm.cloud.google.com",
    75  								Version: "v1alpha1",
    76  								Kind:    "Test1Bar",
    77  							},
    78  						},
    79  					},
    80  				},
    81  			},
    82  			spec: map[string]interface{}{
    83  				"barRef": map[string]interface{}{
    84  					"name": "my-ref1",
    85  				},
    86  			},
    87  			referencedResources: []*unstructured.Unstructured{
    88  				test.NewBarUnstructured("my-ref1", "", corev1.ConditionTrue),
    89  			},
    90  			expected: "bar/my-ref1",
    91  		},
    92  		{
    93  			name: "with reference's ID, but referenced resource is not found",
    94  			rc: &v1alpha1.ResourceConfig{
    95  				IDTemplate: "bar/{{bar_ref}}",
    96  				ResourceReferences: []v1alpha1.ReferenceConfig{
    97  					{
    98  						TFField: "bar_ref",
    99  						TypeConfig: v1alpha1.TypeConfig{
   100  							Key: "barRef",
   101  							GVK: k8sschema.GroupVersionKind{
   102  								Group:   "test1.cnrm.cloud.google.com",
   103  								Version: "v1alpha1",
   104  								Kind:    "Test1Bar",
   105  							},
   106  						},
   107  					},
   108  				},
   109  			},
   110  			spec: map[string]interface{}{
   111  				"barRef": map[string]interface{}{
   112  					"name": "my-ref1",
   113  				},
   114  			},
   115  			assertGotExpectedErr: assertIsReferenceNotFoundError,
   116  		},
   117  		{
   118  			name: "with reference's ID, but referenced resource is not ready",
   119  			rc: &v1alpha1.ResourceConfig{
   120  				IDTemplate: "bar/{{bar_ref}}",
   121  				ResourceReferences: []v1alpha1.ReferenceConfig{
   122  					{
   123  						TFField: "bar_ref",
   124  						TypeConfig: v1alpha1.TypeConfig{
   125  							Key: "barRef",
   126  							GVK: k8sschema.GroupVersionKind{
   127  								Group:   "test1.cnrm.cloud.google.com",
   128  								Version: "v1alpha1",
   129  								Kind:    "Test1Bar",
   130  							},
   131  						},
   132  					},
   133  				},
   134  			},
   135  			spec: map[string]interface{}{
   136  				"barRef": map[string]interface{}{
   137  					"name": "my-ref1",
   138  				},
   139  			},
   140  			referencedResources: []*unstructured.Unstructured{
   141  				test.NewBarUnstructured("my-ref1", "", corev1.ConditionFalse),
   142  			},
   143  			assertGotExpectedErr: assertIsReferenceNotReadyError,
   144  		},
   145  		{
   146  			name: "with reference's status field",
   147  			rc: &v1alpha1.ResourceConfig{
   148  				IDTemplate: "bar/{{bar_ref}}",
   149  				ResourceReferences: []v1alpha1.ReferenceConfig{
   150  					{
   151  						TFField: "bar_ref",
   152  						TypeConfig: v1alpha1.TypeConfig{
   153  							Key: "barRef",
   154  							GVK: k8sschema.GroupVersionKind{
   155  								Group:   "test1.cnrm.cloud.google.com",
   156  								Version: "v1alpha1",
   157  								Kind:    "Test1Bar",
   158  							},
   159  							TargetField: "status_field",
   160  						},
   161  					},
   162  				},
   163  			},
   164  			spec: map[string]interface{}{
   165  				"barRef": map[string]interface{}{
   166  					"name": "my-ref1",
   167  				},
   168  			},
   169  			referencedResources: []*unstructured.Unstructured{
   170  				test.NewBarUnstructured("my-ref1", "", corev1.ConditionTrue),
   171  			},
   172  			expected: "bar/foobar",
   173  		},
   174  		{
   175  			name: "with reference's status field, but referenced resource is not found",
   176  			rc: &v1alpha1.ResourceConfig{
   177  				IDTemplate: "bar/{{bar_ref}}",
   178  				ResourceReferences: []v1alpha1.ReferenceConfig{
   179  					{
   180  						TFField: "bar_ref",
   181  						TypeConfig: v1alpha1.TypeConfig{
   182  							Key: "barRef",
   183  							GVK: k8sschema.GroupVersionKind{
   184  								Group:   "test1.cnrm.cloud.google.com",
   185  								Version: "v1alpha1",
   186  								Kind:    "Test1Bar",
   187  							},
   188  							TargetField: "status_field",
   189  						},
   190  					},
   191  				},
   192  			},
   193  			spec: map[string]interface{}{
   194  				"barRef": map[string]interface{}{
   195  					"name": "my-ref1",
   196  				},
   197  			},
   198  			assertGotExpectedErr: assertIsReferenceNotFoundError,
   199  		},
   200  		{
   201  			name: "with reference's status field, but referenced resource is not ready",
   202  			rc: &v1alpha1.ResourceConfig{
   203  				IDTemplate: "bar/{{bar_ref}}",
   204  				ResourceReferences: []v1alpha1.ReferenceConfig{
   205  					{
   206  						TFField: "bar_ref",
   207  						TypeConfig: v1alpha1.TypeConfig{
   208  							Key: "barRef",
   209  							GVK: k8sschema.GroupVersionKind{
   210  								Group:   "test1.cnrm.cloud.google.com",
   211  								Version: "v1alpha1",
   212  								Kind:    "Test1Bar",
   213  							},
   214  							TargetField: "status_field",
   215  						},
   216  					},
   217  				},
   218  			},
   219  			spec: map[string]interface{}{
   220  				"barRef": map[string]interface{}{
   221  					"name": "my-ref1",
   222  				},
   223  			},
   224  			referencedResources: []*unstructured.Unstructured{
   225  				test.NewBarUnstructured("my-ref1", "", corev1.ConditionFalse),
   226  			},
   227  			assertGotExpectedErr: assertIsReferenceNotReadyError,
   228  		},
   229  		{
   230  			name: "with reference's spec field",
   231  			rc: &v1alpha1.ResourceConfig{
   232  				IDTemplate: "bar/{{bar_ref}}",
   233  				ResourceReferences: []v1alpha1.ReferenceConfig{
   234  					{
   235  						TFField: "bar_ref",
   236  						TypeConfig: v1alpha1.TypeConfig{
   237  							Key: "barRef",
   238  							GVK: k8sschema.GroupVersionKind{
   239  								Group:   "test1.cnrm.cloud.google.com",
   240  								Version: "v1alpha1",
   241  								Kind:    "Test1Bar",
   242  							},
   243  							TargetField: "spec_field",
   244  						},
   245  					},
   246  				},
   247  			},
   248  			spec: map[string]interface{}{
   249  				"barRef": map[string]interface{}{
   250  					"name": "my-ref1",
   251  				},
   252  			},
   253  			referencedResources: []*unstructured.Unstructured{
   254  				test.NewBarUnstructured("my-ref1", "", corev1.ConditionTrue),
   255  			},
   256  			expected: "bar/abc123",
   257  		},
   258  		{
   259  			name: "with reference's spec field, but referenced resource is not found",
   260  			rc: &v1alpha1.ResourceConfig{
   261  				IDTemplate: "bar/{{bar_ref}}",
   262  				ResourceReferences: []v1alpha1.ReferenceConfig{
   263  					{
   264  						TFField: "bar_ref",
   265  						TypeConfig: v1alpha1.TypeConfig{
   266  							Key: "barRef",
   267  							GVK: k8sschema.GroupVersionKind{
   268  								Group:   "test1.cnrm.cloud.google.com",
   269  								Version: "v1alpha1",
   270  								Kind:    "Test1Bar",
   271  							},
   272  							TargetField: "spec_field",
   273  						},
   274  					},
   275  				},
   276  			},
   277  			spec: map[string]interface{}{
   278  				"barRef": map[string]interface{}{
   279  					"name": "my-ref1",
   280  				},
   281  			},
   282  			assertGotExpectedErr: assertIsReferenceNotFoundError,
   283  		},
   284  		{
   285  			name: "with reference's spec field, but referenced resource is not ready",
   286  			rc: &v1alpha1.ResourceConfig{
   287  				IDTemplate: "bar/{{bar_ref}}",
   288  				ResourceReferences: []v1alpha1.ReferenceConfig{
   289  					{
   290  						TFField: "bar_ref",
   291  						TypeConfig: v1alpha1.TypeConfig{
   292  							Key: "barRef",
   293  							GVK: k8sschema.GroupVersionKind{
   294  								Group:   "test1.cnrm.cloud.google.com",
   295  								Version: "v1alpha1",
   296  								Kind:    "Test1Bar",
   297  							},
   298  							TargetField: "spec_field",
   299  						},
   300  					},
   301  				},
   302  			},
   303  			spec: map[string]interface{}{
   304  				"barRef": map[string]interface{}{
   305  					"name": "my-ref1",
   306  				},
   307  			},
   308  			referencedResources: []*unstructured.Unstructured{
   309  				test.NewBarUnstructured("my-ref1", "", corev1.ConditionFalse),
   310  			},
   311  			assertGotExpectedErr: assertIsReferenceNotReadyError,
   312  		},
   313  		{
   314  			name: "server-generated ID from status",
   315  			rc: &v1alpha1.ResourceConfig{
   316  				ServerGeneratedIDField: "status_field",
   317  			},
   318  			expected: "foobar",
   319  		},
   320  		{
   321  			name: "server-generated ID from status, but server-generated ID is not found",
   322  			rc: &v1alpha1.ResourceConfig{
   323  				ServerGeneratedIDField: "non_existent_status_field",
   324  			},
   325  			assertGotExpectedErr: assertIsServerGeneratedIDNotFoundError,
   326  		},
   327  		{
   328  			name: "server-generated ID from status with template",
   329  			rc: &v1alpha1.ResourceConfig{
   330  				IDTemplate:             "id/{{status_field}}",
   331  				ServerGeneratedIDField: "status_field",
   332  			},
   333  			expected: "id/foobar",
   334  		},
   335  		{
   336  			name: "server-generated ID from status with template, but server-generated ID is not found",
   337  			rc: &v1alpha1.ResourceConfig{
   338  				IDTemplate:             "id/{{non_existent_status_field}}",
   339  				ServerGeneratedIDField: "non_existent_status_field",
   340  			},
   341  			assertGotExpectedErr: assertIsServerGeneratedIDNotFoundError,
   342  		},
   343  		{
   344  			name: "no template implies {{project}}/{{resource_id}}",
   345  			rc: &v1alpha1.ResourceConfig{
   346  				MetadataMapping: v1alpha1.MetadataMapping{
   347  					Name: "resource_id",
   348  				},
   349  				Containers: []v1alpha1.Container{
   350  					{
   351  						Type:    v1alpha1.ContainerTypeProject,
   352  						TFField: "project",
   353  					},
   354  				},
   355  			},
   356  			metadataName: "my-resource",
   357  			expected:     "my-project-1/my-resource",
   358  		},
   359  		{
   360  			name: "regional resources map location to region field",
   361  			rc: &v1alpha1.ResourceConfig{
   362  				Locationality: gcp.Regional,
   363  				IDTemplate:    "regions/{{region}}",
   364  			},
   365  			expected: "regions/test-location",
   366  		},
   367  		{
   368  			name: "zonal resources map location to zone field",
   369  			rc: &v1alpha1.ResourceConfig{
   370  				Locationality: gcp.Zonal,
   371  				IDTemplate:    "zones/{{zone}}",
   372  			},
   373  			expected: "zones/test-location",
   374  		},
   375  		{
   376  			name: "optional spec field should resolve when specified",
   377  			rc: &v1alpha1.ResourceConfig{
   378  				IDTemplate: "id/{{barField?}}",
   379  			},
   380  			spec: map[string]interface{}{
   381  				"barField": "bar-value",
   382  			},
   383  			expected: "id/bar-value",
   384  		},
   385  		{
   386  			name: "optional spec field should not resolve when not specified",
   387  			rc: &v1alpha1.ResourceConfig{
   388  				IDTemplate: "id/{{barField?}}/another-field",
   389  			},
   390  			spec:     map[string]interface{}{},
   391  			expected: "id//another-field",
   392  		},
   393  		{
   394  			name: "'or' should pick first value when second is not present",
   395  			rc: &v1alpha1.ResourceConfig{
   396  				IDTemplate: "id/[text-with-{{field1}}|{{field2}}]/another-string",
   397  			},
   398  			spec: map[string]interface{}{
   399  				"field1": "field1-value",
   400  			},
   401  			expected: "id/text-with-field1-value/another-string",
   402  		},
   403  		{
   404  			name: "'or' should pick second value when first is not present",
   405  			rc: &v1alpha1.ResourceConfig{
   406  				IDTemplate: "id/[text-with-{{field1}}|{{field2}}]/another-string",
   407  			},
   408  			spec: map[string]interface{}{
   409  				"field2": "field2-value",
   410  			},
   411  			expected: "id/field2-value/another-string",
   412  		},
   413  		{
   414  			name: "'or' should pick first value when first and second are present",
   415  			rc: &v1alpha1.ResourceConfig{
   416  				IDTemplate: "id/[text-with-{{field1}}|{{field2}}]/another-string",
   417  			},
   418  			spec: map[string]interface{}{
   419  				"field1": "field1-value",
   420  				"field2": "field2-value",
   421  			},
   422  			expected: "id/text-with-field1-value/another-string",
   423  		},
   424  		{
   425  			name: "server-generated resourceID supported",
   426  			rc: &v1alpha1.ResourceConfig{
   427  				ServerGeneratedIDField: "resource_id_field_in_status",
   428  				ResourceID: v1alpha1.ResourceID{
   429  					TargetField: "resource_id_field_in_status",
   430  				},
   431  			},
   432  			spec: map[string]interface{}{
   433  				"resourceID": "id-in-spec",
   434  			},
   435  			expected: "id-in-spec",
   436  		},
   437  		{
   438  			name: "server-generated resourceID supported, with a value template",
   439  			rc: &v1alpha1.ResourceConfig{
   440  				ServerGeneratedIDField: "resource_id_field_in_status",
   441  				ResourceID: v1alpha1.ResourceID{
   442  					TargetField:   "resource_id_field_in_status",
   443  					ValueTemplate: "values/{{value}}",
   444  				},
   445  			},
   446  			spec: map[string]interface{}{
   447  				"resourceID": "id-in-spec",
   448  			},
   449  			expected: "values/id-in-spec",
   450  		},
   451  		{
   452  			name: "server-generated resourceID supported, with an ID template",
   453  			rc: &v1alpha1.ResourceConfig{
   454  				IDTemplate:             "id/{{resource_id_field_in_status}}",
   455  				ServerGeneratedIDField: "resource_id_field_in_status",
   456  				ResourceID: v1alpha1.ResourceID{
   457  					TargetField: "resource_id_field_in_status",
   458  				},
   459  			},
   460  			spec: map[string]interface{}{
   461  				"resourceID": "id-in-spec",
   462  			},
   463  			expected: "id/id-in-spec",
   464  		},
   465  		{
   466  			name: "server-generated resourceID supported, with a value " +
   467  				"template and an ID template",
   468  			rc: &v1alpha1.ResourceConfig{
   469  				IDTemplate:             "parents/parent/{{resource_id_field_in_status}}",
   470  				ServerGeneratedIDField: "resource_id_field_in_status",
   471  				ResourceID: v1alpha1.ResourceID{
   472  					TargetField:   "resource_id_field_in_status",
   473  					ValueTemplate: "groups/group/id/{{value}}",
   474  				},
   475  			},
   476  			spec: map[string]interface{}{
   477  				"resourceID": "id-in-spec",
   478  			},
   479  			expected: "parents/parent/groups/group/id/id-in-spec",
   480  		},
   481  		{
   482  			name: "server-generated resourceID supported, with a value " +
   483  				"template that contains the container",
   484  			rc: &v1alpha1.ResourceConfig{
   485  				ServerGeneratedIDField: "resource_id_field_in_status",
   486  				ResourceID: v1alpha1.ResourceID{
   487  					TargetField:   "resource_id_field_in_status",
   488  					ValueTemplate: "projects/{{project}}/id/{{value}}",
   489  				},
   490  				Containers: []v1alpha1.Container{
   491  					{
   492  						Type:    v1alpha1.ContainerTypeProject,
   493  						TFField: "project",
   494  					},
   495  				},
   496  			},
   497  			spec: map[string]interface{}{
   498  				"resourceID": "id-in-spec",
   499  			},
   500  			expected: "projects/my-project-1/id/id-in-spec",
   501  		},
   502  		{
   503  			name: "server-generated resourceID supported, with a value " +
   504  				"template that contains a spec field",
   505  			rc: &v1alpha1.ResourceConfig{
   506  				ServerGeneratedIDField: "resource_id_field_in_status",
   507  				ResourceID: v1alpha1.ResourceID{
   508  					TargetField:   "resource_id_field_in_status",
   509  					ValueTemplate: "groups/{{spec_field}}/id/{{value}}",
   510  				},
   511  			},
   512  			spec: map[string]interface{}{
   513  				"resourceID": "id-in-spec",
   514  				"specField":  "specTestValue",
   515  			},
   516  			expected: "groups/specTestValue/id/id-in-spec",
   517  		},
   518  		{
   519  			name: "server-generated resourceID supported, with a value " +
   520  				"template that contains a status field",
   521  			rc: &v1alpha1.ResourceConfig{
   522  				ServerGeneratedIDField: "resource_id_field_in_status",
   523  				ResourceID: v1alpha1.ResourceID{
   524  					TargetField:   "resource_id_field_in_status",
   525  					ValueTemplate: "groups/{{status_field}}/id/{{value}}",
   526  				},
   527  			},
   528  			spec: map[string]interface{}{
   529  				"resourceID": "id-in-spec",
   530  			},
   531  			expected: "groups/foobar/id/id-in-spec",
   532  		},
   533  		{
   534  			name: "server-generated resourceID supported, with a value " +
   535  				"template containing a reference field",
   536  			rc: &v1alpha1.ResourceConfig{
   537  				ServerGeneratedIDField: "resource_id_field_in_status",
   538  				ResourceID: v1alpha1.ResourceID{
   539  					TargetField:   "resource_id_field_in_status",
   540  					ValueTemplate: "bar/{{bar_name}}/id/{{value}}",
   541  				},
   542  				ResourceReferences: []v1alpha1.ReferenceConfig{
   543  					{
   544  						TFField: "bar_name",
   545  						TypeConfig: v1alpha1.TypeConfig{
   546  							Key: "barRef",
   547  							GVK: k8sschema.GroupVersionKind{
   548  								Group:   "test1.cnrm.cloud.google.com",
   549  								Version: "v1alpha1",
   550  								Kind:    "Test1Bar",
   551  							},
   552  							TargetField: "spec_field",
   553  							Parent:      true,
   554  						},
   555  					},
   556  				},
   557  			},
   558  			spec: map[string]interface{}{
   559  				"resourceID": "id-in-spec",
   560  				"barRef": map[string]interface{}{
   561  					"name": "my-bar-ref",
   562  				},
   563  			},
   564  			referencedResources: []*unstructured.Unstructured{
   565  				test.NewBarUnstructured("my-bar-ref", "", corev1.ConditionTrue),
   566  			},
   567  			expected: "bar/abc123/id/id-in-spec",
   568  		},
   569  		{
   570  			name: "server-generated resourceID supported, with spec.resourceID " +
   571  				"unspecified but serverGeneratedIDField in status specified",
   572  			rc: &v1alpha1.ResourceConfig{
   573  				ServerGeneratedIDField: "status_field",
   574  				ResourceID: v1alpha1.ResourceID{
   575  					TargetField: "status_field",
   576  				},
   577  			},
   578  			expected: "foobar",
   579  		},
   580  		{
   581  			name: "server-generated resourceID supported, with both " +
   582  				"spec.resourceID and serverGeneratedIDField in status unspecified",
   583  			rc: &v1alpha1.ResourceConfig{
   584  				ServerGeneratedIDField: "non_existent_resource_id",
   585  				ResourceID: v1alpha1.ResourceID{
   586  					TargetField: "non_existent_resource_id",
   587  				},
   588  			},
   589  			assertGotExpectedErr: assertIsServerGeneratedIDNotFoundError,
   590  		},
   591  		{
   592  			name: "user-specified resourceID supported",
   593  			rc: &v1alpha1.ResourceConfig{
   594  				IDTemplate: "id/{{resource_id}}",
   595  				MetadataMapping: v1alpha1.MetadataMapping{
   596  					Name: "resource_id",
   597  				},
   598  				ResourceID: v1alpha1.ResourceID{
   599  					TargetField: "resource_id",
   600  				},
   601  			},
   602  			spec: map[string]interface{}{
   603  				"resourceID": "user-specified-id",
   604  			},
   605  			expected: "id/user-specified-id",
   606  		},
   607  		{
   608  			name: "user-specified resourceID supported, with spec.resourceID " +
   609  				"unspecified but metadata.name specified",
   610  			rc: &v1alpha1.ResourceConfig{
   611  				IDTemplate: "{{resource_id}}",
   612  				MetadataMapping: v1alpha1.MetadataMapping{
   613  					Name: "resource_id",
   614  				},
   615  				ResourceID: v1alpha1.ResourceID{
   616  					TargetField: "resource_id",
   617  				},
   618  			},
   619  			metadataName: "default-id",
   620  			expected:     "default-id",
   621  		},
   622  		{
   623  			name: "user-specified resourceID supported, with both " +
   624  				"spec.resourceID and metadata.name unspecified",
   625  			rc: &v1alpha1.ResourceConfig{
   626  				IDTemplate: "id/{{resource_id}}",
   627  				MetadataMapping: v1alpha1.MetadataMapping{
   628  					Name: "resource_id",
   629  				},
   630  				ResourceID: v1alpha1.ResourceID{
   631  					TargetField: "resource_id",
   632  				},
   633  			},
   634  			assertGotExpectedErr: hasError,
   635  		},
   636  	}
   637  	smLoader := testservicemappingloader.NewForUnitTest(t)
   638  	for _, tc := range tests {
   639  		tc := tc
   640  		t.Run(tc.name, func(t *testing.T) {
   641  			t.Parallel()
   642  			testId := testvariable.NewUniqueId()
   643  			c := mgr.GetClient()
   644  			r := resourceSkeleton()
   645  			r.ResourceConfig = *tc.rc
   646  			if tc.metadataName != "" {
   647  				r.SetName(tc.metadataName)
   648  			}
   649  			r.SetNamespace(testId)
   650  			testcontroller.EnsureNamespaceExistsT(t, c, testId)
   651  			bar := test.NewBarUnstructured("my-resource", testId, corev1.ConditionTrue)
   652  			r.SetAnnotations(bar.GetAnnotations())
   653  			r.Spec = tc.spec
   654  			if r.Spec == nil {
   655  				r.Spec = bar.Object["spec"].(map[string]interface{})
   656  			}
   657  			r.Status = bar.Object["status"].(map[string]interface{})
   658  			for _, obj := range tc.referencedResources {
   659  				obj.SetNamespace(testId)
   660  			}
   661  			test.EnsureObjectsExist(t, tc.referencedResources, c)
   662  			actual, err := r.GetImportID(c, smLoader)
   663  			if tc.assertGotExpectedErr != nil {
   664  				tc.assertGotExpectedErr(t, err)
   665  			} else if err != nil {
   666  				t.Fatalf("error getting import ID: %v", err)
   667  			}
   668  			if tc.expected != actual {
   669  				t.Fatalf("expected: %v, actual %v", tc.expected, actual)
   670  			}
   671  		})
   672  	}
   673  }
   674  
   675  func assertIsReferenceNotFoundError(t *testing.T, err error) {
   676  	if _, ok := k8s.AsReferenceNotFoundError(err); !ok {
   677  		t.Fatalf("expected error that can be unwrapped as '%v', but got: '%v'", reflect.TypeOf(&k8s.ReferenceNotFoundError{}), err)
   678  	}
   679  }
   680  
   681  func assertIsReferenceNotReadyError(t *testing.T, err error) {
   682  	if _, ok := k8s.AsReferenceNotReadyError(err); !ok {
   683  		t.Fatalf("expected error that can be unwrapped as '%v', but got: '%v'", reflect.TypeOf(&k8s.ReferenceNotReadyError{}), err)
   684  	}
   685  }
   686  
   687  func assertIsServerGeneratedIDNotFoundError(t *testing.T, err error) {
   688  	if _, ok := k8s.AsServerGeneratedIDNotFoundError(err); !ok {
   689  		t.Fatalf("expected error that can be unwrapped as '%v', but got: %v", reflect.TypeOf(&k8s.ServerGeneratedIDNotFoundError{}), err)
   690  	}
   691  }
   692  
   693  func hasError(t *testing.T, err error) {
   694  	if err == nil {
   695  		t.Fatalf("got nil, want an error")
   696  	}
   697  }
   698  
   699  func TestResource_ValidateResourceIDIfSupported(t *testing.T) {
   700  	tests := []struct {
   701  		name     string
   702  		rc       *v1alpha1.ResourceConfig
   703  		spec     map[string]interface{}
   704  		hasError bool
   705  	}{
   706  		{
   707  			name: "resourceID not supported",
   708  			rc:   &v1alpha1.ResourceConfig{},
   709  		},
   710  		{
   711  			name: "unspecified resourceID",
   712  			rc: &v1alpha1.ResourceConfig{
   713  				ResourceID: v1alpha1.ResourceID{
   714  					TargetField: "test_field",
   715  				},
   716  				MetadataMapping: v1alpha1.MetadataMapping{
   717  					Name: "test_field",
   718  				},
   719  			},
   720  			spec: map[string]interface{}{},
   721  		},
   722  		{
   723  			name: "empty resourceID",
   724  			rc: &v1alpha1.ResourceConfig{
   725  				ResourceID: v1alpha1.ResourceID{
   726  					TargetField: "test_field",
   727  				},
   728  				MetadataMapping: v1alpha1.MetadataMapping{
   729  					Name: "test_field",
   730  				},
   731  			},
   732  			spec: map[string]interface{}{
   733  				"resourceID": "",
   734  			},
   735  			hasError: true,
   736  		},
   737  		{
   738  			name: "nonempty resourceID",
   739  			rc: &v1alpha1.ResourceConfig{
   740  				ResourceID: v1alpha1.ResourceID{
   741  					TargetField: "test_field",
   742  				},
   743  				MetadataMapping: v1alpha1.MetadataMapping{
   744  					Name: "test_field",
   745  				},
   746  			},
   747  			spec: map[string]interface{}{
   748  				"resourceID": "test-resource-id",
   749  			},
   750  		},
   751  	}
   752  
   753  	for _, tc := range tests {
   754  		tc := tc
   755  		t.Run(tc.name, func(t *testing.T) {
   756  			t.Parallel()
   757  			r := resourceSkeleton()
   758  			r.ResourceConfig = *tc.rc
   759  			r.SetName("my-resource")
   760  			r.Spec = tc.spec
   761  			if r.Spec == nil {
   762  				r.Spec = map[string]interface{}{}
   763  			}
   764  
   765  			err := r.ValidateResourceIDIfSupported()
   766  			if tc.hasError {
   767  				if err == nil {
   768  					t.Fatalf("got nil, want an error")
   769  				}
   770  				return
   771  			} else if err != nil {
   772  				t.Fatalf("error validating the resource ID if supported: %v", err)
   773  			}
   774  		})
   775  	}
   776  }
   777  
   778  func TestResource_ConstructServerGeneratedIDInStatusFromResourceID(t *testing.T) {
   779  	tests := []struct {
   780  		name           string
   781  		rc             *v1alpha1.ResourceConfig
   782  		spec           map[string]interface{}
   783  		status         map[string]interface{}
   784  		expectedSpec   map[string]interface{}
   785  		expectedStatus map[string]interface{}
   786  		expectedResult string
   787  		hasError       bool
   788  	}{
   789  		{
   790  			name: "both resourceID field and server-generated ID field are" +
   791  				"unspecified",
   792  			rc: &v1alpha1.ResourceConfig{
   793  				ServerGeneratedIDField: "server_generated_id",
   794  				ResourceID: v1alpha1.ResourceID{
   795  					TargetField: "server_generated_id",
   796  				},
   797  			},
   798  			spec:           map[string]interface{}{},
   799  			status:         map[string]interface{}{},
   800  			expectedSpec:   map[string]interface{}{},
   801  			expectedStatus: map[string]interface{}{},
   802  			expectedResult: "",
   803  		},
   804  		{
   805  			name: "resourceID field is unspecified and server-generated ID " +
   806  				"field is non-empty",
   807  			rc: &v1alpha1.ResourceConfig{
   808  				ServerGeneratedIDField: "server_generated_id",
   809  				ResourceID: v1alpha1.ResourceID{
   810  					TargetField: "server_generated_id",
   811  				},
   812  			},
   813  			spec: map[string]interface{}{},
   814  			status: map[string]interface{}{
   815  				"serverGeneratedId": "server-generated-id",
   816  			},
   817  			expectedSpec: map[string]interface{}{},
   818  			expectedStatus: map[string]interface{}{
   819  				"serverGeneratedId": "server-generated-id",
   820  			},
   821  			expectedResult: "",
   822  		},
   823  		{
   824  			name: "resourceID field is empty",
   825  			rc: &v1alpha1.ResourceConfig{
   826  				ServerGeneratedIDField: "server_generated_id",
   827  				ResourceID: v1alpha1.ResourceID{
   828  					TargetField: "server_generated_id",
   829  				},
   830  			},
   831  			spec: map[string]interface{}{
   832  				"resourceID": "",
   833  			},
   834  			status: map[string]interface{}{
   835  				"serverGeneratedId": "server-generated-id",
   836  			},
   837  			hasError: true,
   838  		},
   839  		{
   840  			name: "resourceID field and server-generated ID field have " +
   841  				"different non-empty values",
   842  			rc: &v1alpha1.ResourceConfig{
   843  				ServerGeneratedIDField: "server_generated_id",
   844  				ResourceID: v1alpha1.ResourceID{
   845  					TargetField: "server_generated_id",
   846  				},
   847  			},
   848  			spec: map[string]interface{}{
   849  				"resourceID": "non-empty-resource-id",
   850  			},
   851  			status: map[string]interface{}{
   852  				"serverGeneratedId": "non-empty-id-in-status",
   853  			},
   854  			expectedSpec: map[string]interface{}{
   855  				"resourceID": "non-empty-resource-id",
   856  			},
   857  			expectedStatus: map[string]interface{}{
   858  				"serverGeneratedId": "non-empty-id-in-status",
   859  			},
   860  			expectedResult: "non-empty-resource-id",
   861  		},
   862  		{
   863  			name: "resourceID field has a non-empty value and " +
   864  				"server-generated ID field is unspecified",
   865  			rc: &v1alpha1.ResourceConfig{
   866  				ServerGeneratedIDField: "server_generated_id",
   867  				ResourceID: v1alpha1.ResourceID{
   868  					TargetField: "server_generated_id",
   869  				},
   870  			},
   871  			spec: map[string]interface{}{
   872  				"resourceID": "non-empty-resource-id",
   873  			},
   874  			status: map[string]interface{}{},
   875  			expectedSpec: map[string]interface{}{
   876  				"resourceID": "non-empty-resource-id",
   877  			},
   878  			expectedStatus: map[string]interface{}{},
   879  			expectedResult: "non-empty-resource-id",
   880  		},
   881  		{
   882  			name: "resourceID field has a non-empty value and the nested " +
   883  				"server-generated ID field is empty",
   884  			rc: &v1alpha1.ResourceConfig{
   885  				ServerGeneratedIDField: "parent_field.server_generated_id",
   886  				ResourceID: v1alpha1.ResourceID{
   887  					TargetField: "parent_field.server_generated_id",
   888  				},
   889  			},
   890  			spec: map[string]interface{}{
   891  				"resourceID": "non-empty-resource-id",
   892  			},
   893  			status: map[string]interface{}{
   894  				"parentField": map[string]interface{}{
   895  					"serverGeneratedId": "",
   896  				},
   897  			},
   898  			expectedSpec: map[string]interface{}{
   899  				"resourceID": "non-empty-resource-id",
   900  			},
   901  			expectedStatus: map[string]interface{}{
   902  				"parentField": map[string]interface{}{
   903  					"serverGeneratedId": "",
   904  				},
   905  			},
   906  			expectedResult: "non-empty-resource-id",
   907  		},
   908  
   909  		{
   910  			name: "with a value template, resourceID field has a " +
   911  				"non-empty value and server-generated ID field is empty",
   912  			rc: &v1alpha1.ResourceConfig{
   913  				ServerGeneratedIDField: "server_generated_id",
   914  				ResourceID: v1alpha1.ResourceID{
   915  					TargetField:   "server_generated_id",
   916  					ValueTemplate: "id/{{value}}",
   917  				},
   918  			},
   919  			spec: map[string]interface{}{
   920  				"resourceID": "non-empty-resource-id",
   921  			},
   922  			status: map[string]interface{}{
   923  				"serverGeneratedId": "",
   924  			},
   925  			expectedSpec: map[string]interface{}{
   926  				"resourceID": "non-empty-resource-id",
   927  			},
   928  			expectedStatus: map[string]interface{}{
   929  				"serverGeneratedId": "",
   930  			},
   931  			expectedResult: "id/non-empty-resource-id",
   932  		},
   933  		{
   934  			name: "with a complex value template, resourceID field " +
   935  				"has a non-empty value and server-generated ID field is empty",
   936  			rc: &v1alpha1.ResourceConfig{
   937  				ServerGeneratedIDField: "server_generated_id",
   938  				ResourceID: v1alpha1.ResourceID{
   939  					TargetField:   "server_generated_id",
   940  					ValueTemplate: "projects/{{project}}/groups/{{group_id}}/values/{{value}}",
   941  				},
   942  				Containers: []v1alpha1.Container{
   943  					{
   944  						Type:    v1alpha1.ContainerTypeProject,
   945  						TFField: "project",
   946  					},
   947  				},
   948  			},
   949  			spec: map[string]interface{}{
   950  				"resourceID": "resource-id",
   951  				"groupId":    "test-group",
   952  			},
   953  			status: map[string]interface{}{
   954  				"serverGeneratedId": "",
   955  			},
   956  			expectedSpec: map[string]interface{}{
   957  				"resourceID": "resource-id",
   958  				"groupId":    "test-group",
   959  			},
   960  			expectedStatus: map[string]interface{}{
   961  				"serverGeneratedId": "",
   962  			},
   963  			expectedResult: "projects/my-project-1/groups/test-group/values/resource-id",
   964  		},
   965  		{
   966  			name: "with a value template containing parent field, the resourceID " +
   967  				"field has a non-empty value and server-generated ID field is empty",
   968  			rc: &v1alpha1.ResourceConfig{
   969  				ServerGeneratedIDField: "server_generated_id",
   970  				ResourceID: v1alpha1.ResourceID{
   971  					TargetField:   "server_generated_id",
   972  					ValueTemplate: "{{parent}}/values/{{value}}",
   973  				},
   974  				ResourceReferences: []v1alpha1.ReferenceConfig{
   975  					{
   976  						TFField: "parent",
   977  						TypeConfig: v1alpha1.TypeConfig{
   978  							Key: "parentRef",
   979  							GVK: k8sschema.GroupVersionKind{
   980  								Group:   "datacatalog.cnrm.cloud.google.com",
   981  								Version: "v1beta1",
   982  								Kind:    "DataCatalogTaxonomy",
   983  							},
   984  							TargetField: "name",
   985  							Parent:      true,
   986  						},
   987  					},
   988  				},
   989  			},
   990  			spec: map[string]interface{}{
   991  				"resourceID": "resource-id",
   992  				"parentRef": map[string]interface{}{
   993  					"external": "projects/project/locations/us/taxonomies/tid",
   994  				},
   995  			},
   996  			status: map[string]interface{}{
   997  				"serverGeneratedId": "",
   998  			},
   999  			expectedSpec: map[string]interface{}{
  1000  				"resourceID": "resource-id",
  1001  				"parentRef": map[string]interface{}{
  1002  					"external": "projects/project/locations/us/taxonomies/tid",
  1003  				},
  1004  			},
  1005  			expectedStatus: map[string]interface{}{
  1006  				"serverGeneratedId": "",
  1007  			},
  1008  			expectedResult: "projects/project/locations/us/taxonomies/tid/values/resource-id",
  1009  		},
  1010  		{
  1011  			name: "resourceID field has a non-empty value and status is nil",
  1012  			rc: &v1alpha1.ResourceConfig{
  1013  				ServerGeneratedIDField: "server_generated_id",
  1014  				ResourceID: v1alpha1.ResourceID{
  1015  					TargetField: "server_generated_id",
  1016  				},
  1017  			},
  1018  			spec: map[string]interface{}{
  1019  				"resourceID": "non-empty-resource-id",
  1020  			},
  1021  			status: nil,
  1022  			expectedSpec: map[string]interface{}{
  1023  				"resourceID": "non-empty-resource-id",
  1024  			},
  1025  			expectedStatus: nil,
  1026  			expectedResult: "non-empty-resource-id",
  1027  		},
  1028  	}
  1029  
  1030  	smLoader := testservicemappingloader.NewForUnitTest(t)
  1031  	for _, tc := range tests {
  1032  		tc := tc
  1033  		t.Run(tc.name, func(t *testing.T) {
  1034  			t.Parallel()
  1035  			c := mgr.GetClient()
  1036  			r := resourceSkeleton()
  1037  			r.ResourceConfig = *tc.rc
  1038  			r.SetName("test-resource")
  1039  			bar := test.NewBarUnstructured("test", "", corev1.ConditionTrue)
  1040  			r.SetAnnotations(bar.GetAnnotations())
  1041  			r.Spec = tc.spec
  1042  			r.Status = tc.status
  1043  
  1044  			result, err := r.ConstructServerGeneratedIDInStatusFromResourceID(c, smLoader)
  1045  			if tc.hasError {
  1046  				if err == nil {
  1047  					t.Fatalf("got nil, want an error")
  1048  				}
  1049  				return
  1050  			} else if err != nil {
  1051  				t.Fatalf("error syncing the server-generated ID in "+
  1052  					"status from resource ID: %v", err)
  1053  			}
  1054  			if got, want := result, tc.expectedResult; got != want {
  1055  				t.Fatalf("got: %v, want: %v", got, want)
  1056  			}
  1057  			if got, want := r.Spec, tc.expectedSpec; !test.Equals(t, got, want) {
  1058  				t.Fatalf("got: %v, want: %v", got, want)
  1059  			}
  1060  			if got, want := r.Status, tc.expectedStatus; !test.Equals(t, got, want) {
  1061  				t.Fatalf("got: %v, want: %v", got, want)
  1062  			}
  1063  		})
  1064  	}
  1065  }
  1066  
  1067  func TestResource_GetServerGeneratedID(t *testing.T) {
  1068  	tests := []struct {
  1069  		name                 string
  1070  		rc                   *v1alpha1.ResourceConfig
  1071  		spec                 map[string]interface{}
  1072  		status               map[string]interface{}
  1073  		expectedID           string
  1074  		assertGotExpectedErr func(t *testing.T, err error)
  1075  	}{
  1076  		{
  1077  			name: "get server-generated ID",
  1078  			rc: &v1alpha1.ResourceConfig{
  1079  				ServerGeneratedIDField: "test_field",
  1080  			},
  1081  			status: map[string]interface{}{
  1082  				"testField": "test-id",
  1083  			},
  1084  			expectedID: "test-id",
  1085  		},
  1086  		{
  1087  			name: "get server-generated ID from nested field",
  1088  			rc: &v1alpha1.ResourceConfig{
  1089  				ServerGeneratedIDField: "nested_field.test_field",
  1090  			},
  1091  			status: map[string]interface{}{
  1092  				"nestedField": map[string]interface{}{
  1093  					"testField": "nested-id",
  1094  				},
  1095  			},
  1096  			expectedID: "nested-id",
  1097  		},
  1098  		{
  1099  			name: "server-generated ID not found",
  1100  			rc: &v1alpha1.ResourceConfig{
  1101  				ServerGeneratedIDField: "test_field",
  1102  			},
  1103  			status: map[string]interface{}{
  1104  				"otherStatusField": "testValue",
  1105  			},
  1106  			expectedID:           "",
  1107  			assertGotExpectedErr: assertIsServerGeneratedIDNotFoundError,
  1108  		},
  1109  		{
  1110  			name: "get server-generated ID from resource ID",
  1111  			rc: &v1alpha1.ResourceConfig{
  1112  				ResourceID: v1alpha1.ResourceID{
  1113  					TargetField: "test_field",
  1114  				},
  1115  				ServerGeneratedIDField: "test_field",
  1116  			},
  1117  			spec: map[string]interface{}{
  1118  				"resourceID": "test-id-in-spec",
  1119  			},
  1120  			expectedID: "test-id-in-spec",
  1121  		},
  1122  		{
  1123  			name: "server-generated ID not set in spec.resourceID or in status",
  1124  			rc: &v1alpha1.ResourceConfig{
  1125  				ResourceID: v1alpha1.ResourceID{
  1126  					TargetField: "test_field",
  1127  				},
  1128  				ServerGeneratedIDField: "test_field",
  1129  			},
  1130  			spec: map[string]interface{}{
  1131  				"otherSpecField": "testValue",
  1132  			},
  1133  			status: map[string]interface{}{
  1134  				"otherStatusField": "testValue",
  1135  			},
  1136  			expectedID:           "",
  1137  			assertGotExpectedErr: assertIsServerGeneratedIDNotFoundError,
  1138  		},
  1139  		{
  1140  			name: "server-generated ID with a valueTemplate not set in " +
  1141  				"spec.resourceID but in status ",
  1142  			rc: &v1alpha1.ResourceConfig{
  1143  				ResourceID: v1alpha1.ResourceID{
  1144  					TargetField:   "test_field",
  1145  					ValueTemplate: "values/{{value}}",
  1146  				},
  1147  				ServerGeneratedIDField: "test_field",
  1148  			},
  1149  			spec: map[string]interface{}{
  1150  				"otherSpecField": "testValue",
  1151  			},
  1152  			status: map[string]interface{}{
  1153  				"testField": "values/test-id",
  1154  			},
  1155  			expectedID: "test-id",
  1156  		},
  1157  		{
  1158  			name: "empty spec.resourceID",
  1159  			rc: &v1alpha1.ResourceConfig{
  1160  				ResourceID: v1alpha1.ResourceID{
  1161  					TargetField: "test_field",
  1162  				},
  1163  				ServerGeneratedIDField: "test_field",
  1164  			},
  1165  			spec: map[string]interface{}{
  1166  				"resourceID": "",
  1167  			},
  1168  			status:               map[string]interface{}{},
  1169  			expectedID:           "",
  1170  			assertGotExpectedErr: hasError,
  1171  		},
  1172  		{
  1173  			name: "spec.resourceID unspecified and serverGeneratedIDField in " +
  1174  				"status empty",
  1175  			rc: &v1alpha1.ResourceConfig{
  1176  				ResourceID: v1alpha1.ResourceID{
  1177  					TargetField: "test_field",
  1178  				},
  1179  				ServerGeneratedIDField: "test_field",
  1180  			},
  1181  			spec: map[string]interface{}{},
  1182  			status: map[string]interface{}{
  1183  				"testField": "",
  1184  			},
  1185  			expectedID:           "",
  1186  			assertGotExpectedErr: hasError,
  1187  		},
  1188  		{
  1189  			name: "spec.resourceID unspecified and serverGeneratedIDField in " +
  1190  				"status not matching value template",
  1191  			rc: &v1alpha1.ResourceConfig{
  1192  				ResourceID: v1alpha1.ResourceID{
  1193  					TargetField:   "test_field",
  1194  					ValueTemplate: "values/{{value}}",
  1195  				},
  1196  				ServerGeneratedIDField: "test_field",
  1197  			},
  1198  			spec: map[string]interface{}{},
  1199  			status: map[string]interface{}{
  1200  				"testField": "test-123",
  1201  			},
  1202  			expectedID:           "",
  1203  			assertGotExpectedErr: hasError,
  1204  		},
  1205  		{
  1206  			name: "spec.resourceID unspecified and resource ID extracted from " +
  1207  				"serverGeneratedIDField in status empty",
  1208  			rc: &v1alpha1.ResourceConfig{
  1209  				ResourceID: v1alpha1.ResourceID{
  1210  					TargetField:   "test_field",
  1211  					ValueTemplate: "values/{{value}}",
  1212  				},
  1213  				ServerGeneratedIDField: "test_field",
  1214  			},
  1215  			spec: map[string]interface{}{},
  1216  			status: map[string]interface{}{
  1217  				"testField": "values/",
  1218  			},
  1219  			expectedID:           "",
  1220  			assertGotExpectedErr: hasError,
  1221  		},
  1222  		{
  1223  			name: "spec.resourceID specified but does not map to server-generated ID",
  1224  			rc: &v1alpha1.ResourceConfig{
  1225  				MetadataMapping: v1alpha1.MetadataMapping{
  1226  					Name: "name",
  1227  				},
  1228  				ResourceID: v1alpha1.ResourceID{
  1229  					TargetField: "name",
  1230  				},
  1231  				ServerGeneratedIDField: "test_field",
  1232  			},
  1233  			spec: map[string]interface{}{
  1234  				"resourceID": "test-id-in-spec",
  1235  			},
  1236  			status: map[string]interface{}{
  1237  				"testField": "test-id-in-status",
  1238  			},
  1239  			expectedID: "test-id-in-status",
  1240  		},
  1241  		{
  1242  			name: "spec.resourceID specified but does not map to server-generated ID, " +
  1243  				"and serverGeneratedIDField in status not found",
  1244  			rc: &v1alpha1.ResourceConfig{
  1245  				MetadataMapping: v1alpha1.MetadataMapping{
  1246  					Name: "name",
  1247  				},
  1248  				ResourceID: v1alpha1.ResourceID{
  1249  					TargetField: "name",
  1250  				},
  1251  				ServerGeneratedIDField: "test_field",
  1252  			},
  1253  			spec: map[string]interface{}{
  1254  				"resourceID": "test-id-in-spec",
  1255  			},
  1256  			status: map[string]interface{}{
  1257  				"otherStatusField": "testValue",
  1258  			},
  1259  			expectedID:           "",
  1260  			assertGotExpectedErr: assertIsServerGeneratedIDNotFoundError,
  1261  		},
  1262  		{
  1263  			name: "spec.resourceID specified and supports a value template, " +
  1264  				"but does not map to server-generated ID",
  1265  			rc: &v1alpha1.ResourceConfig{
  1266  				MetadataMapping: v1alpha1.MetadataMapping{
  1267  					Name: "name",
  1268  				},
  1269  				ResourceID: v1alpha1.ResourceID{
  1270  					TargetField:   "name",
  1271  					ValueTemplate: "values/{{value}}",
  1272  				},
  1273  				ServerGeneratedIDField: "test_field",
  1274  			},
  1275  			spec: map[string]interface{}{
  1276  				"resourceID": "test-id-in-spec",
  1277  			},
  1278  			status: map[string]interface{}{
  1279  				"testField": "test-id-in-status",
  1280  			},
  1281  			expectedID: "test-id-in-status",
  1282  		},
  1283  	}
  1284  
  1285  	for _, tc := range tests {
  1286  		tc := tc
  1287  		t.Run(tc.name, func(t *testing.T) {
  1288  			t.Parallel()
  1289  			r := resourceSkeleton()
  1290  			r.ResourceConfig = *tc.rc
  1291  			r.SetName("my-resource")
  1292  			r.Spec = tc.spec
  1293  			if r.Spec == nil {
  1294  				r.Spec = map[string]interface{}{}
  1295  			}
  1296  			r.Status = tc.status
  1297  			if r.Status == nil {
  1298  				r.Status = map[string]interface{}{}
  1299  			}
  1300  
  1301  			id, err := r.GetServerGeneratedID()
  1302  			if tc.assertGotExpectedErr != nil {
  1303  				tc.assertGotExpectedErr(t, err)
  1304  			} else if err != nil {
  1305  				t.Fatalf("error getting server-generated ID: %v", err)
  1306  			}
  1307  			if got, want := id, tc.expectedID; got != want {
  1308  				t.Fatalf("got: %v, want: %v", got, want)
  1309  			}
  1310  		})
  1311  	}
  1312  }
  1313  
  1314  func TestResource_GetResourceID(t *testing.T) {
  1315  	tests := []struct {
  1316  		name         string
  1317  		rc           *v1alpha1.ResourceConfig
  1318  		metadataName string
  1319  		spec         map[string]interface{}
  1320  		status       map[string]interface{}
  1321  		expectedID   string
  1322  		hasError     bool
  1323  	}{
  1324  		{
  1325  			name: "empty resourceID",
  1326  			rc: &v1alpha1.ResourceConfig{
  1327  				ResourceID: v1alpha1.ResourceID{
  1328  					TargetField: "test_field",
  1329  				},
  1330  			},
  1331  			spec: map[string]interface{}{
  1332  				"resourceID": "",
  1333  			},
  1334  			hasError: true,
  1335  		},
  1336  		{
  1337  			name: "non-empty resource ID",
  1338  			rc: &v1alpha1.ResourceConfig{
  1339  				ResourceID: v1alpha1.ResourceID{
  1340  					TargetField: "test_field",
  1341  				},
  1342  			},
  1343  			spec: map[string]interface{}{
  1344  				"resourceID": "test-id",
  1345  			},
  1346  			expectedID: "test-id",
  1347  		},
  1348  		{
  1349  			name: "user-specified ID from metadata.name",
  1350  			rc: &v1alpha1.ResourceConfig{
  1351  				ResourceID: v1alpha1.ResourceID{
  1352  					TargetField: "test_field",
  1353  				},
  1354  				MetadataMapping: v1alpha1.MetadataMapping{
  1355  					Name: "test_field",
  1356  				},
  1357  			},
  1358  			metadataName: "user-specified-id",
  1359  			spec:         map[string]interface{}{},
  1360  			expectedID:   "user-specified-id",
  1361  		},
  1362  		{
  1363  			name: "user-specified ID not found",
  1364  			rc: &v1alpha1.ResourceConfig{
  1365  				ResourceID: v1alpha1.ResourceID{
  1366  					TargetField: "test_field",
  1367  				},
  1368  				MetadataMapping: v1alpha1.MetadataMapping{
  1369  					Name: "test_field",
  1370  				},
  1371  			},
  1372  			spec:     map[string]interface{}{},
  1373  			hasError: true,
  1374  		},
  1375  		{
  1376  			name: "server-generated ID from status",
  1377  			rc: &v1alpha1.ResourceConfig{
  1378  				ResourceID: v1alpha1.ResourceID{
  1379  					TargetField: "test_field",
  1380  				},
  1381  				ServerGeneratedIDField: "test_field",
  1382  			},
  1383  			spec: map[string]interface{}{},
  1384  			status: map[string]interface{}{
  1385  				"testField": "id-in-status",
  1386  			},
  1387  			expectedID: "id-in-status",
  1388  		},
  1389  		{
  1390  			name: "server-generated ID with a value template from status",
  1391  			rc: &v1alpha1.ResourceConfig{
  1392  				ResourceID: v1alpha1.ResourceID{
  1393  					TargetField:   "test_field",
  1394  					ValueTemplate: "values/{{value}}",
  1395  				},
  1396  				ServerGeneratedIDField: "test_field",
  1397  			},
  1398  			spec: map[string]interface{}{},
  1399  			status: map[string]interface{}{
  1400  				"testField": "values/id-with-template-in-status",
  1401  			},
  1402  			expectedID: "id-with-template-in-status",
  1403  		},
  1404  		{
  1405  			name: "server-generated ID not found",
  1406  			rc: &v1alpha1.ResourceConfig{
  1407  				ResourceID: v1alpha1.ResourceID{
  1408  					TargetField: "test_field",
  1409  				},
  1410  				ServerGeneratedIDField: "test_field",
  1411  			},
  1412  			spec:     map[string]interface{}{},
  1413  			status:   map[string]interface{}{},
  1414  			hasError: true,
  1415  		},
  1416  		{
  1417  			name: "server-generated ID from status but it doesn't match the " +
  1418  				"value template",
  1419  			rc: &v1alpha1.ResourceConfig{
  1420  				ResourceID: v1alpha1.ResourceID{
  1421  					TargetField:   "test_field",
  1422  					ValueTemplate: "values/{{value}}",
  1423  				},
  1424  				ServerGeneratedIDField: "test_field",
  1425  			},
  1426  			spec: map[string]interface{}{},
  1427  			status: map[string]interface{}{
  1428  				"testField": "incorrectly-formatted-id",
  1429  			},
  1430  			hasError: true,
  1431  		},
  1432  	}
  1433  
  1434  	for _, tc := range tests {
  1435  		tc := tc
  1436  		t.Run(tc.name, func(t *testing.T) {
  1437  			t.Parallel()
  1438  			r := resourceSkeleton()
  1439  			r.ResourceConfig = *tc.rc
  1440  			if tc.metadataName != "" {
  1441  				r.SetName(tc.metadataName)
  1442  			}
  1443  			r.Spec = tc.spec
  1444  			if r.Spec == nil {
  1445  				r.Spec = map[string]interface{}{}
  1446  			}
  1447  			r.Status = tc.status
  1448  			if r.Status == nil {
  1449  				r.Status = map[string]interface{}{}
  1450  			}
  1451  
  1452  			id, err := r.GetResourceID()
  1453  			if tc.hasError {
  1454  				if err == nil {
  1455  					t.Fatalf("got nil, want an error")
  1456  				}
  1457  				return
  1458  			} else if err != nil {
  1459  				t.Fatalf("error getting resource ID: %v", err)
  1460  			}
  1461  			if got, want := id, tc.expectedID; got != want {
  1462  				t.Fatalf("got: %v, want: %v", got, want)
  1463  			}
  1464  		})
  1465  	}
  1466  }
  1467  

View as plain text