...

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

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

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package krmtotf_test
    16  
    17  import (
    18  	"encoding/json"
    19  	"testing"
    20  
    21  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
    22  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
    23  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/krmtotf"
    24  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test"
    25  	testcontroller "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/controller"
    26  	testvariable "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/resourcefixture/variable"
    27  	"github.com/google/go-cmp/cmp"
    28  	tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    31  )
    32  
    33  func TestWithFieldsPresetForRead(t *testing.T) {
    34  	nowTime := metav1.Now()
    35  	tests := []struct {
    36  		name        string
    37  		imported    map[string]interface{}
    38  		resource    *krmtotf.Resource
    39  		expectedRet map[string]interface{}
    40  	}{
    41  		{
    42  			name: "immutable fields",
    43  			imported: map[string]interface{}{
    44  				"imported_field": "imported_val",
    45  			},
    46  			resource: &krmtotf.Resource{
    47  				Resource: k8s.Resource{
    48  					Spec: map[string]interface{}{
    49  						"primitiveField": "primitive_val",
    50  						"listOfPrimitivesField": []interface{}{
    51  							"list_of_primitives_val_0",
    52  						},
    53  						"mapField": map[string]interface{}{
    54  							"map_key_a": "map_val_a",
    55  						},
    56  						"nestedObjectField": map[string]interface{}{
    57  							"immutableField": "immutable_val",
    58  							"mutableField":   "mutable_val",
    59  						},
    60  						"listOfObjectsField": []interface{}{
    61  							map[string]interface{}{
    62  								"immutableFieldA": "immutable_val_a",
    63  								"immutableFieldB": "immutable_val_b",
    64  							},
    65  							map[string]interface{}{
    66  								"immutableFieldA": "immutable_val_a",
    67  								"mutableField":    "mutable_val",
    68  							},
    69  						},
    70  					},
    71  				},
    72  				TFResource: &tfschema.Resource{
    73  					Schema: map[string]*tfschema.Schema{
    74  						"imported_field": &tfschema.Schema{
    75  							Type:     tfschema.TypeString,
    76  							Optional: true,
    77  						},
    78  						"primitive_field": &tfschema.Schema{
    79  							Type:     tfschema.TypeString,
    80  							Optional: true,
    81  							ForceNew: true,
    82  						},
    83  						"list_of_primitives_field": &tfschema.Schema{
    84  							Type:     tfschema.TypeList,
    85  							Optional: true,
    86  							Elem: &tfschema.Schema{
    87  								Type: tfschema.TypeString,
    88  							},
    89  							ForceNew: true,
    90  						},
    91  						"map_field": &tfschema.Schema{
    92  							Type:     tfschema.TypeMap,
    93  							Optional: true,
    94  							ForceNew: true,
    95  						},
    96  						"nested_object_field": &tfschema.Schema{
    97  							Type:     tfschema.TypeList,
    98  							MaxItems: 1,
    99  							Optional: true,
   100  							Elem: &tfschema.Resource{
   101  								Schema: map[string]*tfschema.Schema{
   102  									"immutable_field": &tfschema.Schema{
   103  										Type:     tfschema.TypeString,
   104  										Optional: true,
   105  										ForceNew: true,
   106  									},
   107  									"mutable_field": &tfschema.Schema{
   108  										Type:     tfschema.TypeString,
   109  										Optional: true,
   110  									},
   111  								},
   112  							},
   113  						},
   114  						"list_of_objects_field": &tfschema.Schema{
   115  							Type:     tfschema.TypeList,
   116  							Optional: true,
   117  							Elem: &tfschema.Resource{
   118  								Schema: map[string]*tfschema.Schema{
   119  									"immutable_field_a": &tfschema.Schema{
   120  										Type:     tfschema.TypeString,
   121  										Optional: true,
   122  										ForceNew: true,
   123  									},
   124  									"immutable_field_b": &tfschema.Schema{
   125  										Type:     tfschema.TypeString,
   126  										Optional: true,
   127  										ForceNew: true,
   128  									},
   129  									"mutable_field": &tfschema.Schema{
   130  										Type:     tfschema.TypeString,
   131  										Optional: true,
   132  									},
   133  								},
   134  							},
   135  						},
   136  					},
   137  				},
   138  			},
   139  			expectedRet: map[string]interface{}{
   140  				"imported_field":  "imported_val",
   141  				"primitive_field": "primitive_val",
   142  				"list_of_primitives_field": []interface{}{
   143  					"list_of_primitives_val_0",
   144  				},
   145  				"map_field": map[string]interface{}{
   146  					"map_key_a": "map_val_a",
   147  				},
   148  				"nested_object_field": []interface{}{
   149  					map[string]interface{}{
   150  						"immutable_field": "immutable_val",
   151  					},
   152  				},
   153  				"list_of_objects_field": []interface{}{
   154  					map[string]interface{}{
   155  						"immutable_field_a": "immutable_val_a",
   156  						"immutable_field_b": "immutable_val_b",
   157  					},
   158  					map[string]interface{}{
   159  						"immutable_field_a": "immutable_val_a",
   160  						"immutable_field_b": "",
   161  					},
   162  				},
   163  			},
   164  		},
   165  		{
   166  			name: "mutable-but-unreadable fields; none set in annotation",
   167  			imported: map[string]interface{}{
   168  				"imported_field": "imported_val",
   169  			},
   170  			resource: &krmtotf.Resource{
   171  				Resource: k8s.Resource{
   172  					ObjectMeta: metav1.ObjectMeta{
   173  						Annotations: map[string]string{
   174  							k8s.MutableButUnreadableFieldsAnnotation: `{}`,
   175  						},
   176  					},
   177  				},
   178  				TFResource: &tfschema.Resource{
   179  					Schema: map[string]*tfschema.Schema{
   180  						"imported_field": &tfschema.Schema{
   181  							Type:     tfschema.TypeString,
   182  							Optional: true,
   183  						},
   184  						"primitive_field": &tfschema.Schema{
   185  							Type:     tfschema.TypeString,
   186  							Optional: true,
   187  						},
   188  					},
   189  				},
   190  				ResourceConfig: v1alpha1.ResourceConfig{
   191  					MutableButUnreadableFields: []string{
   192  						"primitive_field",
   193  					},
   194  				},
   195  			},
   196  			expectedRet: map[string]interface{}{
   197  				"imported_field": "imported_val",
   198  			},
   199  		},
   200  		{
   201  			name: "mutable-but-unreadable fields",
   202  			imported: map[string]interface{}{
   203  				"imported_field": "imported_val",
   204  			},
   205  			resource: &krmtotf.Resource{
   206  				Resource: k8s.Resource{
   207  					ObjectMeta: metav1.ObjectMeta{
   208  						Annotations: map[string]string{
   209  							k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"primitiveField":"primitive_val","listOfPrimitivesField":["list_of_primitives_val_0"],"mapField":{"map_key_a":"map_val_a"},"nestedObjectField":{"mutableButUnreadableField":"mutable_but_unreadable_val"}}}`,
   210  						},
   211  					},
   212  				},
   213  				TFResource: &tfschema.Resource{
   214  					Schema: map[string]*tfschema.Schema{
   215  						"imported_field": &tfschema.Schema{
   216  							Type:     tfschema.TypeString,
   217  							Optional: true,
   218  						},
   219  						"primitive_field": &tfschema.Schema{
   220  							Type:     tfschema.TypeString,
   221  							Optional: true,
   222  						},
   223  						"list_of_primitives_field": &tfschema.Schema{
   224  							Type:     tfschema.TypeList,
   225  							Optional: true,
   226  							Elem: &tfschema.Schema{
   227  								Type: tfschema.TypeString,
   228  							},
   229  						},
   230  						"map_field": &tfschema.Schema{
   231  							Type:     tfschema.TypeMap,
   232  							Optional: true,
   233  						},
   234  						"nested_object_field": &tfschema.Schema{
   235  							Type:     tfschema.TypeList,
   236  							MaxItems: 1,
   237  							Optional: true,
   238  							Elem: &tfschema.Resource{
   239  								Schema: map[string]*tfschema.Schema{
   240  									"mutable_but_unreadable_field": &tfschema.Schema{
   241  										Type:     tfschema.TypeString,
   242  										Optional: true,
   243  									},
   244  								},
   245  							},
   246  						},
   247  					},
   248  				},
   249  				ResourceConfig: v1alpha1.ResourceConfig{
   250  					MutableButUnreadableFields: []string{
   251  						"primitive_field",
   252  						"list_of_primitives_field",
   253  						"map_field",
   254  						"nested_object_field.mutable_but_unreadable_field",
   255  					},
   256  				},
   257  			},
   258  			expectedRet: map[string]interface{}{
   259  				"imported_field":  "imported_val",
   260  				"primitive_field": "primitive_val",
   261  				"list_of_primitives_field": []interface{}{
   262  					"list_of_primitives_val_0",
   263  				},
   264  				"map_field": map[string]interface{}{
   265  					"map_key_a": "map_val_a",
   266  				},
   267  				"nested_object_field": []interface{}{
   268  					map[string]interface{}{
   269  						"mutable_but_unreadable_field": "mutable_but_unreadable_val",
   270  					},
   271  				},
   272  			},
   273  		},
   274  		{
   275  			name: "mutable-but-unreadable fields; annotation values differ from spec values",
   276  			imported: map[string]interface{}{
   277  				"imported_field": "imported_val",
   278  			},
   279  			resource: &krmtotf.Resource{
   280  				Resource: k8s.Resource{
   281  					ObjectMeta: metav1.ObjectMeta{
   282  						Annotations: map[string]string{
   283  							k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"primitiveField":"primitive_val","listOfPrimitivesField":["list_of_primitives_val_0"],"mapField":{"map_key_a":"map_val_a"},"nestedObjectField":{"mutableButUnreadableField":"mutable_but_unreadable_val"}}}`,
   284  						},
   285  					},
   286  					Spec: map[string]interface{}{
   287  						"primitiveField": "primitive_val_from_spec",
   288  						"listOfPrimitivesField": []interface{}{
   289  							"list_of_primitives_val_0_from_spec",
   290  						},
   291  						"mapField": map[string]interface{}{
   292  							"map_key_a": "map_val_a_from_spec",
   293  						},
   294  						"nestedObjectField": map[string]interface{}{
   295  							"mutableButUnreadableField": "mutable_but_unreadable_val_from_spec",
   296  						},
   297  					},
   298  				},
   299  				TFResource: &tfschema.Resource{
   300  					Schema: map[string]*tfschema.Schema{
   301  						"imported_field": &tfschema.Schema{
   302  							Type:     tfschema.TypeString,
   303  							Optional: true,
   304  						},
   305  						"primitive_field": &tfschema.Schema{
   306  							Type:     tfschema.TypeString,
   307  							Optional: true,
   308  						},
   309  						"list_of_primitives_field": &tfschema.Schema{
   310  							Type:     tfschema.TypeList,
   311  							Optional: true,
   312  							Elem: &tfschema.Schema{
   313  								Type: tfschema.TypeString,
   314  							},
   315  						},
   316  						"map_field": &tfschema.Schema{
   317  							Type:     tfschema.TypeMap,
   318  							Optional: true,
   319  						},
   320  						"nested_object_field": &tfschema.Schema{
   321  							Type:     tfschema.TypeList,
   322  							MaxItems: 1,
   323  							Optional: true,
   324  							Elem: &tfschema.Resource{
   325  								Schema: map[string]*tfschema.Schema{
   326  									"mutable_but_unreadable_field": &tfschema.Schema{
   327  										Type:     tfschema.TypeString,
   328  										Optional: true,
   329  									},
   330  								},
   331  							},
   332  						},
   333  					},
   334  				},
   335  				ResourceConfig: v1alpha1.ResourceConfig{
   336  					MutableButUnreadableFields: []string{
   337  						"primitive_field",
   338  						"list_of_primitives_field",
   339  						"map_field",
   340  						"nested_object_field.mutable_but_unreadable_field",
   341  					},
   342  				},
   343  			},
   344  			expectedRet: map[string]interface{}{
   345  				"imported_field":  "imported_val",
   346  				"primitive_field": "primitive_val",
   347  				"list_of_primitives_field": []interface{}{
   348  					"list_of_primitives_val_0",
   349  				},
   350  				"map_field": map[string]interface{}{
   351  					"map_key_a": "map_val_a",
   352  				},
   353  				"nested_object_field": []interface{}{
   354  					map[string]interface{}{
   355  						"mutable_but_unreadable_field": "mutable_but_unreadable_val",
   356  					},
   357  				},
   358  			},
   359  		},
   360  		{
   361  			name: "mutable-but-unreadable fields; field in nested object that is set in imported state",
   362  			imported: map[string]interface{}{
   363  				"nested_object": []interface{}{
   364  					map[string]interface{}{
   365  						"field": "val",
   366  					},
   367  				},
   368  			},
   369  			resource: &krmtotf.Resource{
   370  				Resource: k8s.Resource{
   371  					ObjectMeta: metav1.ObjectMeta{
   372  						Annotations: map[string]string{
   373  							k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"nestedObject":{"mutableButUnreadableField":"mutable_but_unreadable_val"}}}`,
   374  						},
   375  					},
   376  				},
   377  				TFResource: &tfschema.Resource{
   378  					Schema: map[string]*tfschema.Schema{
   379  						"nested_object": &tfschema.Schema{
   380  							Type:     tfschema.TypeList,
   381  							MaxItems: 1,
   382  							Optional: true,
   383  							Elem: &tfschema.Resource{
   384  								Schema: map[string]*tfschema.Schema{
   385  									"field": &tfschema.Schema{
   386  										Type:     tfschema.TypeString,
   387  										Optional: true,
   388  									},
   389  									"mutable_but_unreadable_field": &tfschema.Schema{
   390  										Type:     tfschema.TypeString,
   391  										Optional: true,
   392  									},
   393  								},
   394  							},
   395  						},
   396  					},
   397  				},
   398  				ResourceConfig: v1alpha1.ResourceConfig{
   399  					MutableButUnreadableFields: []string{
   400  						"nested_object.mutable_but_unreadable_field",
   401  					},
   402  				},
   403  			},
   404  			expectedRet: map[string]interface{}{
   405  				"nested_object": []interface{}{
   406  					map[string]interface{}{
   407  						"field":                        "val",
   408  						"mutable_but_unreadable_field": "mutable_but_unreadable_val",
   409  					},
   410  				},
   411  			},
   412  		},
   413  		{
   414  			name: "mutable-but-unreadable fields; annotation not set (new KCC resource or backwards compatibility scenario); should get mutable-but-unreadable fields from spec",
   415  			imported: map[string]interface{}{
   416  				"imported_field": "imported_val",
   417  			},
   418  			resource: &krmtotf.Resource{
   419  				Resource: k8s.Resource{
   420  					Spec: map[string]interface{}{
   421  						"primitiveField": "primitive_val",
   422  						"listOfPrimitivesField": []interface{}{
   423  							"list_of_primitives_val_0",
   424  						},
   425  						"mapField": map[string]interface{}{
   426  							"map_key_a": "map_val_a",
   427  						},
   428  						"nestedObjectField": map[string]interface{}{
   429  							"mutableButUnreadableField": "mutable_but_unreadable_val",
   430  						},
   431  					},
   432  				},
   433  				TFResource: &tfschema.Resource{
   434  					Schema: map[string]*tfschema.Schema{
   435  						"imported_field": &tfschema.Schema{
   436  							Type:     tfschema.TypeString,
   437  							Optional: true,
   438  						},
   439  						"primitive_field": &tfschema.Schema{
   440  							Type:     tfschema.TypeString,
   441  							Optional: true,
   442  						},
   443  						"list_of_primitives_field": &tfschema.Schema{
   444  							Type:     tfschema.TypeList,
   445  							Optional: true,
   446  							Elem: &tfschema.Schema{
   447  								Type: tfschema.TypeString,
   448  							},
   449  						},
   450  						"map_field": &tfschema.Schema{
   451  							Type:     tfschema.TypeMap,
   452  							Optional: true,
   453  						},
   454  						"nested_object_field": &tfschema.Schema{
   455  							Type:     tfschema.TypeList,
   456  							MaxItems: 1,
   457  							Optional: true,
   458  							Elem: &tfschema.Resource{
   459  								Schema: map[string]*tfschema.Schema{
   460  									"mutable_but_unreadable_field": &tfschema.Schema{
   461  										Type:     tfschema.TypeString,
   462  										Optional: true,
   463  									},
   464  								},
   465  							},
   466  						},
   467  					},
   468  				},
   469  				ResourceConfig: v1alpha1.ResourceConfig{
   470  					MutableButUnreadableFields: []string{
   471  						"primitive_field",
   472  						"list_of_primitives_field",
   473  						"map_field",
   474  						"nested_object_field.mutable_but_unreadable_field",
   475  					},
   476  				},
   477  			},
   478  			expectedRet: map[string]interface{}{
   479  				"imported_field":  "imported_val",
   480  				"primitive_field": "primitive_val",
   481  				"list_of_primitives_field": []interface{}{
   482  					"list_of_primitives_val_0",
   483  				},
   484  				"map_field": map[string]interface{}{
   485  					"map_key_a": "map_val_a",
   486  				},
   487  				"nested_object_field": []interface{}{
   488  					map[string]interface{}{
   489  						"mutable_but_unreadable_field": "mutable_but_unreadable_val",
   490  					},
   491  				},
   492  			},
   493  		},
   494  		{
   495  			name: "directives",
   496  			imported: map[string]interface{}{
   497  				"imported_field": "imported_val",
   498  			},
   499  			resource: &krmtotf.Resource{
   500  				Resource: k8s.Resource{
   501  					ObjectMeta: metav1.ObjectMeta{
   502  						Annotations: map[string]string{
   503  							k8s.FormatAnnotation("directive-field-b"): "directive_val_b",
   504  						},
   505  					},
   506  				},
   507  				TFResource: &tfschema.Resource{
   508  					Schema: map[string]*tfschema.Schema{
   509  						"imported_field": &tfschema.Schema{
   510  							Type:     tfschema.TypeString,
   511  							Optional: true,
   512  						},
   513  						"directive_field_b": &tfschema.Schema{
   514  							Type:     tfschema.TypeString,
   515  							Optional: true,
   516  						},
   517  						"directive_field_c": &tfschema.Schema{
   518  							Type:     tfschema.TypeString,
   519  							Optional: true,
   520  							Default:  "directive_val_c",
   521  						},
   522  					},
   523  				},
   524  				ResourceConfig: v1alpha1.ResourceConfig{
   525  					Directives: []string{
   526  						"directive_field_b",
   527  						"directive_field_c",
   528  					},
   529  				},
   530  			},
   531  			expectedRet: map[string]interface{}{
   532  				"imported_field":    "imported_val",
   533  				"directive_field_b": "directive_val_b",
   534  				"directive_field_c": "directive_val_c",
   535  			},
   536  		},
   537  		{
   538  			name: "status fields",
   539  			imported: map[string]interface{}{
   540  				"imported_field": "imported_val",
   541  			},
   542  			resource: &krmtotf.Resource{
   543  				Resource: k8s.Resource{
   544  					Status: map[string]interface{}{
   545  						"primitiveField": "val_b",
   546  						"listOfPrimitivesField": []interface{}{
   547  							"list_of_primitives_val_0",
   548  						},
   549  						"mapField": map[string]interface{}{
   550  							"map_key_a": "map_val_a",
   551  						},
   552  						"nestedObjectField": map[string]interface{}{
   553  							"field": "val",
   554  						},
   555  						"listOfObjectsField": []interface{}{
   556  							map[string]interface{}{
   557  								"fieldA": "val_a",
   558  							},
   559  							map[string]interface{}{
   560  								"fieldB": "val_b",
   561  							},
   562  						},
   563  					},
   564  				},
   565  				TFResource: &tfschema.Resource{
   566  					Schema: map[string]*tfschema.Schema{
   567  						"imported_field": &tfschema.Schema{
   568  							Type: tfschema.TypeString,
   569  						},
   570  						"primitive_field": &tfschema.Schema{
   571  							Type: tfschema.TypeString,
   572  						},
   573  						"list_of_primitives_field": &tfschema.Schema{
   574  							Type: tfschema.TypeList,
   575  							Elem: &tfschema.Schema{
   576  								Type: tfschema.TypeString,
   577  							},
   578  						},
   579  						"map_field": &tfschema.Schema{
   580  							Type: tfschema.TypeMap,
   581  						},
   582  						"nested_object_field": &tfschema.Schema{
   583  							Type:     tfschema.TypeList,
   584  							MaxItems: 1,
   585  							Elem: &tfschema.Resource{
   586  								Schema: map[string]*tfschema.Schema{
   587  									"field": &tfschema.Schema{
   588  										Type: tfschema.TypeString,
   589  									},
   590  								},
   591  							},
   592  						},
   593  						"list_of_objects_field": &tfschema.Schema{
   594  							Type: tfschema.TypeList,
   595  							Elem: &tfschema.Resource{
   596  								Schema: map[string]*tfschema.Schema{
   597  									"field_a": &tfschema.Schema{
   598  										Type: tfschema.TypeString,
   599  									},
   600  									"field_b": &tfschema.Schema{
   601  										Type: tfschema.TypeString,
   602  									},
   603  								},
   604  							},
   605  						},
   606  					},
   607  				},
   608  			},
   609  			expectedRet: map[string]interface{}{
   610  				"imported_field":  "imported_val",
   611  				"primitive_field": "val_b",
   612  				"list_of_primitives_field": []interface{}{
   613  					"list_of_primitives_val_0",
   614  				},
   615  				"map_field": map[string]interface{}{
   616  					"map_key_a": "map_val_a",
   617  				},
   618  				"nested_object_field": []interface{}{
   619  					map[string]interface{}{
   620  						"field": "val",
   621  					},
   622  				},
   623  				"list_of_objects_field": []interface{}{
   624  					map[string]interface{}{
   625  						"field_a": "val_a",
   626  					},
   627  					map[string]interface{}{
   628  						"field_b": "val_b",
   629  					},
   630  				},
   631  			},
   632  		},
   633  		{
   634  			name: "if the object is marked for deletion, withPresetFieldsForRead should not return an error when a referenced secret does not exist",
   635  			resource: &krmtotf.Resource{
   636  				Resource: k8s.Resource{
   637  					ObjectMeta: metav1.ObjectMeta{
   638  						DeletionTimestamp: &nowTime,
   639  					},
   640  					Spec: map[string]interface{}{
   641  						"primitiveField": "primitive_val",
   642  						"sensitiveField": map[string]interface{}{
   643  							"valueFrom": map[string]interface{}{
   644  								"secretKeyRef": map[string]interface{}{
   645  									"name": "secret1",
   646  									"key":  "secret-key1",
   647  								},
   648  							},
   649  						},
   650  					},
   651  				},
   652  				TFResource: &tfschema.Resource{
   653  					Schema: map[string]*tfschema.Schema{
   654  						"sensitive_field": &tfschema.Schema{
   655  							Type:      tfschema.TypeString,
   656  							Optional:  true,
   657  							Sensitive: true,
   658  						},
   659  						"primitive_field": &tfschema.Schema{
   660  							Type:     tfschema.TypeString,
   661  							Optional: true,
   662  							ForceNew: true,
   663  						},
   664  					},
   665  				},
   666  			},
   667  			expectedRet: map[string]interface{}{
   668  				"primitive_field": "primitive_val",
   669  			},
   670  		},
   671  	}
   672  
   673  	for _, tc := range tests {
   674  		tc := tc
   675  		t.Run(tc.name, func(t *testing.T) {
   676  			t.Parallel()
   677  			testId := testvariable.NewUniqueId()
   678  			c := mgr.GetClient()
   679  
   680  			testcontroller.EnsureNamespaceExistsT(t, c, testId)
   681  			tc.resource.SetNamespace(testId)
   682  			ret, err := krmtotf.WithFieldsPresetForRead(tc.imported, tc.resource, mgr.GetClient(), nil)
   683  			if err != nil {
   684  				t.Fatal(err)
   685  			}
   686  			if !test.Equals(t, tc.expectedRet, ret) {
   687  				diff := cmp.Diff(tc.expectedRet, ret)
   688  				t.Fatalf("actual result did not match expected result; diff (-expected +actual):\n%v", diff)
   689  			}
   690  		})
   691  	}
   692  }
   693  
   694  func TestWithFieldsPresetForReadMutableUnreadableSensitiveFields(t *testing.T) {
   695  	// Variables common across test cases
   696  	importedState := map[string]interface{}{
   697  		"imported_field": "imported_val",
   698  	}
   699  	tfResource := &tfschema.Resource{
   700  		Schema: map[string]*tfschema.Schema{
   701  			"imported_field": &tfschema.Schema{
   702  				Type:     tfschema.TypeString,
   703  				Optional: true,
   704  			},
   705  			"sensitive_field": &tfschema.Schema{
   706  				Type:      tfschema.TypeString,
   707  				Optional:  true,
   708  				Sensitive: true,
   709  			},
   710  			"nested_object_field": &tfschema.Schema{
   711  				Type:     tfschema.TypeList,
   712  				MaxItems: 1,
   713  				Optional: true,
   714  				Elem: &tfschema.Resource{
   715  					Schema: map[string]*tfschema.Schema{
   716  						"sensitive_field": &tfschema.Schema{
   717  							Type:      tfschema.TypeString,
   718  							Optional:  true,
   719  							Sensitive: true,
   720  						},
   721  					},
   722  				},
   723  			},
   724  		},
   725  	}
   726  	resourceConfig := v1alpha1.ResourceConfig{
   727  		MutableButUnreadableFields: []string{
   728  			"sensitive_field",
   729  			"nested_object_field.sensitive_field",
   730  		},
   731  	}
   732  
   733  	type versionStatus int
   734  	const (
   735  		UP_TO_DATE versionStatus = iota
   736  		OUTDATED
   737  		NOT_FOUND
   738  	)
   739  	tests := []struct {
   740  		name     string
   741  		imported map[string]interface{}
   742  
   743  		// Used to generate an "observed-secret-versions" annotation value for
   744  		// `resource` if specified. If unspecified, the annotation will be left
   745  		// unset. This annotation can't be configured accurately for testing
   746  		// purposes before the Secrets are actually created since that is when
   747  		// the versions of Secrets are generated. Each Secret can be specified
   748  		// as:
   749  		// * UP_TO_DATE: Secret's version in the annotation matches its current version
   750  		// * OUTDATED: Secret's version in the annotation does not match its current version
   751  		// * NOT_FOUND: Secret has a version in the annotation, but the Secret itself does not exist
   752  		observedSecretVersions map[string]versionStatus
   753  
   754  		resource          *krmtotf.Resource
   755  		referencedSecrets []*unstructured.Unstructured
   756  		expectedRet       map[string]interface{}
   757  	}{
   758  		{
   759  			name:     "sensitive fields with values from Secrets in mutable-but-unreadable-fields, and observed-secret-versions is up-to-date",
   760  			imported: importedState,
   761  			observedSecretVersions: map[string]versionStatus{
   762  				"secret1": UP_TO_DATE,
   763  				"secret2": UP_TO_DATE,
   764  			},
   765  			resource: &krmtotf.Resource{
   766  				Resource: k8s.Resource{
   767  					ObjectMeta: metav1.ObjectMeta{
   768  						Annotations: map[string]string{
   769  							k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret1","key":"secret-key1"}}},"nestedObjectField":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret2","key":"secret-key2"}}}}}}`,
   770  						},
   771  					},
   772  				},
   773  				TFResource:     tfResource,
   774  				ResourceConfig: resourceConfig,
   775  			},
   776  
   777  			referencedSecrets: []*unstructured.Unstructured{
   778  				test.NewSecretUnstructured("secret1", "", map[string]interface{}{"secret-key1": "secret-val1"}),
   779  				test.NewSecretUnstructured("secret2", "", map[string]interface{}{"secret-key2": "secret-val2"}),
   780  			},
   781  			expectedRet: map[string]interface{}{
   782  				"imported_field":  "imported_val",
   783  				"sensitive_field": "secret-val1",
   784  				"nested_object_field": []interface{}{
   785  					map[string]interface{}{
   786  						"sensitive_field": "secret-val2",
   787  					},
   788  				},
   789  			},
   790  		},
   791  		{
   792  			name:     "sensitive fields with values from Secrets in mutable-but-unreadable-fields, but observed-secret-versions is outdated",
   793  			imported: importedState,
   794  			observedSecretVersions: map[string]versionStatus{
   795  				"secret1": OUTDATED,
   796  				"secret2": OUTDATED,
   797  			},
   798  			resource: &krmtotf.Resource{
   799  				Resource: k8s.Resource{
   800  					ObjectMeta: metav1.ObjectMeta{
   801  						Annotations: map[string]string{
   802  							k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret1","key":"secret-key1"}}},"nestedObjectField":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret2","key":"secret-key2"}}}}}}`,
   803  						},
   804  					},
   805  				},
   806  				TFResource:     tfResource,
   807  				ResourceConfig: resourceConfig,
   808  			},
   809  			referencedSecrets: []*unstructured.Unstructured{
   810  				test.NewSecretUnstructured("secret1", "", map[string]interface{}{"secret-key1": "secret-val1"}),
   811  				test.NewSecretUnstructured("secret2", "", map[string]interface{}{"secret-key2": "secret-val2"}),
   812  			},
   813  			expectedRet: map[string]interface{}{
   814  				"imported_field": "imported_val",
   815  			},
   816  		},
   817  		{
   818  			name:     "sensitive fields with values from Secrets in mutable-but-unreadable-fields, but Secrets are not found",
   819  			imported: importedState,
   820  			observedSecretVersions: map[string]versionStatus{
   821  				"secret1": NOT_FOUND,
   822  				"secret2": NOT_FOUND,
   823  			},
   824  			resource: &krmtotf.Resource{
   825  				Resource: k8s.Resource{
   826  					ObjectMeta: metav1.ObjectMeta{
   827  						Annotations: map[string]string{
   828  							k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret1","key":"secret-key1"}}},"nestedObjectField":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret2","key":"secret-key2"}}}}}}`,
   829  						},
   830  					},
   831  				},
   832  				TFResource:     tfResource,
   833  				ResourceConfig: resourceConfig,
   834  			},
   835  			expectedRet: map[string]interface{}{
   836  				"imported_field": "imported_val",
   837  			},
   838  		},
   839  		{
   840  			name:     "sensitive fields with values from Secrets in mutable-but-unreadable-fields, and observed-secret-versions is up-to-date, but keys can't be found in Secrets",
   841  			imported: importedState,
   842  			observedSecretVersions: map[string]versionStatus{
   843  				"secret1": UP_TO_DATE,
   844  				"secret2": UP_TO_DATE,
   845  			},
   846  			resource: &krmtotf.Resource{
   847  				Resource: k8s.Resource{
   848  					ObjectMeta: metav1.ObjectMeta{
   849  						Annotations: map[string]string{
   850  							k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret1","key":"secret-key1"}}},"nestedObjectField":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret2","key":"secret-key2"}}}}}}`,
   851  						},
   852  					},
   853  				},
   854  				TFResource:     tfResource,
   855  				ResourceConfig: resourceConfig,
   856  			},
   857  			referencedSecrets: []*unstructured.Unstructured{
   858  				test.NewSecretUnstructured("secret1", "", map[string]interface{}{"unused-secret-key1": "secret-val1"}),
   859  				test.NewSecretUnstructured("secret2", "", map[string]interface{}{"unused-secret-key2": "secret-val2"}),
   860  			},
   861  			expectedRet: map[string]interface{}{
   862  				"imported_field": "imported_val",
   863  			},
   864  		},
   865  		{
   866  			name:     "sensitive fields with simple values in mutable-but-unreadable-fields",
   867  			imported: importedState,
   868  			resource: &krmtotf.Resource{
   869  				Resource: k8s.Resource{
   870  					ObjectMeta: metav1.ObjectMeta{
   871  						Annotations: map[string]string{
   872  							k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"sensitiveField":{"value":"sensitive_val"},"nestedObjectField":{"sensitiveField":{"value":"nested_sensitive_val"}}}}`,
   873  						},
   874  					},
   875  				},
   876  				TFResource:     tfResource,
   877  				ResourceConfig: resourceConfig,
   878  			},
   879  			expectedRet: map[string]interface{}{
   880  				"imported_field":  "imported_val",
   881  				"sensitive_field": "sensitive_val",
   882  				"nested_object_field": []interface{}{
   883  					map[string]interface{}{
   884  						"sensitive_field": "nested_sensitive_val",
   885  					},
   886  				},
   887  			},
   888  		},
   889  		{
   890  			name:     "mutable-but-unreadable-fields and observed-secret-versions annotations not set (new KCC resource or backwards compatibility scenario); should get sensitive, mutable, unreadable fields from spec (where sensitive fields are from Secrets)",
   891  			imported: importedState,
   892  			resource: &krmtotf.Resource{
   893  				Resource: k8s.Resource{
   894  					Spec: map[string]interface{}{
   895  						"sensitiveField": map[string]interface{}{
   896  							"valueFrom": map[string]interface{}{
   897  								"secretKeyRef": map[string]interface{}{
   898  									"name": "secret1",
   899  									"key":  "secret-key1",
   900  								},
   901  							},
   902  						},
   903  						"nestedObjectField": map[string]interface{}{
   904  							"sensitiveField": map[string]interface{}{
   905  								"valueFrom": map[string]interface{}{
   906  									"secretKeyRef": map[string]interface{}{
   907  										"name": "secret2",
   908  										"key":  "secret-key2",
   909  									},
   910  								},
   911  							},
   912  						},
   913  					},
   914  				},
   915  				TFResource:     tfResource,
   916  				ResourceConfig: resourceConfig,
   917  			},
   918  			referencedSecrets: []*unstructured.Unstructured{
   919  				test.NewSecretUnstructured("secret1", "", map[string]interface{}{"secret-key1": "secret-val1"}),
   920  				test.NewSecretUnstructured("secret2", "", map[string]interface{}{"secret-key2": "secret-val2"}),
   921  			},
   922  			expectedRet: map[string]interface{}{
   923  				"imported_field":  "imported_val",
   924  				"sensitive_field": "secret-val1",
   925  				"nested_object_field": []interface{}{
   926  					map[string]interface{}{
   927  						"sensitive_field": "secret-val2",
   928  					},
   929  				},
   930  			},
   931  		},
   932  		{
   933  			name:     "mutable-but-unreadable-fields and observed-secret-versions annotations not set (new KCC resource or backwards compatibility scenario); should get sensitive, mutable, unreadable fields from spec (where sensitive fields are simple values)",
   934  			imported: importedState,
   935  			resource: &krmtotf.Resource{
   936  				Resource: k8s.Resource{
   937  					Spec: map[string]interface{}{
   938  						"sensitiveField": map[string]interface{}{
   939  							"value": "sensitive_val",
   940  						},
   941  						"nestedObjectField": map[string]interface{}{
   942  							"sensitiveField": map[string]interface{}{
   943  								"value": "nested_sensitive_val",
   944  							},
   945  						},
   946  					},
   947  				},
   948  				TFResource:     tfResource,
   949  				ResourceConfig: resourceConfig,
   950  			},
   951  			expectedRet: map[string]interface{}{
   952  				"imported_field":  "imported_val",
   953  				"sensitive_field": "sensitive_val",
   954  				"nested_object_field": []interface{}{
   955  					map[string]interface{}{
   956  						"sensitive_field": "nested_sensitive_val",
   957  					},
   958  				},
   959  			},
   960  		},
   961  		{
   962  			name:     "mutable-but-unreadable-fields annotation values differ from spec values (where sensitive fields in annotation are from Secrets, but sensitive fields in Spec are simple values)",
   963  			imported: importedState,
   964  			observedSecretVersions: map[string]versionStatus{
   965  				"secret1": UP_TO_DATE,
   966  				"secret2": UP_TO_DATE,
   967  			},
   968  			resource: &krmtotf.Resource{
   969  				Resource: k8s.Resource{
   970  					ObjectMeta: metav1.ObjectMeta{
   971  						Annotations: map[string]string{
   972  							k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret1","key":"secret-key1"}}},"nestedObjectField":{"sensitiveField":{"valueFrom":{"secretKeyRef":{"name":"secret2","key":"secret-key2"}}}}}}`,
   973  						},
   974  					},
   975  					Spec: map[string]interface{}{
   976  						"sensitiveField": map[string]interface{}{
   977  							"value": "sensitive_val",
   978  						},
   979  						"nestedObjectField": map[string]interface{}{
   980  							"sensitiveField": map[string]interface{}{
   981  								"value": "nested_sensitive_val",
   982  							},
   983  						},
   984  					},
   985  				},
   986  				TFResource:     tfResource,
   987  				ResourceConfig: resourceConfig,
   988  			},
   989  			referencedSecrets: []*unstructured.Unstructured{
   990  				test.NewSecretUnstructured("secret1", "", map[string]interface{}{"secret-key1": "secret-val1"}),
   991  				test.NewSecretUnstructured("secret2", "", map[string]interface{}{"secret-key2": "secret-val2"}),
   992  			},
   993  			expectedRet: map[string]interface{}{
   994  				"imported_field":  "imported_val",
   995  				"sensitive_field": "secret-val1",
   996  				"nested_object_field": []interface{}{
   997  					map[string]interface{}{
   998  						"sensitive_field": "secret-val2",
   999  					},
  1000  				},
  1001  			},
  1002  		},
  1003  		{
  1004  			name:                   "mutable-but-unreadable-fields annotation values differ from spec values (where sensitive fields in annotation are simple values, but sensitive fields in Spec are from Secrets)",
  1005  			imported:               importedState,
  1006  			observedSecretVersions: map[string]versionStatus{},
  1007  			resource: &krmtotf.Resource{
  1008  				Resource: k8s.Resource{
  1009  					ObjectMeta: metav1.ObjectMeta{
  1010  						Annotations: map[string]string{
  1011  							k8s.MutableButUnreadableFieldsAnnotation: `{"spec":{"sensitiveField":{"value":"sensitive_val"},"nestedObjectField":{"sensitiveField":{"value":"nested_sensitive_val"}}}}`,
  1012  						},
  1013  					},
  1014  					Spec: map[string]interface{}{
  1015  						"sensitiveField": map[string]interface{}{
  1016  							"valueFrom": map[string]interface{}{
  1017  								"secretKeyRef": map[string]interface{}{
  1018  									"name": "secret1",
  1019  									"key":  "secret-key1",
  1020  								},
  1021  							},
  1022  						},
  1023  						"nestedObjectField": map[string]interface{}{
  1024  							"sensitiveField": map[string]interface{}{
  1025  								"valueFrom": map[string]interface{}{
  1026  									"secretKeyRef": map[string]interface{}{
  1027  										"name": "secret2",
  1028  										"key":  "secret-key2",
  1029  									},
  1030  								},
  1031  							},
  1032  						},
  1033  					},
  1034  				},
  1035  				TFResource:     tfResource,
  1036  				ResourceConfig: resourceConfig,
  1037  			},
  1038  			referencedSecrets: []*unstructured.Unstructured{
  1039  				test.NewSecretUnstructured("secret1", "", map[string]interface{}{"secret-key1": "secret-val1"}),
  1040  				test.NewSecretUnstructured("secret2", "", map[string]interface{}{"secret-key2": "secret-val2"}),
  1041  			},
  1042  			expectedRet: map[string]interface{}{
  1043  				"imported_field":  "imported_val",
  1044  				"sensitive_field": "sensitive_val",
  1045  				"nested_object_field": []interface{}{
  1046  					map[string]interface{}{
  1047  						"sensitive_field": "nested_sensitive_val",
  1048  					},
  1049  				},
  1050  			},
  1051  		},
  1052  	}
  1053  
  1054  	for _, tc := range tests {
  1055  		tc := tc
  1056  		t.Run(tc.name, func(t *testing.T) {
  1057  			t.Parallel()
  1058  			testId := testvariable.NewUniqueId()
  1059  			c := mgr.GetClient()
  1060  
  1061  			testcontroller.EnsureNamespaceExistsT(t, c, testId)
  1062  			tc.resource.SetNamespace(testId)
  1063  			for _, obj := range tc.referencedSecrets {
  1064  				obj.SetNamespace(testId)
  1065  			}
  1066  			test.EnsureObjectsExist(t, tc.referencedSecrets, c)
  1067  
  1068  			// Generate value for observed-secret-versions annotation if needed
  1069  			if tc.observedSecretVersions != nil {
  1070  				secretVersions := make(map[string]string)
  1071  				for secretName, status := range tc.observedSecretVersions {
  1072  					if status == NOT_FOUND {
  1073  						secretVersions[secretName] = "12345"
  1074  						continue
  1075  					}
  1076  					version, err := getResourceVersionOfSecret(secretName, testId, c)
  1077  					if err != nil {
  1078  						t.Fatalf("error determining version of Secret %v: %v", secretName, err)
  1079  					}
  1080  					switch status {
  1081  					case UP_TO_DATE:
  1082  						secretVersions[secretName] = version
  1083  					case OUTDATED:
  1084  						secretVersions[secretName] = version + "0"
  1085  					}
  1086  				}
  1087  				b, err := json.Marshal(secretVersions)
  1088  				if err != nil {
  1089  					t.Fatalf("error marshalling secret versions map: %v", err)
  1090  				}
  1091  				k8s.SetAnnotation(k8s.ObservedSecretVersionsAnnotation, string(b), tc.resource)
  1092  			}
  1093  
  1094  			ret, err := krmtotf.WithFieldsPresetForRead(tc.imported, tc.resource, mgr.GetClient(), nil)
  1095  			if err != nil {
  1096  				t.Fatal(err)
  1097  			}
  1098  			if !test.Equals(t, tc.expectedRet, ret) {
  1099  				diff := cmp.Diff(tc.expectedRet, ret)
  1100  				t.Fatalf("actual result did not match expected result; diff (-expected +actual):\n%v", diff)
  1101  			}
  1102  		})
  1103  	}
  1104  }
  1105  

View as plain text