...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/kcclite/templating_test.go

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

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

View as plain text