...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/webhook/immutable_fields_validator_test.go

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

     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 webhook
    16  
    17  import (
    18  	"container/list"
    19  	"fmt"
    20  	"net/http"
    21  	"reflect"
    22  	"testing"
    23  
    24  	corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
    25  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/iam/v1beta1"
    26  	dclmetadata "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/metadata"
    27  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
    28  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
    29  	testutil "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test"
    30  	testdclschemaloader "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/dclschemaloader"
    31  	testservicemetadataloader "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/servicemetadataloader"
    32  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
    33  
    34  	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    35  	"github.com/hashicorp/terraform-provider-google-beta/google-beta"
    36  	"github.com/nasa9084/go-openapi"
    37  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    38  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    39  	k8sschema "k8s.io/apimachinery/pkg/runtime/schema"
    40  	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    41  )
    42  
    43  func TestChangesOnImmutableFields(t *testing.T) {
    44  	t.Parallel()
    45  	p := google.Provider()
    46  	v := newImmutableFieldsValidatorHandler(t)
    47  	for _, testcase := range TestCases {
    48  		t.Run(testcase.Name, func(t *testing.T) {
    49  			assertImmutableFieldsValidatorResult(t, v, p, testcase)
    50  		})
    51  	}
    52  }
    53  
    54  func TestChangesOnImmutableFieldsForDCLResource(t *testing.T) {
    55  	tests := []struct {
    56  		name     string
    57  		obj      *unstructured.Unstructured
    58  		oldObj   *unstructured.Unstructured
    59  		spec     map[string]interface{}
    60  		oldSpec  map[string]interface{}
    61  		schema   *openapi.Schema
    62  		response admission.Response
    63  	}{
    64  		{
    65  			name: "changes on the base level immutable fields",
    66  			obj: &unstructured.Unstructured{
    67  				Object: map[string]interface{}{
    68  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
    69  					"kind":       "Test1Bar",
    70  					"metadata": map[string]interface{}{
    71  						"annotations": map[string]interface{}{
    72  							k8s.ProjectIDAnnotation: "my-project-1",
    73  						},
    74  					},
    75  				},
    76  			},
    77  			oldObj: &unstructured.Unstructured{
    78  				Object: map[string]interface{}{
    79  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
    80  					"kind":       "Test1Bar",
    81  					"metadata": map[string]interface{}{
    82  						"annotations": map[string]interface{}{
    83  							k8s.ProjectIDAnnotation: "my-project-1",
    84  						},
    85  					},
    86  				},
    87  			},
    88  
    89  			oldSpec: map[string]interface{}{
    90  				"location": "US",
    91  			},
    92  			schema: &openapi.Schema{
    93  				Type: "object",
    94  				Properties: map[string]*openapi.Schema{
    95  					"location": &openapi.Schema{
    96  						Type: "string",
    97  						Extension: map[string]interface{}{
    98  							"x-kubernetes-immutable": true,
    99  						},
   100  					},
   101  				},
   102  				Extension: map[string]interface{}{
   103  					"x-dcl-parent-container": "project",
   104  				},
   105  			},
   106  			response: admission.Errored(http.StatusForbidden,
   107  				k8s.NewImmutableFieldsMutationError([]string{"spec.location"})),
   108  		},
   109  		{
   110  			name: "changes on the resourceID field",
   111  			obj: &unstructured.Unstructured{
   112  				Object: map[string]interface{}{
   113  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   114  					"kind":       "Test1Bar",
   115  					"metadata": map[string]interface{}{
   116  						"annotations": map[string]interface{}{
   117  							k8s.ProjectIDAnnotation: "my-project-1",
   118  						},
   119  					},
   120  				},
   121  			},
   122  			oldObj: &unstructured.Unstructured{
   123  				Object: map[string]interface{}{
   124  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   125  					"kind":       "Test1Bar",
   126  					"metadata": map[string]interface{}{
   127  						"annotations": map[string]interface{}{
   128  							k8s.ProjectIDAnnotation: "my-project-1",
   129  						},
   130  					},
   131  				},
   132  			},
   133  			spec: map[string]interface{}{
   134  				"resourceID": "name1",
   135  			},
   136  			oldSpec: map[string]interface{}{
   137  				"resourceID": "name2",
   138  			},
   139  			schema: &openapi.Schema{
   140  				Type: "object",
   141  				Properties: map[string]*openapi.Schema{
   142  					"name": &openapi.Schema{
   143  						Type: "string",
   144  					},
   145  				},
   146  				Extension: map[string]interface{}{
   147  					"x-dcl-parent-container": "project",
   148  				},
   149  			},
   150  			response: admission.Errored(http.StatusForbidden,
   151  				k8s.NewImmutableFieldsMutationError([]string{"spec.resourceID"})),
   152  		},
   153  		{
   154  			name: "changes on nested immutable fields",
   155  			obj: &unstructured.Unstructured{
   156  				Object: map[string]interface{}{
   157  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   158  					"kind":       "Test1Bar",
   159  					"metadata": map[string]interface{}{
   160  						"annotations": map[string]interface{}{
   161  							k8s.ProjectIDAnnotation: "my-project-1",
   162  						},
   163  					},
   164  				},
   165  			},
   166  			oldObj: &unstructured.Unstructured{
   167  				Object: map[string]interface{}{
   168  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   169  					"kind":       "Test1Bar",
   170  					"metadata": map[string]interface{}{
   171  						"annotations": map[string]interface{}{
   172  							k8s.ProjectIDAnnotation: "my-project-1",
   173  						},
   174  					},
   175  				},
   176  			},
   177  			spec: map[string]interface{}{
   178  				"location": "EU",
   179  				"nestedObjectKey": map[string]interface{}{
   180  					"nestedIntField":    1,
   181  					"nestedStringField": "strval1",
   182  				},
   183  			},
   184  			oldSpec: map[string]interface{}{
   185  				"location": "EU",
   186  				"nestedObjectKey": map[string]interface{}{
   187  					"nestedIntField":    2,
   188  					"nestedStringField": "strval2",
   189  				},
   190  			},
   191  			schema: &openapi.Schema{
   192  				Type: "object",
   193  				Properties: map[string]*openapi.Schema{
   194  					"location": &openapi.Schema{
   195  						Type: "string",
   196  						Extension: map[string]interface{}{
   197  							"x-kubernetes-immutable": true,
   198  						},
   199  					},
   200  					"nestedObjectKey": &openapi.Schema{
   201  						Type: "object",
   202  						Properties: map[string]*openapi.Schema{
   203  							"nestedIntField": &openapi.Schema{
   204  								Type: "integer",
   205  								Extension: map[string]interface{}{
   206  									"x-kubernetes-immutable": true,
   207  								},
   208  							},
   209  							"nestedStringField": &openapi.Schema{
   210  								Type: "string",
   211  							},
   212  						},
   213  					},
   214  				},
   215  				Extension: map[string]interface{}{
   216  					"x-dcl-parent-container": "project",
   217  				},
   218  			},
   219  			response: admission.Errored(http.StatusForbidden,
   220  				k8s.NewImmutableFieldsMutationError([]string{"spec.nestedObjectKey.nestedIntField"})),
   221  		},
   222  		{
   223  			name: "changes on immutable resource reference",
   224  			obj: &unstructured.Unstructured{
   225  				Object: map[string]interface{}{
   226  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   227  					"kind":       "Test1Bar",
   228  				},
   229  			},
   230  			oldObj: &unstructured.Unstructured{
   231  				Object: map[string]interface{}{
   232  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   233  					"kind":       "Test1Bar",
   234  				},
   235  			},
   236  			spec: map[string]interface{}{
   237  				"referenceKeyRef": map[string]interface{}{
   238  					"name": "pubsubtopic-sample-1",
   239  				},
   240  			},
   241  			oldSpec: map[string]interface{}{
   242  				"referenceKeyRef": map[string]interface{}{
   243  					"name": "pubsubtopic-sample-2",
   244  				},
   245  			},
   246  			schema: &openapi.Schema{
   247  				Type: "object",
   248  				Properties: map[string]*openapi.Schema{
   249  					"referenceKey": {
   250  						Type: "string",
   251  						Extension: map[string]interface{}{
   252  							"x-dcl-references": []interface{}{
   253  								map[interface{}]interface{}{
   254  									"resource": "FakeService/FakeKind",
   255  									"field":    "name",
   256  								},
   257  							},
   258  							"x-kubernetes-immutable": true,
   259  						},
   260  					},
   261  				},
   262  			},
   263  			response: admission.Errored(http.StatusForbidden,
   264  				k8s.NewImmutableFieldsMutationError([]string{"spec.referenceKeyRef"})),
   265  		},
   266  		{
   267  			name: "changes on immutable hierarchical reference for single-parent resource",
   268  			obj: &unstructured.Unstructured{
   269  				Object: map[string]interface{}{
   270  					"apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
   271  					"kind":       "Test5ProjectRef",
   272  				},
   273  			},
   274  			oldObj: &unstructured.Unstructured{
   275  				Object: map[string]interface{}{
   276  					"apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
   277  					"kind":       "Test5ProjectRef",
   278  				},
   279  			},
   280  			spec: map[string]interface{}{
   281  				"projectRef": map[string]interface{}{
   282  					"name": "project-sample-1",
   283  				},
   284  			},
   285  			oldSpec: map[string]interface{}{
   286  				"projectRef": map[string]interface{}{
   287  					"name": "project-sample-2",
   288  				},
   289  			},
   290  			schema: &openapi.Schema{
   291  				Type: "object",
   292  				Properties: map[string]*openapi.Schema{
   293  					"project": &openapi.Schema{
   294  						Type: "string",
   295  						Extension: map[string]interface{}{
   296  							"x-dcl-references": []interface{}{
   297  								map[interface{}]interface{}{
   298  									"field":    "name",
   299  									"parent":   true,
   300  									"resource": "Cloudresourcemanager/Project",
   301  								},
   302  							},
   303  							"x-kubernetes-immutable": true,
   304  						},
   305  					},
   306  				},
   307  			},
   308  			response: admission.Errored(http.StatusForbidden,
   309  				k8s.NewImmutableFieldsMutationError([]string{"spec.projectRef"})),
   310  		},
   311  		{
   312  			name: "changes on immutable hierarchical reference for multi-parent resource (value change)",
   313  			obj: &unstructured.Unstructured{
   314  				Object: map[string]interface{}{
   315  					"apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
   316  					"kind":       "Test5MultipleRefs",
   317  				},
   318  			},
   319  			oldObj: &unstructured.Unstructured{
   320  				Object: map[string]interface{}{
   321  					"apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
   322  					"kind":       "Test5MultipleRefs",
   323  				},
   324  			},
   325  			spec: map[string]interface{}{
   326  				"folderRef": map[string]interface{}{
   327  					"name": "folder-sample-1",
   328  				},
   329  			},
   330  			oldSpec: map[string]interface{}{
   331  				"folderRef": map[string]interface{}{
   332  					"name": "folder-sample-2",
   333  				},
   334  			},
   335  			schema: &openapi.Schema{
   336  				Type: "object",
   337  				Properties: map[string]*openapi.Schema{
   338  					"parent": &openapi.Schema{
   339  						Type: "string",
   340  						Extension: map[string]interface{}{
   341  							"x-dcl-references": []interface{}{
   342  								map[interface{}]interface{}{
   343  									"field":    "name",
   344  									"parent":   true,
   345  									"resource": "Cloudresourcemanager/Project",
   346  								},
   347  								map[interface{}]interface{}{
   348  									"field":    "name",
   349  									"parent":   true,
   350  									"resource": "Cloudresourcemanager/Folder",
   351  								},
   352  								map[interface{}]interface{}{
   353  									"field":    "name",
   354  									"parent":   true,
   355  									"resource": "Cloudresourcemanager/Organization",
   356  								},
   357  							},
   358  							"x-kubernetes-immutable": true,
   359  						},
   360  					},
   361  				},
   362  			},
   363  			response: admission.Errored(http.StatusForbidden,
   364  				k8s.NewImmutableFieldsMutationError([]string{"spec.folderRef"})),
   365  		},
   366  		{
   367  			name: "changes on immutable hierarchical reference for multi-parent resource (key change)",
   368  			obj: &unstructured.Unstructured{
   369  				Object: map[string]interface{}{
   370  					"apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
   371  					"kind":       "Test5MultipleRefs",
   372  				},
   373  			},
   374  			oldObj: &unstructured.Unstructured{
   375  				Object: map[string]interface{}{
   376  					"apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
   377  					"kind":       "Test5MultipleRefs",
   378  				},
   379  			},
   380  			spec: map[string]interface{}{
   381  				"folderRef": map[string]interface{}{
   382  					"name": "folder-sample-1",
   383  				},
   384  			},
   385  			oldSpec: map[string]interface{}{
   386  				"organizationRef": map[string]interface{}{
   387  					"name": "organization-sample-1",
   388  				},
   389  			},
   390  			schema: &openapi.Schema{
   391  				Type: "object",
   392  				Properties: map[string]*openapi.Schema{
   393  					"parent": &openapi.Schema{
   394  						Type: "string",
   395  						Extension: map[string]interface{}{
   396  							"x-dcl-references": []interface{}{
   397  								map[interface{}]interface{}{
   398  									"field":    "name",
   399  									"parent":   true,
   400  									"resource": "Cloudresourcemanager/Project",
   401  								},
   402  								map[interface{}]interface{}{
   403  									"field":    "name",
   404  									"parent":   true,
   405  									"resource": "Cloudresourcemanager/Folder",
   406  								},
   407  								map[interface{}]interface{}{
   408  									"field":    "name",
   409  									"parent":   true,
   410  									"resource": "Cloudresourcemanager/Organization",
   411  								},
   412  							},
   413  							"x-kubernetes-immutable": true,
   414  						},
   415  					},
   416  				},
   417  			},
   418  			response: admission.Errored(http.StatusForbidden,
   419  				k8s.NewImmutableFieldsMutationError([]string{"spec.folderRef", "spec.organizationRef"})),
   420  		},
   421  		{
   422  			name: "changes on mutable hierarchical reference for single-parent resource",
   423  			obj: &unstructured.Unstructured{
   424  				Object: map[string]interface{}{
   425  					"apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
   426  					"kind":       "Test5ProjectRef",
   427  				},
   428  			},
   429  			oldObj: &unstructured.Unstructured{
   430  				Object: map[string]interface{}{
   431  					"apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
   432  					"kind":       "Test5ProjectRef",
   433  				},
   434  			},
   435  			spec: map[string]interface{}{
   436  				"projectRef": map[string]interface{}{
   437  					"name": "project-sample-1",
   438  				},
   439  			},
   440  			oldSpec: map[string]interface{}{
   441  				"projectRef": map[string]interface{}{
   442  					"name": "project-sample-2",
   443  				},
   444  			},
   445  			schema: &openapi.Schema{
   446  				Type: "object",
   447  				Properties: map[string]*openapi.Schema{
   448  					"project": &openapi.Schema{
   449  						Type: "string",
   450  						Extension: map[string]interface{}{
   451  							"x-dcl-references": []interface{}{
   452  								map[interface{}]interface{}{
   453  									"field":    "name",
   454  									"parent":   true,
   455  									"resource": "Cloudresourcemanager/Project",
   456  								},
   457  							},
   458  						},
   459  					},
   460  				},
   461  			},
   462  			response: allowedResponse,
   463  		},
   464  		{
   465  			name: "changes on mutable hierarchical reference for multi-parent resource (value change)",
   466  			obj: &unstructured.Unstructured{
   467  				Object: map[string]interface{}{
   468  					"apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
   469  					"kind":       "Test5MultipleRefs",
   470  				},
   471  			},
   472  			oldObj: &unstructured.Unstructured{
   473  				Object: map[string]interface{}{
   474  					"apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
   475  					"kind":       "Test5MultipleRefs",
   476  				},
   477  			},
   478  			spec: map[string]interface{}{
   479  				"folderRef": map[string]interface{}{
   480  					"name": "folder-sample-1",
   481  				},
   482  			},
   483  			oldSpec: map[string]interface{}{
   484  				"folderRef": map[string]interface{}{
   485  					"name": "folder-sample-2",
   486  				},
   487  			},
   488  			schema: &openapi.Schema{
   489  				Type: "object",
   490  				Properties: map[string]*openapi.Schema{
   491  					"parent": &openapi.Schema{
   492  						Type: "string",
   493  						Extension: map[string]interface{}{
   494  							"x-dcl-references": []interface{}{
   495  								map[interface{}]interface{}{
   496  									"field":    "name",
   497  									"parent":   true,
   498  									"resource": "Cloudresourcemanager/Project",
   499  								},
   500  								map[interface{}]interface{}{
   501  									"field":    "name",
   502  									"parent":   true,
   503  									"resource": "Cloudresourcemanager/Folder",
   504  								},
   505  								map[interface{}]interface{}{
   506  									"field":    "name",
   507  									"parent":   true,
   508  									"resource": "Cloudresourcemanager/Organization",
   509  								},
   510  							},
   511  						},
   512  					},
   513  				},
   514  			},
   515  			response: allowedResponse,
   516  		},
   517  		{
   518  			name: "changes on mutable hierarchical reference for multi-parent resource (key change)",
   519  			obj: &unstructured.Unstructured{
   520  				Object: map[string]interface{}{
   521  					"apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
   522  					"kind":       "Test5MultipleRefs",
   523  				},
   524  			},
   525  			oldObj: &unstructured.Unstructured{
   526  				Object: map[string]interface{}{
   527  					"apiVersion": "test5.cnrm.cloud.google.com/v1alpha1",
   528  					"kind":       "Test5MultipleRefs",
   529  				},
   530  			},
   531  			spec: map[string]interface{}{
   532  				"folderRef": map[string]interface{}{
   533  					"name": "folder-sample-1",
   534  				},
   535  			},
   536  			oldSpec: map[string]interface{}{
   537  				"organizationRef": map[string]interface{}{
   538  					"name": "organization-sample-1",
   539  				},
   540  			},
   541  			schema: &openapi.Schema{
   542  				Type: "object",
   543  				Properties: map[string]*openapi.Schema{
   544  					"parent": &openapi.Schema{
   545  						Type: "string",
   546  						Extension: map[string]interface{}{
   547  							"x-dcl-references": []interface{}{
   548  								map[interface{}]interface{}{
   549  									"field":    "name",
   550  									"parent":   true,
   551  									"resource": "Cloudresourcemanager/Project",
   552  								},
   553  								map[interface{}]interface{}{
   554  									"field":    "name",
   555  									"parent":   true,
   556  									"resource": "Cloudresourcemanager/Folder",
   557  								},
   558  								map[interface{}]interface{}{
   559  									"field":    "name",
   560  									"parent":   true,
   561  									"resource": "Cloudresourcemanager/Organization",
   562  								},
   563  							},
   564  						},
   565  					},
   566  				},
   567  			},
   568  			response: allowedResponse,
   569  		},
   570  		{
   571  			name: "changes on immutable arrays of primitives",
   572  			obj: &unstructured.Unstructured{
   573  				Object: map[string]interface{}{
   574  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   575  					"kind":       "Test1Bar",
   576  					"metadata": map[string]interface{}{
   577  						"annotations": map[string]interface{}{
   578  							k8s.ProjectIDAnnotation: "my-project-1",
   579  						},
   580  					},
   581  				},
   582  			},
   583  			oldObj: &unstructured.Unstructured{
   584  				Object: map[string]interface{}{
   585  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   586  					"kind":       "Test1Bar",
   587  					"metadata": map[string]interface{}{
   588  						"annotations": map[string]interface{}{
   589  							k8s.ProjectIDAnnotation: "my-project-1",
   590  						},
   591  					},
   592  				},
   593  			},
   594  			spec: map[string]interface{}{
   595  				"stringArrayKey": []string{"val1"},
   596  			},
   597  			oldSpec: map[string]interface{}{
   598  				"stringArrayKey": []string{"val1", "val2"},
   599  			},
   600  			schema: &openapi.Schema{
   601  				Type: "object",
   602  				Properties: map[string]*openapi.Schema{
   603  					"stringArrayKey": &openapi.Schema{
   604  						Type: "array",
   605  						Items: &openapi.Schema{
   606  							Type: "string",
   607  						},
   608  						Extension: map[string]interface{}{
   609  							"x-kubernetes-immutable": true,
   610  						},
   611  					},
   612  				},
   613  				Extension: map[string]interface{}{
   614  					"x-dcl-parent-container": "project",
   615  				},
   616  			},
   617  			response: admission.Errored(http.StatusForbidden,
   618  				k8s.NewImmutableFieldsMutationError([]string{"spec.stringArrayKey"})),
   619  		},
   620  		{
   621  			name: "changes on immutable maps of primitives",
   622  			obj: &unstructured.Unstructured{
   623  				Object: map[string]interface{}{
   624  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   625  					"kind":       "Test1Bar",
   626  					"metadata": map[string]interface{}{
   627  						"annotations": map[string]interface{}{
   628  							k8s.ProjectIDAnnotation: "my-project-1",
   629  						},
   630  					},
   631  				},
   632  			},
   633  			oldObj: &unstructured.Unstructured{
   634  				Object: map[string]interface{}{
   635  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   636  					"kind":       "Test1Bar",
   637  					"metadata": map[string]interface{}{
   638  						"annotations": map[string]interface{}{
   639  							k8s.ProjectIDAnnotation: "my-project-1",
   640  						},
   641  					},
   642  				},
   643  			},
   644  			spec: map[string]interface{}{
   645  				"stringMapKey": map[string]interface{}{
   646  					"foo": "foo",
   647  				},
   648  			},
   649  			oldSpec: map[string]interface{}{
   650  				"stringMapKey": map[string]interface{}{
   651  					"foo": "bar",
   652  				},
   653  			},
   654  			schema: &openapi.Schema{
   655  				Type: "object",
   656  				Properties: map[string]*openapi.Schema{
   657  					"stringMapKey": &openapi.Schema{
   658  						Type: "object",
   659  						AdditionalProperties: &openapi.Schema{
   660  							Type: "string",
   661  						},
   662  						Extension: map[string]interface{}{
   663  							"x-kubernetes-immutable": true,
   664  						},
   665  					},
   666  				},
   667  				Extension: map[string]interface{}{
   668  					"x-dcl-parent-container": "project",
   669  				},
   670  			},
   671  			response: admission.Errored(http.StatusForbidden,
   672  				k8s.NewImmutableFieldsMutationError([]string{"spec.stringMapKey"})),
   673  		},
   674  		{
   675  			name: "no changes to immutable maps of objects",
   676  			obj: &unstructured.Unstructured{
   677  				Object: map[string]interface{}{
   678  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   679  					"kind":       "Test1Bar",
   680  					"metadata": map[string]interface{}{
   681  						"annotations": map[string]interface{}{
   682  							k8s.ProjectIDAnnotation: "my-project-1",
   683  						},
   684  					},
   685  				},
   686  			},
   687  			oldObj: &unstructured.Unstructured{
   688  				Object: map[string]interface{}{
   689  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   690  					"kind":       "Test1Bar",
   691  					"metadata": map[string]interface{}{
   692  						"annotations": map[string]interface{}{
   693  							k8s.ProjectIDAnnotation: "my-project-1",
   694  						},
   695  					},
   696  				},
   697  			},
   698  			spec: map[string]interface{}{
   699  				"objectMapKey": map[string]interface{}{
   700  					"obj1": map[string]interface{}{
   701  						"objectField": "foo1",
   702  					},
   703  					"obj2": map[string]interface{}{
   704  						"objectField": "foo2",
   705  					},
   706  				},
   707  			},
   708  			oldSpec: map[string]interface{}{
   709  				"objectMapKey": map[string]interface{}{
   710  					"obj1": map[string]interface{}{
   711  						"objectField": "foo1",
   712  					},
   713  					"obj2": map[string]interface{}{
   714  						"objectField": "foo2",
   715  					},
   716  				},
   717  			},
   718  			schema: &openapi.Schema{
   719  				Type: "object",
   720  				Properties: map[string]*openapi.Schema{
   721  					"objectMapKey": &openapi.Schema{
   722  						Type: "object",
   723  						AdditionalProperties: &openapi.Schema{
   724  							Type: "object",
   725  							Properties: map[string]*openapi.Schema{
   726  								"objectField": &openapi.Schema{
   727  									Type: "string",
   728  								},
   729  							},
   730  						},
   731  						Extension: map[string]interface{}{
   732  							"x-kubernetes-immutable": true,
   733  						},
   734  					},
   735  				},
   736  				Extension: map[string]interface{}{
   737  					"x-dcl-parent-container": "project",
   738  				},
   739  			},
   740  			response: allowedResponse,
   741  		},
   742  		{
   743  			name: "changes on immutable maps of objects (changed a value in one of the objects in the map)",
   744  			obj: &unstructured.Unstructured{
   745  				Object: map[string]interface{}{
   746  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   747  					"kind":       "Test1Bar",
   748  					"metadata": map[string]interface{}{
   749  						"annotations": map[string]interface{}{
   750  							k8s.ProjectIDAnnotation: "my-project-1",
   751  						},
   752  					},
   753  				},
   754  			},
   755  			oldObj: &unstructured.Unstructured{
   756  				Object: map[string]interface{}{
   757  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   758  					"kind":       "Test1Bar",
   759  					"metadata": map[string]interface{}{
   760  						"annotations": map[string]interface{}{
   761  							k8s.ProjectIDAnnotation: "my-project-1",
   762  						},
   763  					},
   764  				},
   765  			},
   766  			spec: map[string]interface{}{
   767  				"objectMapKey": map[string]interface{}{
   768  					"obj1": map[string]interface{}{
   769  						"objectField": "bar1",
   770  					},
   771  					"obj2": map[string]interface{}{
   772  						"objectField": "foo2",
   773  					},
   774  				},
   775  			},
   776  			oldSpec: map[string]interface{}{
   777  				"objectMapKey": map[string]interface{}{
   778  					"obj1": map[string]interface{}{
   779  						"objectField": "foo1",
   780  					},
   781  					"obj2": map[string]interface{}{
   782  						"objectField": "foo2",
   783  					},
   784  				},
   785  			},
   786  			schema: &openapi.Schema{
   787  				Type: "object",
   788  				Properties: map[string]*openapi.Schema{
   789  					"objectMapKey": &openapi.Schema{
   790  						Type: "object",
   791  						AdditionalProperties: &openapi.Schema{
   792  							Type: "object",
   793  							Properties: map[string]*openapi.Schema{
   794  								"objectField": &openapi.Schema{
   795  									Type: "string",
   796  								},
   797  							},
   798  						},
   799  						Extension: map[string]interface{}{
   800  							"x-kubernetes-immutable": true,
   801  						},
   802  					},
   803  				},
   804  				Extension: map[string]interface{}{
   805  					"x-dcl-parent-container": "project",
   806  				},
   807  			},
   808  			response: admission.Errored(http.StatusForbidden,
   809  				k8s.NewImmutableFieldsMutationError([]string{"spec.objectMapKey"})),
   810  		},
   811  		{
   812  			name: "changes on immutable maps of objects (changed the key of one of the objects in the map)",
   813  			obj: &unstructured.Unstructured{
   814  				Object: map[string]interface{}{
   815  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   816  					"kind":       "Test1Bar",
   817  					"metadata": map[string]interface{}{
   818  						"annotations": map[string]interface{}{
   819  							k8s.ProjectIDAnnotation: "my-project-1",
   820  						},
   821  					},
   822  				},
   823  			},
   824  			oldObj: &unstructured.Unstructured{
   825  				Object: map[string]interface{}{
   826  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   827  					"kind":       "Test1Bar",
   828  					"metadata": map[string]interface{}{
   829  						"annotations": map[string]interface{}{
   830  							k8s.ProjectIDAnnotation: "my-project-1",
   831  						},
   832  					},
   833  				},
   834  			},
   835  			spec: map[string]interface{}{
   836  				"objectMapKey": map[string]interface{}{
   837  					"obj1-new": map[string]interface{}{
   838  						"objectField": "foo1",
   839  					},
   840  					"obj2": map[string]interface{}{
   841  						"objectField": "foo2",
   842  					},
   843  				},
   844  			},
   845  			oldSpec: map[string]interface{}{
   846  				"objectMapKey": map[string]interface{}{
   847  					"obj1": map[string]interface{}{
   848  						"objectField": "foo1",
   849  					},
   850  					"obj2": map[string]interface{}{
   851  						"objectField": "foo2",
   852  					},
   853  				},
   854  			},
   855  			schema: &openapi.Schema{
   856  				Type: "object",
   857  				Properties: map[string]*openapi.Schema{
   858  					"objectMapKey": &openapi.Schema{
   859  						Type: "object",
   860  						AdditionalProperties: &openapi.Schema{
   861  							Type: "object",
   862  							Properties: map[string]*openapi.Schema{
   863  								"objectField": &openapi.Schema{
   864  									Type: "string",
   865  								},
   866  							},
   867  						},
   868  						Extension: map[string]interface{}{
   869  							"x-kubernetes-immutable": true,
   870  						},
   871  					},
   872  				},
   873  				Extension: map[string]interface{}{
   874  					"x-dcl-parent-container": "project",
   875  				},
   876  			},
   877  			response: admission.Errored(http.StatusForbidden,
   878  				k8s.NewImmutableFieldsMutationError([]string{"spec.objectMapKey"})),
   879  		},
   880  		{
   881  			name: "changes on immutable maps of objects (added a new object to the map)",
   882  			obj: &unstructured.Unstructured{
   883  				Object: map[string]interface{}{
   884  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   885  					"kind":       "Test1Bar",
   886  					"metadata": map[string]interface{}{
   887  						"annotations": map[string]interface{}{
   888  							k8s.ProjectIDAnnotation: "my-project-1",
   889  						},
   890  					},
   891  				},
   892  			},
   893  			oldObj: &unstructured.Unstructured{
   894  				Object: map[string]interface{}{
   895  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   896  					"kind":       "Test1Bar",
   897  					"metadata": map[string]interface{}{
   898  						"annotations": map[string]interface{}{
   899  							k8s.ProjectIDAnnotation: "my-project-1",
   900  						},
   901  					},
   902  				},
   903  			},
   904  			spec: map[string]interface{}{
   905  				"objectMapKey": map[string]interface{}{
   906  					"obj1": map[string]interface{}{
   907  						"objectField": "foo1",
   908  					},
   909  					"obj2": map[string]interface{}{
   910  						"objectField": "foo2",
   911  					},
   912  					"obj3": map[string]interface{}{
   913  						"objectField": "foo3",
   914  					},
   915  				},
   916  			},
   917  			oldSpec: map[string]interface{}{
   918  				"objectMapKey": map[string]interface{}{
   919  					"obj1": map[string]interface{}{
   920  						"objectField": "foo1",
   921  					},
   922  					"obj2": map[string]interface{}{
   923  						"objectField": "foo2",
   924  					},
   925  				},
   926  			},
   927  			schema: &openapi.Schema{
   928  				Type: "object",
   929  				Properties: map[string]*openapi.Schema{
   930  					"objectMapKey": &openapi.Schema{
   931  						Type: "object",
   932  						AdditionalProperties: &openapi.Schema{
   933  							Type: "object",
   934  							Properties: map[string]*openapi.Schema{
   935  								"objectField": &openapi.Schema{
   936  									Type: "string",
   937  								},
   938  							},
   939  						},
   940  						Extension: map[string]interface{}{
   941  							"x-kubernetes-immutable": true,
   942  						},
   943  					},
   944  				},
   945  				Extension: map[string]interface{}{
   946  					"x-dcl-parent-container": "project",
   947  				},
   948  			},
   949  			response: admission.Errored(http.StatusForbidden,
   950  				k8s.NewImmutableFieldsMutationError([]string{"spec.objectMapKey"})),
   951  		},
   952  		{
   953  			name: "changes on immutable maps of objects (deleted an object from the map)",
   954  			obj: &unstructured.Unstructured{
   955  				Object: map[string]interface{}{
   956  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   957  					"kind":       "Test1Bar",
   958  					"metadata": map[string]interface{}{
   959  						"annotations": map[string]interface{}{
   960  							k8s.ProjectIDAnnotation: "my-project-1",
   961  						},
   962  					},
   963  				},
   964  			},
   965  			oldObj: &unstructured.Unstructured{
   966  				Object: map[string]interface{}{
   967  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
   968  					"kind":       "Test1Bar",
   969  					"metadata": map[string]interface{}{
   970  						"annotations": map[string]interface{}{
   971  							k8s.ProjectIDAnnotation: "my-project-1",
   972  						},
   973  					},
   974  				},
   975  			},
   976  			spec: map[string]interface{}{
   977  				"objectMapKey": map[string]interface{}{
   978  					"obj1": map[string]interface{}{
   979  						"objectField": "foo1",
   980  					},
   981  				},
   982  			},
   983  			oldSpec: map[string]interface{}{
   984  				"objectMapKey": map[string]interface{}{
   985  					"obj1": map[string]interface{}{
   986  						"objectField": "foo1",
   987  					},
   988  					"obj2": map[string]interface{}{
   989  						"objectField": "foo2",
   990  					},
   991  				},
   992  			},
   993  			schema: &openapi.Schema{
   994  				Type: "object",
   995  				Properties: map[string]*openapi.Schema{
   996  					"objectMapKey": &openapi.Schema{
   997  						Type: "object",
   998  						AdditionalProperties: &openapi.Schema{
   999  							Type: "object",
  1000  							Properties: map[string]*openapi.Schema{
  1001  								"objectField": &openapi.Schema{
  1002  									Type: "string",
  1003  								},
  1004  							},
  1005  						},
  1006  						Extension: map[string]interface{}{
  1007  							"x-kubernetes-immutable": true,
  1008  						},
  1009  					},
  1010  				},
  1011  				Extension: map[string]interface{}{
  1012  					"x-dcl-parent-container": "project",
  1013  				},
  1014  			},
  1015  			response: admission.Errored(http.StatusForbidden,
  1016  				k8s.NewImmutableFieldsMutationError([]string{"spec.objectMapKey"})),
  1017  		},
  1018  		{
  1019  			name: "changes on mutable maps of objects (changed a field in one of the objects in the map)",
  1020  			obj: &unstructured.Unstructured{
  1021  				Object: map[string]interface{}{
  1022  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
  1023  					"kind":       "Test1Bar",
  1024  					"metadata": map[string]interface{}{
  1025  						"annotations": map[string]interface{}{
  1026  							k8s.ProjectIDAnnotation: "my-project-1",
  1027  						},
  1028  					},
  1029  				},
  1030  			},
  1031  			oldObj: &unstructured.Unstructured{
  1032  				Object: map[string]interface{}{
  1033  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
  1034  					"kind":       "Test1Bar",
  1035  					"metadata": map[string]interface{}{
  1036  						"annotations": map[string]interface{}{
  1037  							k8s.ProjectIDAnnotation: "my-project-1",
  1038  						},
  1039  					},
  1040  				},
  1041  			},
  1042  			spec: map[string]interface{}{
  1043  				"objectMapKey": map[string]interface{}{
  1044  					"obj1": map[string]interface{}{
  1045  						"objectField": "bar1",
  1046  					},
  1047  					"obj2": map[string]interface{}{
  1048  						"objectField": "foo2",
  1049  					},
  1050  				},
  1051  			},
  1052  			oldSpec: map[string]interface{}{
  1053  				"objectMapKey": map[string]interface{}{
  1054  					"obj1": map[string]interface{}{
  1055  						"objectField": "foo1",
  1056  					},
  1057  					"obj2": map[string]interface{}{
  1058  						"objectField": "foo2",
  1059  					},
  1060  				},
  1061  			},
  1062  			schema: &openapi.Schema{
  1063  				Type: "object",
  1064  				Properties: map[string]*openapi.Schema{
  1065  					"objectMapKey": &openapi.Schema{
  1066  						Type: "object",
  1067  						AdditionalProperties: &openapi.Schema{
  1068  							Type: "object",
  1069  							Properties: map[string]*openapi.Schema{
  1070  								"objectField": &openapi.Schema{
  1071  									Type: "string",
  1072  								},
  1073  							},
  1074  						},
  1075  					},
  1076  				},
  1077  				Extension: map[string]interface{}{
  1078  					"x-dcl-parent-container": "project",
  1079  				},
  1080  			},
  1081  			response: allowedResponse,
  1082  		},
  1083  		{
  1084  			name: "no changes to immutable maps of arrays",
  1085  			obj: &unstructured.Unstructured{
  1086  				Object: map[string]interface{}{
  1087  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
  1088  					"kind":       "Test1Bar",
  1089  					"metadata": map[string]interface{}{
  1090  						"annotations": map[string]interface{}{
  1091  							k8s.ProjectIDAnnotation: "my-project-1",
  1092  						},
  1093  					},
  1094  				},
  1095  			},
  1096  			oldObj: &unstructured.Unstructured{
  1097  				Object: map[string]interface{}{
  1098  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
  1099  					"kind":       "Test1Bar",
  1100  					"metadata": map[string]interface{}{
  1101  						"annotations": map[string]interface{}{
  1102  							k8s.ProjectIDAnnotation: "my-project-1",
  1103  						},
  1104  					},
  1105  				},
  1106  			},
  1107  			spec: map[string]interface{}{
  1108  				"arrayMapKey": map[string]interface{}{
  1109  					"arr1": []interface{}{
  1110  						"foo1",
  1111  					},
  1112  					"arr2": []interface{}{
  1113  						"foo2",
  1114  					},
  1115  				},
  1116  			},
  1117  			oldSpec: map[string]interface{}{
  1118  				"arrayMapKey": map[string]interface{}{
  1119  					"arr1": []interface{}{
  1120  						"foo1",
  1121  					},
  1122  					"arr2": []interface{}{
  1123  						"foo2",
  1124  					},
  1125  				},
  1126  			},
  1127  			schema: &openapi.Schema{
  1128  				Type: "object",
  1129  				Properties: map[string]*openapi.Schema{
  1130  					"arrayMapKey": &openapi.Schema{
  1131  						Type: "object",
  1132  						AdditionalProperties: &openapi.Schema{
  1133  							Type: "array",
  1134  							Items: &openapi.Schema{
  1135  								Type: "string",
  1136  							},
  1137  						},
  1138  						Extension: map[string]interface{}{
  1139  							"x-kubernetes-immutable": true,
  1140  						},
  1141  					},
  1142  				},
  1143  				Extension: map[string]interface{}{
  1144  					"x-dcl-parent-container": "project",
  1145  				},
  1146  			},
  1147  			response: allowedResponse,
  1148  		},
  1149  		{
  1150  			name: "changes on immutable maps of arrays (changed a value in one of the arrays in the map)",
  1151  			obj: &unstructured.Unstructured{
  1152  				Object: map[string]interface{}{
  1153  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
  1154  					"kind":       "Test1Bar",
  1155  					"metadata": map[string]interface{}{
  1156  						"annotations": map[string]interface{}{
  1157  							k8s.ProjectIDAnnotation: "my-project-1",
  1158  						},
  1159  					},
  1160  				},
  1161  			},
  1162  			oldObj: &unstructured.Unstructured{
  1163  				Object: map[string]interface{}{
  1164  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
  1165  					"kind":       "Test1Bar",
  1166  					"metadata": map[string]interface{}{
  1167  						"annotations": map[string]interface{}{
  1168  							k8s.ProjectIDAnnotation: "my-project-1",
  1169  						},
  1170  					},
  1171  				},
  1172  			},
  1173  			spec: map[string]interface{}{
  1174  				"arrayMapKey": map[string]interface{}{
  1175  					"arr1": []interface{}{
  1176  						"bar1",
  1177  					},
  1178  					"arr2": []interface{}{
  1179  						"foo2",
  1180  					},
  1181  				},
  1182  			},
  1183  			oldSpec: map[string]interface{}{
  1184  				"arrayMapKey": map[string]interface{}{
  1185  					"arr1": []interface{}{
  1186  						"foo1",
  1187  					},
  1188  					"arr2": []interface{}{
  1189  						"foo2",
  1190  					},
  1191  				},
  1192  			},
  1193  			schema: &openapi.Schema{
  1194  				Type: "object",
  1195  				Properties: map[string]*openapi.Schema{
  1196  					"arrayMapKey": &openapi.Schema{
  1197  						Type: "object",
  1198  						AdditionalProperties: &openapi.Schema{
  1199  							Type: "array",
  1200  							Items: &openapi.Schema{
  1201  								Type: "string",
  1202  							},
  1203  						},
  1204  						Extension: map[string]interface{}{
  1205  							"x-kubernetes-immutable": true,
  1206  						},
  1207  					},
  1208  				},
  1209  				Extension: map[string]interface{}{
  1210  					"x-dcl-parent-container": "project",
  1211  				},
  1212  			},
  1213  			response: admission.Errored(http.StatusForbidden,
  1214  				k8s.NewImmutableFieldsMutationError([]string{"spec.arrayMapKey"})),
  1215  		},
  1216  		{
  1217  			name: "changes on immutable maps of arrays (added a value in one of the arrays in the map)",
  1218  			obj: &unstructured.Unstructured{
  1219  				Object: map[string]interface{}{
  1220  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
  1221  					"kind":       "Test1Bar",
  1222  					"metadata": map[string]interface{}{
  1223  						"annotations": map[string]interface{}{
  1224  							k8s.ProjectIDAnnotation: "my-project-1",
  1225  						},
  1226  					},
  1227  				},
  1228  			},
  1229  			oldObj: &unstructured.Unstructured{
  1230  				Object: map[string]interface{}{
  1231  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
  1232  					"kind":       "Test1Bar",
  1233  					"metadata": map[string]interface{}{
  1234  						"annotations": map[string]interface{}{
  1235  							k8s.ProjectIDAnnotation: "my-project-1",
  1236  						},
  1237  					},
  1238  				},
  1239  			},
  1240  			spec: map[string]interface{}{
  1241  				"arrayMapKey": map[string]interface{}{
  1242  					"arr1": []interface{}{
  1243  						"foo1",
  1244  						"foo11",
  1245  					},
  1246  					"arr2": []interface{}{
  1247  						"foo2",
  1248  					},
  1249  				},
  1250  			},
  1251  			oldSpec: map[string]interface{}{
  1252  				"arrayMapKey": map[string]interface{}{
  1253  					"arr1": []interface{}{
  1254  						"foo1",
  1255  					},
  1256  					"arr2": []interface{}{
  1257  						"foo2",
  1258  					},
  1259  				},
  1260  			},
  1261  			schema: &openapi.Schema{
  1262  				Type: "object",
  1263  				Properties: map[string]*openapi.Schema{
  1264  					"arrayMapKey": &openapi.Schema{
  1265  						Type: "object",
  1266  						AdditionalProperties: &openapi.Schema{
  1267  							Type: "array",
  1268  							Items: &openapi.Schema{
  1269  								Type: "string",
  1270  							},
  1271  						},
  1272  						Extension: map[string]interface{}{
  1273  							"x-kubernetes-immutable": true,
  1274  						},
  1275  					},
  1276  				},
  1277  				Extension: map[string]interface{}{
  1278  					"x-dcl-parent-container": "project",
  1279  				},
  1280  			},
  1281  			response: admission.Errored(http.StatusForbidden,
  1282  				k8s.NewImmutableFieldsMutationError([]string{"spec.arrayMapKey"})),
  1283  		},
  1284  		{
  1285  			name: "changes on immutable maps of arrays (deleted a value from one of the arrays in the map)",
  1286  			obj: &unstructured.Unstructured{
  1287  				Object: map[string]interface{}{
  1288  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
  1289  					"kind":       "Test1Bar",
  1290  					"metadata": map[string]interface{}{
  1291  						"annotations": map[string]interface{}{
  1292  							k8s.ProjectIDAnnotation: "my-project-1",
  1293  						},
  1294  					},
  1295  				},
  1296  			},
  1297  			oldObj: &unstructured.Unstructured{
  1298  				Object: map[string]interface{}{
  1299  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
  1300  					"kind":       "Test1Bar",
  1301  					"metadata": map[string]interface{}{
  1302  						"annotations": map[string]interface{}{
  1303  							k8s.ProjectIDAnnotation: "my-project-1",
  1304  						},
  1305  					},
  1306  				},
  1307  			},
  1308  			spec: map[string]interface{}{
  1309  				"arrayMapKey": map[string]interface{}{
  1310  					"arr1": []interface{}{
  1311  						"foo1",
  1312  					},
  1313  					"arr2": []interface{}{
  1314  						"foo2",
  1315  						"foo22",
  1316  					},
  1317  				},
  1318  			},
  1319  			oldSpec: map[string]interface{}{
  1320  				"arrayMapKey": map[string]interface{}{
  1321  					"arr1": []interface{}{
  1322  						"foo1",
  1323  						"foo11",
  1324  					},
  1325  					"arr2": []interface{}{
  1326  						"foo2",
  1327  						"foo22",
  1328  					},
  1329  				},
  1330  			},
  1331  			schema: &openapi.Schema{
  1332  				Type: "object",
  1333  				Properties: map[string]*openapi.Schema{
  1334  					"arrayMapKey": &openapi.Schema{
  1335  						Type: "object",
  1336  						AdditionalProperties: &openapi.Schema{
  1337  							Type: "array",
  1338  							Items: &openapi.Schema{
  1339  								Type: "string",
  1340  							},
  1341  						},
  1342  						Extension: map[string]interface{}{
  1343  							"x-kubernetes-immutable": true,
  1344  						},
  1345  					},
  1346  				},
  1347  				Extension: map[string]interface{}{
  1348  					"x-dcl-parent-container": "project",
  1349  				},
  1350  			},
  1351  			response: admission.Errored(http.StatusForbidden,
  1352  				k8s.NewImmutableFieldsMutationError([]string{"spec.arrayMapKey"})),
  1353  		},
  1354  		{
  1355  			name: "changes on mutable maps of arrays (changed a value in one of the arrays in the map)",
  1356  			obj: &unstructured.Unstructured{
  1357  				Object: map[string]interface{}{
  1358  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
  1359  					"kind":       "Test1Bar",
  1360  					"metadata": map[string]interface{}{
  1361  						"annotations": map[string]interface{}{
  1362  							k8s.ProjectIDAnnotation: "my-project-1",
  1363  						},
  1364  					},
  1365  				},
  1366  			},
  1367  			oldObj: &unstructured.Unstructured{
  1368  				Object: map[string]interface{}{
  1369  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
  1370  					"kind":       "Test1Bar",
  1371  					"metadata": map[string]interface{}{
  1372  						"annotations": map[string]interface{}{
  1373  							k8s.ProjectIDAnnotation: "my-project-1",
  1374  						},
  1375  					},
  1376  				},
  1377  			},
  1378  			spec: map[string]interface{}{
  1379  				"arrayMapKey": map[string]interface{}{
  1380  					"arr1": []interface{}{
  1381  						"bar1",
  1382  					},
  1383  					"arr2": []interface{}{
  1384  						"foo2",
  1385  					},
  1386  				},
  1387  			},
  1388  			oldSpec: map[string]interface{}{
  1389  				"arrayMapKey": map[string]interface{}{
  1390  					"arr1": []interface{}{
  1391  						"foo1",
  1392  					},
  1393  					"arr2": []interface{}{
  1394  						"foo2",
  1395  					},
  1396  				},
  1397  			},
  1398  			schema: &openapi.Schema{
  1399  				Type: "object",
  1400  				Properties: map[string]*openapi.Schema{
  1401  					"arrayMapKey": &openapi.Schema{
  1402  						Type: "object",
  1403  						AdditionalProperties: &openapi.Schema{
  1404  							Type: "array",
  1405  							Items: &openapi.Schema{
  1406  								Type: "string",
  1407  							},
  1408  						},
  1409  					},
  1410  				},
  1411  				Extension: map[string]interface{}{
  1412  					"x-dcl-parent-container": "project",
  1413  				},
  1414  			},
  1415  			response: allowedResponse,
  1416  		},
  1417  		{
  1418  			name: "changes the container annotation",
  1419  			obj: &unstructured.Unstructured{
  1420  				Object: map[string]interface{}{
  1421  					"apiVersion": "test4.cnrm.cloud.google.com/v1alpha1",
  1422  					"kind":       "Test4ProjectContainer",
  1423  					"metadata": map[string]interface{}{
  1424  						"annotations": map[string]interface{}{
  1425  							k8s.ProjectIDAnnotation: "my-project-1",
  1426  						},
  1427  					},
  1428  				},
  1429  			},
  1430  			oldObj: &unstructured.Unstructured{
  1431  				Object: map[string]interface{}{
  1432  					"apiVersion": "test4.cnrm.cloud.google.com/v1alpha1",
  1433  					"kind":       "Test4ProjectContainer",
  1434  					"metadata": map[string]interface{}{
  1435  						"annotations": map[string]interface{}{
  1436  							k8s.ProjectIDAnnotation: "my-project-2",
  1437  						},
  1438  					},
  1439  				},
  1440  			},
  1441  			spec:    map[string]interface{}{},
  1442  			oldSpec: map[string]interface{}{},
  1443  			schema: &openapi.Schema{
  1444  				Type: "object",
  1445  				Extension: map[string]interface{}{
  1446  					"x-dcl-parent-container": "project",
  1447  				},
  1448  			},
  1449  			response: admission.Errored(http.StatusBadRequest,
  1450  				fmt.Errorf("error validating container annotations: cannot make changes to container annotation cnrm.cloud.google.com/project-id")),
  1451  		},
  1452  		{
  1453  			name: "changes on mutable fields",
  1454  			obj: &unstructured.Unstructured{
  1455  				Object: map[string]interface{}{
  1456  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
  1457  					"kind":       "Test1Bar",
  1458  					"metadata": map[string]interface{}{
  1459  						"annotations": map[string]interface{}{
  1460  							k8s.ProjectIDAnnotation: "my-project-1",
  1461  						},
  1462  					},
  1463  				},
  1464  			},
  1465  			oldObj: &unstructured.Unstructured{
  1466  				Object: map[string]interface{}{
  1467  					"apiVersion": "test1.cnrm.cloud.google.com/v1alpha1",
  1468  					"kind":       "Test1Bar",
  1469  					"metadata": map[string]interface{}{
  1470  						"annotations": map[string]interface{}{
  1471  							k8s.ProjectIDAnnotation: "my-project-1",
  1472  						},
  1473  					},
  1474  				},
  1475  			},
  1476  			spec: map[string]interface{}{
  1477  				"location": "EU",
  1478  				"nestedObjectKey": map[string]interface{}{
  1479  					"nestedIntField":    1,
  1480  					"nestedStringField": "strval1",
  1481  				},
  1482  			},
  1483  			oldSpec: map[string]interface{}{
  1484  				"location": "US",
  1485  				"nestedObjectKey": map[string]interface{}{
  1486  					"nestedIntField":    2,
  1487  					"nestedStringField": "strval2",
  1488  				},
  1489  			},
  1490  			schema: &openapi.Schema{
  1491  				Type: "object",
  1492  				Properties: map[string]*openapi.Schema{
  1493  					"location": &openapi.Schema{
  1494  						Type: "string",
  1495  					},
  1496  					"nestedObjectKey": &openapi.Schema{
  1497  						Type: "object",
  1498  						Properties: map[string]*openapi.Schema{
  1499  							"nestedIntField": &openapi.Schema{
  1500  								Type: "integer",
  1501  							},
  1502  							"nestedStringField": &openapi.Schema{
  1503  								Type: "string",
  1504  							},
  1505  						},
  1506  					},
  1507  				},
  1508  				Extension: map[string]interface{}{
  1509  					"x-dcl-parent-container": "project",
  1510  				},
  1511  			},
  1512  			response: allowedResponse,
  1513  		},
  1514  	}
  1515  
  1516  	smLoader := dclmetadata.NewFromServiceList(testservicemetadataloader.FakeServiceMetadataWithHierarchicalResources())
  1517  	for _, tc := range tests {
  1518  		tc := tc
  1519  		t.Run(tc.name, func(t *testing.T) {
  1520  			dclSchemaKey := testdclschemaloader.DCLSchemaKeyForGVK(t, tc.obj.GroupVersionKind(), smLoader)
  1521  			dclSchemaMap := make(map[string]*openapi.Schema)
  1522  			dclSchemaMap[dclSchemaKey] = tc.schema
  1523  			dclSchemaLoader := testdclschemaloader.New(dclSchemaMap)
  1524  			actual := validateImmutableFieldsForDCLBasedResource(tc.obj, tc.oldObj, tc.spec, tc.oldSpec, dclSchemaLoader, smLoader)
  1525  			if !testutil.Equals(t, actual, tc.response) {
  1526  				t.Fatalf("got: %v, but want: %v", actual, tc.response)
  1527  			}
  1528  		})
  1529  	}
  1530  }
  1531  
  1532  func assertImmutableFieldsValidatorResult(t *testing.T, v *immutableFieldsValidatorHandler, provider *schema.Provider, testCase TestCase) {
  1533  	r, ok := provider.ResourcesMap[testCase.TFSchemaName]
  1534  	if !ok {
  1535  		t.Errorf("couldn't get the schema for %v", testCase.TFSchemaName)
  1536  	}
  1537  	fields := list.New()
  1538  	compareAndFindChangesOnImmutableFields(testCase.Spec, testCase.OldSpec, r.Schema, "", testCase.ResourceConfig, nil, fields)
  1539  
  1540  	res := make([]string, 0)
  1541  	for e := fields.Front(); e != nil; e = e.Next() {
  1542  		res = append(res, e.Value.(string))
  1543  	}
  1544  	if !reflect.DeepEqual(testCase.ExpectedResult, res) {
  1545  		t.Errorf("expected to find changes on immutable location field %v, instead get %v", testCase.ExpectedResult, res)
  1546  	}
  1547  }
  1548  
  1549  func TestChangesOnImmutableLocationField(t *testing.T) {
  1550  	spec := map[string]interface{}{
  1551  		"location": "us-east1",
  1552  	}
  1553  
  1554  	oldSpec := map[string]interface{}{
  1555  		"location": "us-west1",
  1556  	}
  1557  
  1558  	rc := &corekccv1alpha1.ResourceConfig{
  1559  		Locationality: "regional",
  1560  	}
  1561  
  1562  	found := findChangesOnImmutableLocationField(spec, oldSpec, rc)
  1563  	if !found {
  1564  		t.Errorf("expected to find changes on immutable location field")
  1565  	}
  1566  }
  1567  
  1568  func TestChangesOnImmutableResourceIDField(t *testing.T) {
  1569  	tests := []struct {
  1570  		name           string
  1571  		spec           map[string]interface{}
  1572  		oldSpec        map[string]interface{}
  1573  		rc             *corekccv1alpha1.ResourceConfig
  1574  		expectedResult bool
  1575  	}{
  1576  		{
  1577  			name:    "resource ID not changed",
  1578  			spec:    map[string]interface{}{k8s.ResourceIDFieldName: "test-id"},
  1579  			oldSpec: map[string]interface{}{k8s.ResourceIDFieldName: "test-id"},
  1580  			rc: &corekccv1alpha1.ResourceConfig{
  1581  				ResourceID: corekccv1alpha1.ResourceID{
  1582  					TargetField: "test_field",
  1583  				},
  1584  			},
  1585  			expectedResult: false,
  1586  		},
  1587  		{
  1588  			name:    "resource ID changed",
  1589  			spec:    map[string]interface{}{k8s.ResourceIDFieldName: "updated-id"},
  1590  			oldSpec: map[string]interface{}{k8s.ResourceIDFieldName: "test-id"},
  1591  			rc: &corekccv1alpha1.ResourceConfig{
  1592  				ResourceID: corekccv1alpha1.ResourceID{
  1593  					TargetField: "test_field",
  1594  				},
  1595  			},
  1596  			expectedResult: true,
  1597  		},
  1598  		{
  1599  			name:           "resource ID not supported",
  1600  			spec:           map[string]interface{}{},
  1601  			oldSpec:        map[string]interface{}{},
  1602  			rc:             &corekccv1alpha1.ResourceConfig{},
  1603  			expectedResult: false,
  1604  		},
  1605  	}
  1606  
  1607  	for _, tc := range tests {
  1608  		tc := tc
  1609  		t.Run(tc.name, func(t *testing.T) {
  1610  			if got, want :=
  1611  				findChangesOnImmutableResourceIDField(tc.spec, tc.oldSpec, tc.rc),
  1612  				tc.expectedResult; got != want {
  1613  				t.Errorf("unexpected result finding changes on %q "+
  1614  					"field: got %t, want %t", k8s.ResourceIDFieldPath, got, want)
  1615  			}
  1616  		})
  1617  	}
  1618  }
  1619  
  1620  func newImmutableFieldsValidatorHandler(t *testing.T) *immutableFieldsValidatorHandler {
  1621  	t.Helper()
  1622  	smLoader, err := servicemappingloader.New()
  1623  	if err != nil {
  1624  		t.Fatal(err)
  1625  	}
  1626  
  1627  	return NewImmutableFieldsValidatorHandler(smLoader, nil, testservicemetadataloader.NewForUnitTest())
  1628  }
  1629  
  1630  func TestValidateContainerAnnotations(t *testing.T) {
  1631  	tests := []struct {
  1632  		name             string
  1633  		kind             string
  1634  		old              map[string]string
  1635  		updated          map[string]string
  1636  		containers       []corekccv1alpha1.Container
  1637  		hierarchicalRefs []corekccv1alpha1.HierarchicalReference
  1638  		shouldErr        bool
  1639  	}{
  1640  		{
  1641  			name:    "changing project ID is not allowed",
  1642  			kind:    "GenericKind",
  1643  			old:     map[string]string{k8s.ProjectIDAnnotation: "project-1"},
  1644  			updated: map[string]string{k8s.ProjectIDAnnotation: "project-2"},
  1645  			containers: []corekccv1alpha1.Container{
  1646  				{Type: corekccv1alpha1.ContainerTypeProject},
  1647  			},
  1648  			shouldErr: true,
  1649  		},
  1650  		{
  1651  			name:    "changing folder ID is not allowed (in the general case)",
  1652  			kind:    "GenericKind",
  1653  			old:     map[string]string{k8s.FolderIDAnnotation: "123321"},
  1654  			updated: map[string]string{k8s.FolderIDAnnotation: "321123"},
  1655  			containers: []corekccv1alpha1.Container{
  1656  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1657  			},
  1658  			shouldErr: true,
  1659  		},
  1660  		{
  1661  			name:    "changing folder ID is allowed for Projects",
  1662  			kind:    "Project",
  1663  			old:     map[string]string{k8s.FolderIDAnnotation: "123321"},
  1664  			updated: map[string]string{k8s.FolderIDAnnotation: "321123"},
  1665  			containers: []corekccv1alpha1.Container{
  1666  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1667  			},
  1668  		},
  1669  		{
  1670  			name:    "changing folder ID is allowed for Folders",
  1671  			kind:    "Folder",
  1672  			old:     map[string]string{k8s.FolderIDAnnotation: "123321"},
  1673  			updated: map[string]string{k8s.FolderIDAnnotation: "321123"},
  1674  			containers: []corekccv1alpha1.Container{
  1675  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1676  			},
  1677  		},
  1678  		{
  1679  			name:    "changing org ID is not allowed (in the general case)",
  1680  			kind:    "GenericKind",
  1681  			old:     map[string]string{k8s.OrgIDAnnotation: "0987654"},
  1682  			updated: map[string]string{k8s.OrgIDAnnotation: "1234567"},
  1683  			containers: []corekccv1alpha1.Container{
  1684  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1685  			},
  1686  			shouldErr: true,
  1687  		},
  1688  		{
  1689  			name:    "changing org ID is allowed for Projects",
  1690  			kind:    "Project",
  1691  			old:     map[string]string{k8s.OrgIDAnnotation: "123321"},
  1692  			updated: map[string]string{k8s.OrgIDAnnotation: "321123"},
  1693  			containers: []corekccv1alpha1.Container{
  1694  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1695  			},
  1696  		},
  1697  		{
  1698  			name:    "changing org ID is allowed for Folders",
  1699  			kind:    "Folder",
  1700  			old:     map[string]string{k8s.OrgIDAnnotation: "123321"},
  1701  			updated: map[string]string{k8s.OrgIDAnnotation: "321123"},
  1702  			containers: []corekccv1alpha1.Container{
  1703  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1704  			},
  1705  		},
  1706  		{
  1707  			name:    "changing from project ID to folder ID is not allowed",
  1708  			kind:    "GenericKind",
  1709  			old:     map[string]string{k8s.ProjectIDAnnotation: "project-1"},
  1710  			updated: map[string]string{k8s.FolderIDAnnotation: "123321"},
  1711  			containers: []corekccv1alpha1.Container{
  1712  				{Type: corekccv1alpha1.ContainerTypeProject},
  1713  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1714  			},
  1715  			shouldErr: true,
  1716  		},
  1717  		{
  1718  			name:    "changing from project ID to org ID is not allowed",
  1719  			kind:    "GenericKind",
  1720  			old:     map[string]string{k8s.ProjectIDAnnotation: "project-1"},
  1721  			updated: map[string]string{k8s.OrgIDAnnotation: "0987654"},
  1722  			containers: []corekccv1alpha1.Container{
  1723  				{Type: corekccv1alpha1.ContainerTypeProject},
  1724  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1725  			},
  1726  			shouldErr: true,
  1727  		},
  1728  		{
  1729  			name:    "changing from folder ID to project ID is not allowed",
  1730  			kind:    "GenericKind",
  1731  			old:     map[string]string{k8s.FolderIDAnnotation: "123321"},
  1732  			updated: map[string]string{k8s.ProjectIDAnnotation: "project-1"},
  1733  			containers: []corekccv1alpha1.Container{
  1734  				{Type: corekccv1alpha1.ContainerTypeProject},
  1735  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1736  			},
  1737  			shouldErr: true,
  1738  		},
  1739  		{
  1740  			name:    "changing from folder ID to org ID is not allowed",
  1741  			kind:    "GenericKind",
  1742  			old:     map[string]string{k8s.FolderIDAnnotation: "123321"},
  1743  			updated: map[string]string{k8s.OrgIDAnnotation: "0987654"},
  1744  			containers: []corekccv1alpha1.Container{
  1745  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1746  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1747  			},
  1748  			shouldErr: true,
  1749  		},
  1750  		{
  1751  			name:    "changing from org ID to project ID is not allowed",
  1752  			kind:    "GenericKind",
  1753  			old:     map[string]string{k8s.OrgIDAnnotation: "0987654"},
  1754  			updated: map[string]string{k8s.ProjectIDAnnotation: "project-1"},
  1755  			containers: []corekccv1alpha1.Container{
  1756  				{Type: corekccv1alpha1.ContainerTypeProject},
  1757  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1758  			},
  1759  			shouldErr: true,
  1760  		},
  1761  		{
  1762  			name:    "changing from org ID to folder ID is not allowed",
  1763  			kind:    "GenericKind",
  1764  			old:     map[string]string{k8s.OrgIDAnnotation: "0987654"},
  1765  			updated: map[string]string{k8s.FolderIDAnnotation: "123321"},
  1766  			containers: []corekccv1alpha1.Container{
  1767  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1768  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1769  			},
  1770  			shouldErr: true,
  1771  		},
  1772  		{
  1773  			name:    "changing from folder ID to org ID is not allowed for Projects",
  1774  			kind:    "Project",
  1775  			old:     map[string]string{k8s.FolderIDAnnotation: "123321"},
  1776  			updated: map[string]string{k8s.OrgIDAnnotation: "0987654"},
  1777  			containers: []corekccv1alpha1.Container{
  1778  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1779  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1780  			},
  1781  			shouldErr: true,
  1782  		},
  1783  		{
  1784  			name:    "changing from org ID to folder ID is not allowed for Projects",
  1785  			kind:    "Project",
  1786  			old:     map[string]string{k8s.OrgIDAnnotation: "0987654"},
  1787  			updated: map[string]string{k8s.FolderIDAnnotation: "123321"},
  1788  			containers: []corekccv1alpha1.Container{
  1789  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1790  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1791  			},
  1792  			shouldErr: true,
  1793  		},
  1794  		{
  1795  			name:    "changing from folder ID to org ID is not allowed for Folders",
  1796  			kind:    "Folder",
  1797  			old:     map[string]string{k8s.FolderIDAnnotation: "123321"},
  1798  			updated: map[string]string{k8s.OrgIDAnnotation: "0987654"},
  1799  			containers: []corekccv1alpha1.Container{
  1800  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1801  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1802  			},
  1803  			shouldErr: true,
  1804  		},
  1805  		{
  1806  			name:    "changing from org ID to folder ID is not allowed for Folders",
  1807  			kind:    "Folder",
  1808  			old:     map[string]string{k8s.OrgIDAnnotation: "0987654"},
  1809  			updated: map[string]string{k8s.FolderIDAnnotation: "123321"},
  1810  			containers: []corekccv1alpha1.Container{
  1811  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1812  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1813  			},
  1814  			shouldErr: true,
  1815  		},
  1816  		{
  1817  			name:    "changing project ID is not allowed if if resource supports hierarchical references",
  1818  			kind:    "GenericKind",
  1819  			old:     map[string]string{k8s.ProjectIDAnnotation: "project-1"},
  1820  			updated: map[string]string{k8s.ProjectIDAnnotation: "project-2"},
  1821  			containers: []corekccv1alpha1.Container{
  1822  				{Type: corekccv1alpha1.ContainerTypeProject},
  1823  			},
  1824  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
  1825  				{Type: corekccv1alpha1.HierarchicalReferenceTypeProject},
  1826  			},
  1827  			shouldErr: true,
  1828  		},
  1829  		{
  1830  			name:    "changing folder ID is not allowed if resource supports hierarchical references (in the general case)",
  1831  			kind:    "GenericKind",
  1832  			old:     map[string]string{k8s.FolderIDAnnotation: "123321"},
  1833  			updated: map[string]string{k8s.FolderIDAnnotation: "321123"},
  1834  			containers: []corekccv1alpha1.Container{
  1835  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1836  			},
  1837  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
  1838  				{Type: corekccv1alpha1.HierarchicalReferenceTypeFolder},
  1839  			},
  1840  			shouldErr: true,
  1841  		},
  1842  		{
  1843  			name:    "changing org ID is not allowed if resource supports hierarchical references (in the general case)",
  1844  			kind:    "GenericKind",
  1845  			old:     map[string]string{k8s.OrgIDAnnotation: "0987654"},
  1846  			updated: map[string]string{k8s.OrgIDAnnotation: "1234567"},
  1847  			containers: []corekccv1alpha1.Container{
  1848  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1849  			},
  1850  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
  1851  				{Type: corekccv1alpha1.HierarchicalReferenceTypeOrganization},
  1852  			},
  1853  			shouldErr: true,
  1854  		},
  1855  		{
  1856  			name:    "changing folder ID is not allowed for Projects once Project supports hierarchical references",
  1857  			kind:    "Project",
  1858  			old:     map[string]string{k8s.FolderIDAnnotation: "123321"},
  1859  			updated: map[string]string{k8s.FolderIDAnnotation: "321123"},
  1860  			containers: []corekccv1alpha1.Container{
  1861  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1862  			},
  1863  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
  1864  				{Type: corekccv1alpha1.HierarchicalReferenceTypeFolder},
  1865  			},
  1866  			shouldErr: true,
  1867  		},
  1868  		{
  1869  			name:    "changing folder ID is not allowed for Folders once Folder supports hierarchical references",
  1870  			kind:    "Folder",
  1871  			old:     map[string]string{k8s.FolderIDAnnotation: "123321"},
  1872  			updated: map[string]string{k8s.FolderIDAnnotation: "321123"},
  1873  			containers: []corekccv1alpha1.Container{
  1874  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1875  			},
  1876  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
  1877  				{Type: corekccv1alpha1.HierarchicalReferenceTypeFolder},
  1878  			},
  1879  			shouldErr: true,
  1880  		},
  1881  		{
  1882  			name:    "changing org ID is not allowed for Projects once Project supports hierarchical references",
  1883  			kind:    "Project",
  1884  			old:     map[string]string{k8s.OrgIDAnnotation: "123321"},
  1885  			updated: map[string]string{k8s.OrgIDAnnotation: "321123"},
  1886  			containers: []corekccv1alpha1.Container{
  1887  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1888  			},
  1889  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
  1890  				{Type: corekccv1alpha1.HierarchicalReferenceTypeOrganization},
  1891  			},
  1892  			shouldErr: true,
  1893  		},
  1894  		{
  1895  			name:    "changing org ID is not allowed for Folders once Folder supports hierarchical references",
  1896  			kind:    "Folder",
  1897  			old:     map[string]string{k8s.OrgIDAnnotation: "123321"},
  1898  			updated: map[string]string{k8s.OrgIDAnnotation: "321123"},
  1899  			containers: []corekccv1alpha1.Container{
  1900  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1901  			},
  1902  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
  1903  				{Type: corekccv1alpha1.HierarchicalReferenceTypeOrganization},
  1904  			},
  1905  			shouldErr: true,
  1906  		},
  1907  		{
  1908  			name:    "removing project ID is allowed if resource supports hierarchical references",
  1909  			kind:    "GenericKind",
  1910  			old:     map[string]string{k8s.ProjectIDAnnotation: "project-1"},
  1911  			updated: map[string]string{},
  1912  			containers: []corekccv1alpha1.Container{
  1913  				{Type: corekccv1alpha1.ContainerTypeProject},
  1914  			},
  1915  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
  1916  				{Type: corekccv1alpha1.HierarchicalReferenceTypeProject},
  1917  			},
  1918  		},
  1919  		{
  1920  			name:    "removing folder ID is allowed if resource supports hierarchical references",
  1921  			kind:    "GenericKind",
  1922  			old:     map[string]string{k8s.FolderIDAnnotation: "123321"},
  1923  			updated: map[string]string{},
  1924  			containers: []corekccv1alpha1.Container{
  1925  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1926  			},
  1927  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
  1928  				{Type: corekccv1alpha1.HierarchicalReferenceTypeFolder},
  1929  			},
  1930  		},
  1931  		{
  1932  			name:    "removing org ID is allowed if resource supports hierarchical references",
  1933  			kind:    "GenericKind",
  1934  			old:     map[string]string{k8s.OrgIDAnnotation: "123321"},
  1935  			updated: map[string]string{},
  1936  			containers: []corekccv1alpha1.Container{
  1937  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1938  			},
  1939  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
  1940  				{Type: corekccv1alpha1.HierarchicalReferenceTypeOrganization},
  1941  			},
  1942  		},
  1943  		{
  1944  			name:    "adding project ID is not allowed if resource supports hierarchical references",
  1945  			kind:    "GenericKind",
  1946  			old:     map[string]string{},
  1947  			updated: map[string]string{k8s.ProjectIDAnnotation: "project-1"},
  1948  			containers: []corekccv1alpha1.Container{
  1949  				{Type: corekccv1alpha1.ContainerTypeProject},
  1950  			},
  1951  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
  1952  				{Type: corekccv1alpha1.HierarchicalReferenceTypeProject},
  1953  			},
  1954  			shouldErr: true,
  1955  		},
  1956  		{
  1957  			name:    "adding folder ID is not allowed if resource supports hierarchical references",
  1958  			kind:    "GenericKind",
  1959  			old:     map[string]string{},
  1960  			updated: map[string]string{k8s.FolderIDAnnotation: "123321"},
  1961  			containers: []corekccv1alpha1.Container{
  1962  				{Type: corekccv1alpha1.ContainerTypeFolder},
  1963  			},
  1964  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
  1965  				{Type: corekccv1alpha1.HierarchicalReferenceTypeFolder},
  1966  			},
  1967  			shouldErr: true,
  1968  		},
  1969  		{
  1970  			name:    "adding org ID is not allowed if resource supports hierarchical references",
  1971  			kind:    "GenericKind",
  1972  			old:     map[string]string{},
  1973  			updated: map[string]string{k8s.OrgIDAnnotation: "123321"},
  1974  			containers: []corekccv1alpha1.Container{
  1975  				{Type: corekccv1alpha1.ContainerTypeOrganization},
  1976  			},
  1977  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
  1978  				{Type: corekccv1alpha1.HierarchicalReferenceTypeOrganization},
  1979  			},
  1980  			shouldErr: true,
  1981  		},
  1982  		{
  1983  			name: "adding a different annotation is allowed",
  1984  			kind: "GenericKind",
  1985  			old:  map[string]string{k8s.ProjectIDAnnotation: "project-1"},
  1986  			updated: map[string]string{
  1987  				k8s.ProjectIDAnnotation: "project-1",
  1988  				"key":                   "value",
  1989  			},
  1990  			containers: []corekccv1alpha1.Container{
  1991  				{Type: corekccv1alpha1.ContainerTypeProject},
  1992  			},
  1993  		},
  1994  		{
  1995  			name:    "changing an unrecognized container annotation is allowed",
  1996  			kind:    "GenericKind",
  1997  			old:     map[string]string{k8s.ProjectIDAnnotation: "project-1"},
  1998  			updated: map[string]string{k8s.ProjectIDAnnotation: "project-2"},
  1999  			containers: []corekccv1alpha1.Container{
  2000  				{Type: corekccv1alpha1.ContainerTypeFolder},
  2001  			},
  2002  		},
  2003  	}
  2004  	for _, tc := range tests {
  2005  		tc := tc
  2006  		t.Run(tc.name, func(t *testing.T) {
  2007  			err := validateContainerAnnotationsForResource(tc.kind, tc.updated, tc.old, tc.containers, tc.hierarchicalRefs)
  2008  			if tc.shouldErr && err == nil {
  2009  				t.Errorf("expected error but there was none")
  2010  				return
  2011  			} else if !tc.shouldErr && err != nil {
  2012  				t.Errorf("got unexpected error: %v", err)
  2013  			}
  2014  		})
  2015  	}
  2016  }
  2017  
  2018  func newUnstructuredFromObject(t *testing.T, value interface{}) *unstructured.Unstructured {
  2019  	var mapResult map[string]interface{}
  2020  	if err := util.Marshal(value, &mapResult); err != nil {
  2021  		t.Fatalf("unable to marshal %v to map: %v", reflect.TypeOf(value).Name(), err)
  2022  	}
  2023  	return &unstructured.Unstructured{
  2024  		Object: mapResult,
  2025  	}
  2026  }
  2027  
  2028  type TestCase struct {
  2029  	Name           string
  2030  	Spec           map[string]interface{}
  2031  	OldSpec        map[string]interface{}
  2032  	TFSchemaName   string
  2033  	ResourceConfig *corekccv1alpha1.ResourceConfig
  2034  	ExpectedResult []string
  2035  }
  2036  
  2037  var TestCases = []TestCase{
  2038  	{
  2039  		Name: "changesOnBaseLevelImmutableField",
  2040  		Spec: map[string]interface{}{
  2041  			"location": "EU",
  2042  		},
  2043  		OldSpec: map[string]interface{}{
  2044  			"location": "US",
  2045  		},
  2046  		TFSchemaName:   "google_bigquery_dataset",
  2047  		ResourceConfig: &corekccv1alpha1.ResourceConfig{},
  2048  		ExpectedResult: []string{"location"},
  2049  	},
  2050  	{
  2051  		Name: "changesOnNestedImmutableField",
  2052  		Spec: map[string]interface{}{
  2053  			"databaseVersion": "MYSQL_5_7",
  2054  			"replicaConfiguration": map[string]interface{}{
  2055  				"connectRetryInterval": 2,
  2056  			},
  2057  		},
  2058  		OldSpec: map[string]interface{}{
  2059  			"databaseVersion": "MYSQL_5_7",
  2060  			"replicaConfiguration": map[string]interface{}{
  2061  				"connectRetryInterval": 4,
  2062  			},
  2063  		},
  2064  		TFSchemaName:   "google_sql_database_instance",
  2065  		ResourceConfig: &corekccv1alpha1.ResourceConfig{},
  2066  		ExpectedResult: []string{"replica_configuration.connect_retry_interval"},
  2067  	},
  2068  	{
  2069  		Name: "changesOnImmutableReferenceSingleton",
  2070  		Spec: map[string]interface{}{
  2071  			"topicRef": map[string]interface{}{
  2072  				"name": "pubsubtopic-sample-1",
  2073  			},
  2074  		},
  2075  		OldSpec: map[string]interface{}{
  2076  			"topicRef": map[string]interface{}{
  2077  				"name": "pubsubtopic-sample-2",
  2078  			},
  2079  		},
  2080  		TFSchemaName: "google_pubsub_subscription",
  2081  		ResourceConfig: &corekccv1alpha1.ResourceConfig{
  2082  			ResourceReferences: []corekccv1alpha1.ReferenceConfig{
  2083  				{
  2084  					TypeConfig: corekccv1alpha1.TypeConfig{
  2085  						Key: "topicRef",
  2086  						GVK: k8sschema.GroupVersionKind{
  2087  							Kind: "PubsubTopic",
  2088  						},
  2089  					},
  2090  					TFField: "topic",
  2091  				},
  2092  			},
  2093  		},
  2094  		ExpectedResult: []string{"topicRef"},
  2095  	},
  2096  	{
  2097  		Name: "changesOnMutableNestedReference",
  2098  		Spec: map[string]interface{}{
  2099  			"peeringConfig": map[string]interface{}{
  2100  				"targetNetwork": map[string]interface{}{
  2101  					"networkRef": map[string]interface{}{
  2102  						"name": "ref1",
  2103  					},
  2104  				},
  2105  			},
  2106  		},
  2107  		OldSpec: map[string]interface{}{
  2108  			"peeringConfig": map[string]interface{}{
  2109  				"targetNetwork": map[string]interface{}{
  2110  					"networkRef": map[string]interface{}{
  2111  						"name": "ref2",
  2112  					},
  2113  				},
  2114  			},
  2115  		},
  2116  		TFSchemaName: "google_dns_managed_zone",
  2117  		ResourceConfig: &corekccv1alpha1.ResourceConfig{
  2118  			ResourceReferences: []corekccv1alpha1.ReferenceConfig{
  2119  				{
  2120  					TypeConfig: corekccv1alpha1.TypeConfig{
  2121  						Key: "networkRef",
  2122  						GVK: k8sschema.GroupVersionKind{
  2123  							Kind: "ComputeNetwork",
  2124  						},
  2125  					},
  2126  					TFField: "peering_config.target_network.network_url",
  2127  				},
  2128  			},
  2129  		},
  2130  		ExpectedResult: []string{},
  2131  	},
  2132  	{
  2133  		Name: "changesOnImmutableComplexReference",
  2134  		Spec: map[string]interface{}{
  2135  			"ipAddress": map[string]interface{}{
  2136  				"addressRef": map[string]interface{}{
  2137  					"name": "ref1",
  2138  				},
  2139  			},
  2140  		},
  2141  		OldSpec: map[string]interface{}{
  2142  			"ipAddress": map[string]interface{}{
  2143  				"ip": "8.8.8.8",
  2144  			},
  2145  		},
  2146  		TFSchemaName: "google_compute_forwarding_rule",
  2147  		ResourceConfig: &corekccv1alpha1.ResourceConfig{
  2148  			ResourceReferences: []corekccv1alpha1.ReferenceConfig{
  2149  				{
  2150  					Types: []corekccv1alpha1.TypeConfig{
  2151  						{
  2152  							Key: "addressRef",
  2153  						},
  2154  						{
  2155  							Key: "ip",
  2156  						},
  2157  					},
  2158  					TFField: "ip_address",
  2159  				},
  2160  			},
  2161  		},
  2162  		ExpectedResult: []string{"ipAddress"},
  2163  	},
  2164  	// (TODO:maqiuyu): Add a test case for resourceID once supported.
  2165  }
  2166  
  2167  func TestUpdateIAMPolicy(t *testing.T) {
  2168  	policy := v1beta1.IAMPolicy{
  2169  		TypeMeta: metav1.TypeMeta{
  2170  			Kind:       v1beta1.IAMPolicyGVK.Kind,
  2171  			APIVersion: v1beta1.IAMPolicyGVK.GroupVersion().String(),
  2172  		},
  2173  		Spec: v1beta1.IAMPolicySpec{
  2174  			ResourceReference: v1beta1.ResourceReference{
  2175  				Kind:       "my-resource-kind",
  2176  				Namespace:  "my-namespace",
  2177  				Name:       "my-pubsub-topic",
  2178  				APIVersion: "my-api-version",
  2179  			},
  2180  		},
  2181  	}
  2182  	oldPolicyUnstructred := newUnstructuredFromObject(t, &policy)
  2183  	newPolicyUnstructured := newUnstructuredFromObject(t, &policy)
  2184  	assertHandleIAMPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, true)
  2185  	copyPolicy := policy
  2186  	copyPolicy.Spec.ResourceReference.Kind = "new-resource-reference-kind"
  2187  	newPolicyUnstructured = newUnstructuredFromObject(t, &copyPolicy)
  2188  	assertHandleIAMPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
  2189  	copyPolicy = policy
  2190  	copyPolicy.Spec.ResourceReference.Namespace = "new-resource-reference-namespace"
  2191  	newPolicyUnstructured = newUnstructuredFromObject(t, &copyPolicy)
  2192  	assertHandleIAMPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
  2193  	copyPolicy = policy
  2194  	copyPolicy.Spec.ResourceReference.Name = "new-resource-reference-name"
  2195  	newPolicyUnstructured = newUnstructuredFromObject(t, &copyPolicy)
  2196  	assertHandleIAMPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
  2197  	copyPolicy = policy
  2198  	copyPolicy.Spec.ResourceReference.APIVersion = "new-resource-reference-apiversion"
  2199  	newPolicyUnstructured = newUnstructuredFromObject(t, &copyPolicy)
  2200  	assertHandleIAMPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
  2201  }
  2202  
  2203  func assertHandleIAMPolicy(t *testing.T, old *unstructured.Unstructured, new *unstructured.Unstructured, expectedAllowedValue bool) {
  2204  	t.Helper()
  2205  	oldSpec := getSpecFromUnstructed(t, old)
  2206  	newSpec := getSpecFromUnstructed(t, new)
  2207  	response := handleIAMPolicy(oldSpec, newSpec)
  2208  	if response.Allowed != expectedAllowedValue {
  2209  		t.Fatalf("unexpected value for Allowed: got '%v', want '%v'", response.Allowed, expectedAllowedValue)
  2210  	}
  2211  }
  2212  
  2213  func TestUpdateIAMPartialPolicy(t *testing.T) {
  2214  	policy := v1beta1.IAMPartialPolicy{
  2215  		TypeMeta: metav1.TypeMeta{
  2216  			Kind:       v1beta1.IAMPolicyGVK.Kind,
  2217  			APIVersion: v1beta1.IAMPolicyGVK.GroupVersion().String(),
  2218  		},
  2219  		Spec: v1beta1.IAMPartialPolicySpec{
  2220  			ResourceReference: v1beta1.ResourceReference{
  2221  				Kind:       "my-resource-kind",
  2222  				Namespace:  "my-namespace",
  2223  				Name:       "my-pubsub-topic",
  2224  				APIVersion: "my-api-version",
  2225  			},
  2226  		},
  2227  	}
  2228  	oldPolicyUnstructred := newUnstructuredFromObject(t, &policy)
  2229  	newPolicyUnstructured := newUnstructuredFromObject(t, &policy)
  2230  	assertHandleIAMPartialPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, true)
  2231  	copyPolicy := policy
  2232  	copyPolicy.Spec.ResourceReference.Kind = "new-resource-reference-kind"
  2233  	newPolicyUnstructured = newUnstructuredFromObject(t, &copyPolicy)
  2234  	assertHandleIAMPartialPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
  2235  	copyPolicy = policy
  2236  	copyPolicy.Spec.ResourceReference.Namespace = "new-resource-reference-namespace"
  2237  	newPolicyUnstructured = newUnstructuredFromObject(t, &copyPolicy)
  2238  	assertHandleIAMPartialPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
  2239  	copyPolicy = policy
  2240  	copyPolicy.Spec.ResourceReference.Name = "new-resource-reference-name"
  2241  	newPolicyUnstructured = newUnstructuredFromObject(t, &copyPolicy)
  2242  	assertHandleIAMPartialPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
  2243  	copyPolicy = policy
  2244  	copyPolicy.Spec.ResourceReference.APIVersion = "new-resource-reference-apiversion"
  2245  	newPolicyUnstructured = newUnstructuredFromObject(t, &copyPolicy)
  2246  	assertHandleIAMPartialPolicy(t, oldPolicyUnstructred, newPolicyUnstructured, false)
  2247  }
  2248  
  2249  func assertHandleIAMPartialPolicy(t *testing.T, old *unstructured.Unstructured, new *unstructured.Unstructured, expectedAllowedValue bool) {
  2250  	t.Helper()
  2251  	oldSpec := getSpecFromUnstructed(t, old)
  2252  	newSpec := getSpecFromUnstructed(t, new)
  2253  	response := handleIAMPartialPolicy(oldSpec, newSpec)
  2254  	if response.Allowed != expectedAllowedValue {
  2255  		t.Fatalf("unexpected value for Allowed: got '%v', want '%v'", response.Allowed, expectedAllowedValue)
  2256  	}
  2257  }
  2258  
  2259  func TestUpdateIAMPolicyMember(t *testing.T) {
  2260  	policyMember := v1beta1.IAMPolicyMember{
  2261  		TypeMeta: metav1.TypeMeta{
  2262  			Kind:       v1beta1.IAMPolicyMemberGVK.Kind,
  2263  			APIVersion: v1beta1.IAMPolicyMemberGVK.GroupVersion().String(),
  2264  		},
  2265  		Spec: v1beta1.IAMPolicyMemberSpec{
  2266  			Member: "test@google.com",
  2267  			Role:   "roles/editor",
  2268  			ResourceReference: v1beta1.ResourceReference{
  2269  				Kind:       "my-resource-kind",
  2270  				Namespace:  "my-namespace",
  2271  				Name:       "my-pubsub-topic",
  2272  				APIVersion: "my-api-version",
  2273  			},
  2274  		},
  2275  	}
  2276  	oldPolicyMemberUnstructred := newUnstructuredFromObject(t, &policyMember)
  2277  	newPolicyMemberUnstructured := newUnstructuredFromObject(t, &policyMember)
  2278  	assertHandleIAMPolicyMember(t, oldPolicyMemberUnstructred, newPolicyMemberUnstructured, true)
  2279  	copyPolicyMember := policyMember
  2280  	copyPolicyMember.Spec.Member = "new-member"
  2281  	newPolicyMemberUnstructured = newUnstructuredFromObject(t, &copyPolicyMember)
  2282  	assertHandleIAMPolicyMember(t, oldPolicyMemberUnstructred, newPolicyMemberUnstructured, false)
  2283  	copyPolicyMember = policyMember
  2284  	copyPolicyMember.Spec.Role = "new-role"
  2285  	newPolicyMemberUnstructured = newUnstructuredFromObject(t, &copyPolicyMember)
  2286  	assertHandleIAMPolicyMember(t, oldPolicyMemberUnstructred, newPolicyMemberUnstructured, false)
  2287  	copyPolicyMember = policyMember
  2288  	copyPolicyMember.Spec.ResourceReference.Kind = "new-resource-reference-kind"
  2289  	newPolicyMemberUnstructured = newUnstructuredFromObject(t, &copyPolicyMember)
  2290  	assertHandleIAMPolicyMember(t, oldPolicyMemberUnstructred, newPolicyMemberUnstructured, false)
  2291  	copyPolicyMember = policyMember
  2292  	copyPolicyMember.Spec.ResourceReference.Namespace = "new-resource-reference-namespace"
  2293  	newPolicyMemberUnstructured = newUnstructuredFromObject(t, &copyPolicyMember)
  2294  	assertHandleIAMPolicyMember(t, oldPolicyMemberUnstructred, newPolicyMemberUnstructured, false)
  2295  	copyPolicyMember = policyMember
  2296  	copyPolicyMember.Spec.ResourceReference.Name = "new-resource-reference-name"
  2297  	newPolicyMemberUnstructured = newUnstructuredFromObject(t, &copyPolicyMember)
  2298  	assertHandleIAMPolicyMember(t, oldPolicyMemberUnstructred, newPolicyMemberUnstructured, false)
  2299  	copyPolicyMember = policyMember
  2300  	copyPolicyMember.Spec.ResourceReference.APIVersion = "new-resource-reference-apiversion"
  2301  	newPolicyMemberUnstructured = newUnstructuredFromObject(t, &copyPolicyMember)
  2302  	assertHandleIAMPolicyMember(t, oldPolicyMemberUnstructred, newPolicyMemberUnstructured, false)
  2303  }
  2304  
  2305  func assertHandleIAMPolicyMember(t *testing.T, old *unstructured.Unstructured, new *unstructured.Unstructured, expectedAllowedValue bool) {
  2306  	t.Helper()
  2307  	oldSpec := getSpecFromUnstructed(t, old)
  2308  	newSpec := getSpecFromUnstructed(t, new)
  2309  	response := handleIAMPolicyMember(oldSpec, newSpec)
  2310  	if response.Allowed != expectedAllowedValue {
  2311  		t.Fatalf("unexpected value for Allowed: got '%v', want '%v'", response.Allowed, expectedAllowedValue)
  2312  	}
  2313  }
  2314  
  2315  func TestUpdateIAMAuditConfig(t *testing.T) {
  2316  	auditConfig := v1beta1.IAMAuditConfig{
  2317  		TypeMeta: metav1.TypeMeta{
  2318  			Kind:       v1beta1.IAMAuditConfigGVK.Kind,
  2319  			APIVersion: v1beta1.IAMAuditConfigGVK.GroupVersion().String(),
  2320  		},
  2321  		Spec: v1beta1.IAMAuditConfigSpec{
  2322  			Service: "sampleservice.googleapis.com",
  2323  			AuditLogConfigs: []v1beta1.AuditLogConfig{
  2324  				{
  2325  					LogType: "DATA_READ",
  2326  					ExemptedMembers: []v1beta1.Member{
  2327  						"test@google.com",
  2328  					},
  2329  				},
  2330  			},
  2331  			ResourceReference: v1beta1.ResourceReference{
  2332  				Kind:       "my-resource-kind",
  2333  				Namespace:  "my-namespace",
  2334  				Name:       "my-pubsub-topic",
  2335  				APIVersion: "my-api-version",
  2336  			},
  2337  		},
  2338  	}
  2339  	oldAuditConfigUnstructured := newUnstructuredFromObject(t, &auditConfig)
  2340  	newAuditConfigUnstructured := newUnstructuredFromObject(t, &auditConfig)
  2341  	assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, true)
  2342  	copyAuditConfig := auditConfig
  2343  	copyAuditConfig.Spec.Service = "newservice.googleapis.com"
  2344  	newAuditConfigUnstructured = newUnstructuredFromObject(t, &copyAuditConfig)
  2345  	assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, false)
  2346  	copyAuditConfig = auditConfig
  2347  	newAuditLogConfig := v1beta1.AuditLogConfig{LogType: "DATA_WRITE"}
  2348  	copyAuditConfig.Spec.AuditLogConfigs = append(copyAuditConfig.Spec.AuditLogConfigs, newAuditLogConfig)
  2349  	newAuditConfigUnstructured = newUnstructuredFromObject(t, &copyAuditConfig)
  2350  	assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, true)
  2351  	copyAuditConfig = auditConfig
  2352  	copyAuditConfig.Spec.AuditLogConfigs[0].LogType = "ADMIN_READ"
  2353  	newAuditConfigUnstructured = newUnstructuredFromObject(t, &copyAuditConfig)
  2354  	assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, true)
  2355  	copyAuditConfig = auditConfig
  2356  	copyAuditConfig.Spec.AuditLogConfigs = nil
  2357  	newAuditConfigUnstructured = newUnstructuredFromObject(t, &copyAuditConfig)
  2358  	assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, true)
  2359  	copyAuditConfig = auditConfig
  2360  	copyAuditConfig.Spec.ResourceReference.Kind = "new-resource-reference-kind"
  2361  	newAuditConfigUnstructured = newUnstructuredFromObject(t, &copyAuditConfig)
  2362  	assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, false)
  2363  	copyAuditConfig = auditConfig
  2364  	copyAuditConfig.Spec.ResourceReference.Namespace = "new-resource-reference-namespace"
  2365  	newAuditConfigUnstructured = newUnstructuredFromObject(t, &copyAuditConfig)
  2366  	assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, false)
  2367  	copyAuditConfig = auditConfig
  2368  	copyAuditConfig.Spec.ResourceReference.Name = "new-resource-reference-name"
  2369  	newAuditConfigUnstructured = newUnstructuredFromObject(t, &copyAuditConfig)
  2370  	assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, false)
  2371  	copyAuditConfig = auditConfig
  2372  	copyAuditConfig.Spec.ResourceReference.APIVersion = "new-resource-reference-apiversion"
  2373  	newAuditConfigUnstructured = newUnstructuredFromObject(t, &copyAuditConfig)
  2374  	assertHandleIAMAuditConfig(t, oldAuditConfigUnstructured, newAuditConfigUnstructured, false)
  2375  }
  2376  
  2377  func assertHandleIAMAuditConfig(t *testing.T, old *unstructured.Unstructured, new *unstructured.Unstructured, expectedAllowedValue bool) {
  2378  	t.Helper()
  2379  	oldSpec := getSpecFromUnstructed(t, old)
  2380  	newSpec := getSpecFromUnstructed(t, new)
  2381  	response := handleIAMAuditConfig(oldSpec, newSpec)
  2382  	if response.Allowed != expectedAllowedValue {
  2383  		t.Fatalf("unexpected value for Allowed: got '%v', want '%v'", response.Allowed, expectedAllowedValue)
  2384  	}
  2385  }
  2386  
  2387  func getSpecFromUnstructed(t *testing.T, u *unstructured.Unstructured) map[string]interface{} {
  2388  	spec, ok, err := unstructured.NestedMap(u.Object, "spec")
  2389  	if err != nil {
  2390  		t.Fatalf("unexpected error retrieving spec from '%v': %v", u.Object, err)
  2391  	}
  2392  	if !ok {
  2393  		t.Fatalf("unexpected false value for 'ok' when retrieving spec from '%v'", u.Object)
  2394  	}
  2395  	return spec
  2396  }
  2397  

View as plain text