...

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

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

     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 k8s_test
    16  
    17  import (
    18  	"reflect"
    19  	"testing"
    20  
    21  	corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
    22  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
    23  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  	corev1 "k8s.io/api/core/v1"
    27  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  )
    29  
    30  func TestSetDefaultHierarchicalReference(t *testing.T) {
    31  	tests := []struct {
    32  		name             string
    33  		resource         *k8s.Resource
    34  		ns               *corev1.Namespace
    35  		hierarchicalRefs []corekccv1alpha1.HierarchicalReference
    36  		containers       []corekccv1alpha1.Container
    37  
    38  		expectedResource *k8s.Resource
    39  		shouldErr        bool
    40  	}{
    41  		{
    42  			name: "no defaulting if resource doesn't support hierarchical references",
    43  			resource: &k8s.Resource{
    44  				ObjectMeta: v1.ObjectMeta{
    45  					Annotations: map[string]string{
    46  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
    47  					},
    48  				},
    49  				Spec: map[string]interface{}{
    50  					"field": "val",
    51  				},
    52  			},
    53  			ns: &corev1.Namespace{
    54  				ObjectMeta: v1.ObjectMeta{
    55  					Annotations: map[string]string{
    56  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
    57  					},
    58  					Name: "namespace-name",
    59  				},
    60  			},
    61  			containers: []corekccv1alpha1.Container{
    62  				{Type: corekccv1alpha1.ContainerTypeProject},
    63  			},
    64  			expectedResource: &k8s.Resource{
    65  				ObjectMeta: v1.ObjectMeta{
    66  					Annotations: map[string]string{
    67  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
    68  					},
    69  				},
    70  				Spec: map[string]interface{}{
    71  					"field": "val",
    72  				},
    73  			},
    74  		},
    75  		{
    76  			name: "no defaulting if resource specifies a reference already",
    77  			resource: &k8s.Resource{
    78  				ObjectMeta: v1.ObjectMeta{
    79  					Annotations: map[string]string{
    80  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
    81  					},
    82  				},
    83  				Spec: map[string]interface{}{
    84  					"field": "val",
    85  					"projectRef": map[string]interface{}{
    86  						"name": "project-id-from-spec",
    87  					},
    88  				},
    89  			},
    90  			ns: &corev1.Namespace{
    91  				ObjectMeta: v1.ObjectMeta{
    92  					Annotations: map[string]string{
    93  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
    94  					},
    95  					Name: "namespace-name",
    96  				},
    97  			},
    98  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
    99  				{
   100  					Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   101  					Key:  "projectRef",
   102  				},
   103  			},
   104  			containers: []corekccv1alpha1.Container{
   105  				{Type: corekccv1alpha1.ContainerTypeProject},
   106  			},
   107  			expectedResource: &k8s.Resource{
   108  				ObjectMeta: v1.ObjectMeta{
   109  					Annotations: map[string]string{
   110  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
   111  					},
   112  				},
   113  				Spec: map[string]interface{}{
   114  					"field": "val",
   115  					"projectRef": map[string]interface{}{
   116  						"name": "project-id-from-spec",
   117  					},
   118  				},
   119  			},
   120  		},
   121  		{
   122  			name: "default from resource annotation if specified and supported over namespace annotation of same type",
   123  			resource: &k8s.Resource{
   124  				ObjectMeta: v1.ObjectMeta{
   125  					Annotations: map[string]string{
   126  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
   127  					},
   128  				},
   129  				Spec: map[string]interface{}{
   130  					"field": "val",
   131  				},
   132  			},
   133  			ns: &corev1.Namespace{
   134  				ObjectMeta: v1.ObjectMeta{
   135  					Annotations: map[string]string{
   136  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
   137  					},
   138  					Name: "namespace-name",
   139  				},
   140  			},
   141  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   142  				{
   143  					Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   144  					Key:  "projectRef",
   145  				},
   146  			},
   147  			containers: []corekccv1alpha1.Container{
   148  				{Type: corekccv1alpha1.ContainerTypeProject},
   149  			},
   150  			expectedResource: &k8s.Resource{
   151  				ObjectMeta: v1.ObjectMeta{
   152  					Annotations: map[string]string{
   153  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
   154  					},
   155  				},
   156  				Spec: map[string]interface{}{
   157  					"field": "val",
   158  					"projectRef": map[string]interface{}{
   159  						"external": "project-id-from-resource-annotation",
   160  					},
   161  				},
   162  			},
   163  		},
   164  		{
   165  			name: "default from resource annotation if specified and supported over namespace annotation of different type",
   166  			resource: &k8s.Resource{
   167  				ObjectMeta: v1.ObjectMeta{
   168  					Annotations: map[string]string{
   169  						k8s.FolderIDAnnotation: "folder-id-from-resource-annotation",
   170  					},
   171  				},
   172  				Spec: map[string]interface{}{
   173  					"field": "val",
   174  				},
   175  			},
   176  			ns: &corev1.Namespace{
   177  				ObjectMeta: v1.ObjectMeta{
   178  					Annotations: map[string]string{
   179  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
   180  					},
   181  					Name: "namespace-name",
   182  				},
   183  			},
   184  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   185  				{
   186  					Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   187  					Key:  "projectRef",
   188  				},
   189  				{
   190  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   191  					Key:  "folderRef",
   192  				},
   193  			},
   194  			containers: []corekccv1alpha1.Container{
   195  				{Type: corekccv1alpha1.ContainerTypeProject},
   196  				{Type: corekccv1alpha1.ContainerTypeFolder},
   197  			},
   198  			expectedResource: &k8s.Resource{
   199  				ObjectMeta: v1.ObjectMeta{
   200  					Annotations: map[string]string{
   201  						k8s.FolderIDAnnotation: "folder-id-from-resource-annotation",
   202  					},
   203  				},
   204  				Spec: map[string]interface{}{
   205  					"field": "val",
   206  					"folderRef": map[string]interface{}{
   207  						"external": "folder-id-from-resource-annotation",
   208  					},
   209  				},
   210  			},
   211  		},
   212  		{
   213  			name: "default from resource annotation if specified and supported over namespace name",
   214  			resource: &k8s.Resource{
   215  				ObjectMeta: v1.ObjectMeta{
   216  					Annotations: map[string]string{
   217  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
   218  					},
   219  				},
   220  				Spec: map[string]interface{}{
   221  					"field": "val",
   222  				},
   223  			},
   224  			ns: &corev1.Namespace{
   225  				ObjectMeta: v1.ObjectMeta{
   226  					Name: "namespace-name",
   227  				},
   228  			},
   229  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   230  				{
   231  					Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   232  					Key:  "projectRef",
   233  				},
   234  			},
   235  			containers: []corekccv1alpha1.Container{
   236  				{Type: corekccv1alpha1.ContainerTypeProject},
   237  			},
   238  			expectedResource: &k8s.Resource{
   239  				ObjectMeta: v1.ObjectMeta{
   240  					Annotations: map[string]string{
   241  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
   242  					},
   243  				},
   244  				Spec: map[string]interface{}{
   245  					"field": "val",
   246  					"projectRef": map[string]interface{}{
   247  						"external": "project-id-from-resource-annotation",
   248  					},
   249  				},
   250  			},
   251  		},
   252  		{
   253  			name: "default from namespace annotation if resource annotations are not supported",
   254  			resource: &k8s.Resource{
   255  				ObjectMeta: v1.ObjectMeta{
   256  					Annotations: map[string]string{
   257  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
   258  					},
   259  				},
   260  				Spec: map[string]interface{}{
   261  					"field": "val",
   262  				},
   263  			},
   264  			ns: &corev1.Namespace{
   265  				ObjectMeta: v1.ObjectMeta{
   266  					Annotations: map[string]string{
   267  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
   268  					},
   269  					Name: "namespace-name",
   270  				},
   271  			},
   272  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   273  				{
   274  					Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   275  					Key:  "projectRef",
   276  				},
   277  			},
   278  			expectedResource: &k8s.Resource{
   279  				ObjectMeta: v1.ObjectMeta{
   280  					Annotations: map[string]string{
   281  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
   282  					},
   283  				},
   284  				Spec: map[string]interface{}{
   285  					"field": "val",
   286  					"projectRef": map[string]interface{}{
   287  						"external": "project-id-from-namespace-annotation",
   288  					},
   289  				},
   290  			},
   291  		},
   292  		{
   293  			name: "default from namespace annotation if no resource annotation specified",
   294  			resource: &k8s.Resource{
   295  				Spec: map[string]interface{}{
   296  					"field": "val",
   297  				},
   298  			},
   299  			ns: &corev1.Namespace{
   300  				ObjectMeta: v1.ObjectMeta{
   301  					Annotations: map[string]string{
   302  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
   303  					},
   304  					Name: "namespace-name",
   305  				},
   306  			},
   307  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   308  				{
   309  					Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   310  					Key:  "projectRef",
   311  				},
   312  			},
   313  			containers: []corekccv1alpha1.Container{
   314  				{Type: corekccv1alpha1.ContainerTypeProject},
   315  			},
   316  			expectedResource: &k8s.Resource{
   317  				Spec: map[string]interface{}{
   318  					"field": "val",
   319  					"projectRef": map[string]interface{}{
   320  						"external": "project-id-from-namespace-annotation",
   321  					},
   322  				},
   323  			},
   324  		},
   325  		{
   326  			name: "default from namespace name if no namespace annotation specified and resource annotations are not supported",
   327  			resource: &k8s.Resource{
   328  				ObjectMeta: v1.ObjectMeta{
   329  					Annotations: map[string]string{
   330  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
   331  					},
   332  				},
   333  				Spec: map[string]interface{}{
   334  					"field": "val",
   335  				},
   336  			},
   337  			ns: &corev1.Namespace{
   338  				ObjectMeta: v1.ObjectMeta{
   339  					Name: "namespace-name",
   340  				},
   341  			},
   342  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   343  				{
   344  					Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   345  					Key:  "projectRef",
   346  				},
   347  			},
   348  			expectedResource: &k8s.Resource{
   349  				ObjectMeta: v1.ObjectMeta{
   350  					Annotations: map[string]string{
   351  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
   352  					},
   353  				},
   354  				Spec: map[string]interface{}{
   355  					"field": "val",
   356  					"projectRef": map[string]interface{}{
   357  						"external": "namespace-name",
   358  					},
   359  				},
   360  			},
   361  		},
   362  		{
   363  			name: "default from namespace name if no namespace or resource annotations specified",
   364  			resource: &k8s.Resource{
   365  				Spec: map[string]interface{}{
   366  					"field": "val",
   367  				},
   368  			},
   369  			ns: &corev1.Namespace{
   370  				ObjectMeta: v1.ObjectMeta{
   371  					Name: "namespace-name",
   372  				},
   373  			},
   374  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   375  				{
   376  					Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   377  					Key:  "projectRef",
   378  				},
   379  			},
   380  			expectedResource: &k8s.Resource{
   381  				Spec: map[string]interface{}{
   382  					"field": "val",
   383  					"projectRef": map[string]interface{}{
   384  						"external": "namespace-name",
   385  					},
   386  				},
   387  			},
   388  		},
   389  		{
   390  			name: "error if no namespace or resource annotations specified and resource does not support project references",
   391  			resource: &k8s.Resource{
   392  				Spec: map[string]interface{}{
   393  					"field": "val",
   394  				},
   395  			},
   396  			ns: &corev1.Namespace{
   397  				ObjectMeta: v1.ObjectMeta{
   398  					Name: "namespace-name",
   399  				},
   400  			},
   401  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   402  				{
   403  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   404  					Key:  "folderRef",
   405  				},
   406  			},
   407  			shouldErr: true,
   408  		},
   409  		{
   410  			name: "only default supported reference from resource annotation",
   411  			resource: &k8s.Resource{
   412  				ObjectMeta: v1.ObjectMeta{
   413  					Annotations: map[string]string{
   414  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
   415  						k8s.FolderIDAnnotation:  "folder-id-from-resource-annotation",
   416  						k8s.OrgIDAnnotation:     "org-id-from-resource-annotation",
   417  					},
   418  				},
   419  				Spec: map[string]interface{}{
   420  					"field": "val",
   421  				},
   422  			},
   423  			ns: &corev1.Namespace{
   424  				ObjectMeta: v1.ObjectMeta{
   425  					Annotations: map[string]string{
   426  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
   427  						k8s.FolderIDAnnotation:  "folder-id-from-namespace-annotation",
   428  						k8s.OrgIDAnnotation:     "org-id-from-namespace-annotation",
   429  					},
   430  					Name: "namespace-name",
   431  				},
   432  			},
   433  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   434  				{
   435  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   436  					Key:  "folderRef",
   437  				},
   438  			},
   439  			containers: []corekccv1alpha1.Container{
   440  				{Type: corekccv1alpha1.ContainerTypeFolder},
   441  			},
   442  			expectedResource: &k8s.Resource{
   443  				ObjectMeta: v1.ObjectMeta{
   444  					Annotations: map[string]string{
   445  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
   446  						k8s.FolderIDAnnotation:  "folder-id-from-resource-annotation",
   447  						k8s.OrgIDAnnotation:     "org-id-from-resource-annotation",
   448  					},
   449  				},
   450  				Spec: map[string]interface{}{
   451  					"field": "val",
   452  					"folderRef": map[string]interface{}{
   453  						"external": "folder-id-from-resource-annotation",
   454  					},
   455  				},
   456  			},
   457  		},
   458  		{
   459  			name: "only default supported reference from namespace annotation (no resource annotations specified)",
   460  			resource: &k8s.Resource{
   461  				Spec: map[string]interface{}{
   462  					"field": "val",
   463  				},
   464  			},
   465  			ns: &corev1.Namespace{
   466  				ObjectMeta: v1.ObjectMeta{
   467  					Annotations: map[string]string{
   468  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
   469  						k8s.FolderIDAnnotation:  "folder-id-from-namespace-annotation",
   470  						k8s.OrgIDAnnotation:     "org-id-from-namespace-annotation",
   471  					},
   472  					Name: "namespace-name",
   473  				},
   474  			},
   475  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   476  				{
   477  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   478  					Key:  "folderRef",
   479  				},
   480  			},
   481  			containers: []corekccv1alpha1.Container{
   482  				{Type: corekccv1alpha1.ContainerTypeFolder},
   483  			},
   484  			expectedResource: &k8s.Resource{
   485  				Spec: map[string]interface{}{
   486  					"field": "val",
   487  					"folderRef": map[string]interface{}{
   488  						"external": "folder-id-from-namespace-annotation",
   489  					},
   490  				},
   491  			},
   492  		},
   493  		{
   494  			name: "only default supported reference from namespace annotation (no resource annotations supported)",
   495  			resource: &k8s.Resource{
   496  				Spec: map[string]interface{}{
   497  					"field": "val",
   498  				},
   499  			},
   500  			ns: &corev1.Namespace{
   501  				ObjectMeta: v1.ObjectMeta{
   502  					Annotations: map[string]string{
   503  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
   504  						k8s.FolderIDAnnotation:  "folder-id-from-namespace-annotation",
   505  						k8s.OrgIDAnnotation:     "org-id-from-namespace-annotation",
   506  					},
   507  					Name: "namespace-name",
   508  				},
   509  			},
   510  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   511  				{
   512  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   513  					Key:  "folderRef",
   514  				},
   515  			},
   516  			expectedResource: &k8s.Resource{
   517  				Spec: map[string]interface{}{
   518  					"field": "val",
   519  					"folderRef": map[string]interface{}{
   520  						"external": "folder-id-from-namespace-annotation",
   521  					},
   522  				},
   523  			},
   524  		},
   525  		{
   526  			name: "error if multiple references supported and multiple resource annotations specified",
   527  			resource: &k8s.Resource{
   528  				ObjectMeta: v1.ObjectMeta{
   529  					Annotations: map[string]string{
   530  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
   531  						k8s.FolderIDAnnotation:  "folder-id-from-resource-annotation",
   532  					},
   533  				},
   534  				Spec: map[string]interface{}{
   535  					"field": "val",
   536  				},
   537  			},
   538  			ns: &corev1.Namespace{
   539  				ObjectMeta: v1.ObjectMeta{
   540  					Annotations: map[string]string{
   541  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
   542  						k8s.FolderIDAnnotation:  "folder-id-from-namespace-annotation",
   543  					},
   544  					Name: "namespace-name",
   545  				},
   546  			},
   547  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   548  				{
   549  					Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   550  					Key:  "projectRef",
   551  				},
   552  				{
   553  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   554  					Key:  "folderRef",
   555  				},
   556  			},
   557  			containers: []corekccv1alpha1.Container{
   558  				{Type: corekccv1alpha1.ContainerTypeProject},
   559  				{Type: corekccv1alpha1.ContainerTypeFolder},
   560  			},
   561  			shouldErr: true,
   562  		},
   563  		{
   564  			name: "error if multiple references supported and multiple resource annotations specified with one being set to empty string",
   565  			resource: &k8s.Resource{
   566  				ObjectMeta: v1.ObjectMeta{
   567  					Annotations: map[string]string{
   568  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
   569  						k8s.FolderIDAnnotation:  "",
   570  					},
   571  				},
   572  				Spec: map[string]interface{}{
   573  					"field": "val",
   574  				},
   575  			},
   576  			ns: &corev1.Namespace{
   577  				ObjectMeta: v1.ObjectMeta{
   578  					Annotations: map[string]string{
   579  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
   580  						k8s.FolderIDAnnotation:  "",
   581  					},
   582  					Name: "namespace-name",
   583  				},
   584  			},
   585  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   586  				{
   587  					Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   588  					Key:  "projectRef",
   589  				},
   590  				{
   591  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   592  					Key:  "folderRef",
   593  				},
   594  			},
   595  			containers: []corekccv1alpha1.Container{
   596  				{Type: corekccv1alpha1.ContainerTypeProject},
   597  				{Type: corekccv1alpha1.ContainerTypeFolder},
   598  			},
   599  			shouldErr: true,
   600  		},
   601  		{
   602  			name: "error if multiple references supported and multiple namespace annotations specified (no resource annotations specified)",
   603  			resource: &k8s.Resource{
   604  				Spec: map[string]interface{}{
   605  					"field": "val",
   606  				},
   607  			},
   608  			ns: &corev1.Namespace{
   609  				ObjectMeta: v1.ObjectMeta{
   610  					Annotations: map[string]string{
   611  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
   612  						k8s.FolderIDAnnotation:  "folder-id-from-namespace-annotation",
   613  					},
   614  					Name: "namespace-name",
   615  				},
   616  			},
   617  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   618  				{
   619  					Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   620  					Key:  "projectRef",
   621  				},
   622  				{
   623  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   624  					Key:  "folderRef",
   625  				},
   626  			},
   627  			containers: []corekccv1alpha1.Container{
   628  				{Type: corekccv1alpha1.ContainerTypeProject},
   629  				{Type: corekccv1alpha1.ContainerTypeFolder},
   630  			},
   631  			shouldErr: true,
   632  		},
   633  		{
   634  			name: "error if multiple references supported and multiple namespace annotations specified with one being set to empty string (no resource annotations specified)",
   635  			resource: &k8s.Resource{
   636  				Spec: map[string]interface{}{
   637  					"field": "val",
   638  				},
   639  			},
   640  			ns: &corev1.Namespace{
   641  				ObjectMeta: v1.ObjectMeta{
   642  					Annotations: map[string]string{
   643  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
   644  						k8s.FolderIDAnnotation:  "",
   645  					},
   646  					Name: "namespace-name",
   647  				},
   648  			},
   649  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   650  				{
   651  					Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   652  					Key:  "projectRef",
   653  				},
   654  				{
   655  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   656  					Key:  "folderRef",
   657  				},
   658  			},
   659  			containers: []corekccv1alpha1.Container{
   660  				{Type: corekccv1alpha1.ContainerTypeProject},
   661  				{Type: corekccv1alpha1.ContainerTypeFolder},
   662  			},
   663  			shouldErr: true,
   664  		},
   665  		{
   666  			name: "error if multiple references supported and multiple namespace annotations specified (no resource annotations supported)",
   667  			resource: &k8s.Resource{
   668  				Spec: map[string]interface{}{
   669  					"field": "val",
   670  				},
   671  			},
   672  			ns: &corev1.Namespace{
   673  				ObjectMeta: v1.ObjectMeta{
   674  					Annotations: map[string]string{
   675  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
   676  						k8s.FolderIDAnnotation:  "folder-id-from-namespace-annotation",
   677  					},
   678  					Name: "namespace-name",
   679  				},
   680  			},
   681  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   682  				{
   683  					Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   684  					Key:  "projectRef",
   685  				},
   686  				{
   687  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   688  					Key:  "folderRef",
   689  				},
   690  			},
   691  			shouldErr: true,
   692  		},
   693  		{
   694  			name: "error if multiple references supported and multiple namespace annotations specified with one being set to empty string (no resource annotations supported)",
   695  			resource: &k8s.Resource{
   696  				Spec: map[string]interface{}{
   697  					"field": "val",
   698  				},
   699  			},
   700  			ns: &corev1.Namespace{
   701  				ObjectMeta: v1.ObjectMeta{
   702  					Annotations: map[string]string{
   703  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
   704  						k8s.FolderIDAnnotation:  "",
   705  					},
   706  					Name: "namespace-name",
   707  				},
   708  			},
   709  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   710  				{
   711  					Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   712  					Key:  "projectRef",
   713  				},
   714  				{
   715  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   716  					Key:  "folderRef",
   717  				},
   718  			},
   719  			shouldErr: true,
   720  		},
   721  		{
   722  			name: "defaulting creates a new spec map if none present",
   723  			resource: &k8s.Resource{
   724  				ObjectMeta: v1.ObjectMeta{
   725  					Annotations: map[string]string{
   726  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
   727  					},
   728  				},
   729  			},
   730  			ns: &corev1.Namespace{
   731  				ObjectMeta: v1.ObjectMeta{
   732  					Annotations: map[string]string{
   733  						k8s.ProjectIDAnnotation: "project-id-from-namespace-annotation",
   734  					},
   735  					Name: "namespace-name",
   736  				},
   737  			},
   738  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   739  				{
   740  					Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   741  					Key:  "projectRef",
   742  				},
   743  			},
   744  			containers: []corekccv1alpha1.Container{
   745  				{Type: corekccv1alpha1.ContainerTypeProject},
   746  			},
   747  			expectedResource: &k8s.Resource{
   748  				ObjectMeta: v1.ObjectMeta{
   749  					Annotations: map[string]string{
   750  						k8s.ProjectIDAnnotation: "project-id-from-resource-annotation",
   751  					},
   752  				},
   753  				Spec: map[string]interface{}{
   754  					"projectRef": map[string]interface{}{
   755  						"external": "project-id-from-resource-annotation",
   756  					},
   757  				},
   758  			},
   759  		},
   760  	}
   761  
   762  	for _, tc := range tests {
   763  		tc := tc
   764  		t.Run(tc.name, func(t *testing.T) {
   765  			t.Parallel()
   766  			err := k8s.SetDefaultHierarchicalReference(tc.resource, tc.ns, tc.hierarchicalRefs, tc.containers)
   767  			if tc.shouldErr && err == nil {
   768  				t.Fatalf("got no error, but wanted one")
   769  			} else if !tc.shouldErr && err != nil {
   770  				t.Fatalf("got unexpected error: %v", err)
   771  			}
   772  
   773  			if err == nil {
   774  				if !test.Equals(t, tc.expectedResource, tc.resource) {
   775  					diff := cmp.Diff(tc.expectedResource, tc.resource)
   776  					t.Fatalf("unexpected diff in resource (-want +got):\n%v", diff)
   777  				}
   778  			}
   779  		})
   780  	}
   781  }
   782  
   783  func TestGetHierarchicalReference(t *testing.T) {
   784  	tests := []struct {
   785  		name             string
   786  		resource         *k8s.Resource
   787  		hierarchicalRefs []corekccv1alpha1.HierarchicalReference
   788  
   789  		resourceRef     *corekccv1alpha1.ResourceReference
   790  		hierarchicalRef corekccv1alpha1.HierarchicalReference
   791  		shouldErr       bool
   792  	}{
   793  		{
   794  			name:     "return nil resource reference if resource doesn't have a spec",
   795  			resource: &k8s.Resource{},
   796  		},
   797  		{
   798  			name: "return nil resource reference if resource doesn't support hierarchical references",
   799  			resource: &k8s.Resource{
   800  				Spec: map[string]interface{}{
   801  					"folderRef": map[string]interface{}{
   802  						"name": "folder-dep",
   803  					},
   804  				},
   805  			},
   806  		},
   807  		{
   808  			name: "return nil resource reference if resource supports a hierarchical reference, but spec does not specify that reference",
   809  			resource: &k8s.Resource{
   810  				Spec: map[string]interface{}{
   811  					"field": "val",
   812  				},
   813  			},
   814  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   815  				{
   816  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   817  					Key:  "folderRef",
   818  				},
   819  			},
   820  		},
   821  		{
   822  			name: "return resource reference if resource supports a hierarchical reference, and spec specifies that reference",
   823  			resource: &k8s.Resource{
   824  				Spec: map[string]interface{}{
   825  					"folderRef": map[string]interface{}{
   826  						"name": "folder-dep",
   827  					},
   828  				},
   829  			},
   830  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   831  				{
   832  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   833  					Key:  "folderRef",
   834  				},
   835  			},
   836  			resourceRef: &corekccv1alpha1.ResourceReference{
   837  				Name: "folder-dep",
   838  			},
   839  			hierarchicalRef: corekccv1alpha1.HierarchicalReference{
   840  				Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   841  				Key:  "folderRef",
   842  			},
   843  		},
   844  		{
   845  			name: "return nil resource reference if resource supports multiple hierarchical references, and spec specifies none of those references",
   846  			resource: &k8s.Resource{
   847  				Spec: map[string]interface{}{
   848  					"field": "val",
   849  				},
   850  			},
   851  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   852  				{
   853  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   854  					Key:  "folderRef",
   855  				},
   856  				{
   857  					Type: corekccv1alpha1.HierarchicalReferenceTypeOrganization,
   858  					Key:  "organizationRef",
   859  				},
   860  			},
   861  		},
   862  		{
   863  			name: "return resource reference if resource supports multiple hierarchical references, and spec specifies one of those references",
   864  			resource: &k8s.Resource{
   865  				Spec: map[string]interface{}{
   866  					"folderRef": map[string]interface{}{
   867  						"name": "folder-dep",
   868  					},
   869  				},
   870  			},
   871  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   872  				{
   873  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   874  					Key:  "folderRef",
   875  				},
   876  				{
   877  					Type: corekccv1alpha1.HierarchicalReferenceTypeOrganization,
   878  					Key:  "organizationRef",
   879  				},
   880  			},
   881  			resourceRef: &corekccv1alpha1.ResourceReference{
   882  				Name: "folder-dep",
   883  			},
   884  			hierarchicalRef: corekccv1alpha1.HierarchicalReference{
   885  				Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   886  				Key:  "folderRef",
   887  			},
   888  		},
   889  		{
   890  			name: "error if resource supports multiple hierarchical references, but spec specifies more than one of those references",
   891  			resource: &k8s.Resource{
   892  				Spec: map[string]interface{}{
   893  					"folderRef": map[string]interface{}{
   894  						"name": "folder-dep",
   895  					},
   896  					"organizationRef": map[string]interface{}{
   897  						"external": "123456789",
   898  					},
   899  				},
   900  			},
   901  			hierarchicalRefs: []corekccv1alpha1.HierarchicalReference{
   902  				{
   903  					Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
   904  					Key:  "folderRef",
   905  				},
   906  				{
   907  					Type: corekccv1alpha1.HierarchicalReferenceTypeOrganization,
   908  					Key:  "organizationRef",
   909  				},
   910  			},
   911  			shouldErr: true,
   912  		},
   913  	}
   914  
   915  	for _, tc := range tests {
   916  		tc := tc
   917  		t.Run(tc.name, func(t *testing.T) {
   918  			t.Parallel()
   919  			resourceRef, hierarchicalRef, err := k8s.GetHierarchicalReference(tc.resource, tc.hierarchicalRefs)
   920  			if tc.shouldErr {
   921  				if err == nil {
   922  					t.Fatalf("got no error, but wanted one")
   923  				}
   924  				if resourceRef != nil {
   925  					t.Fatalf("got a non-nil resource reference, but wanted nil since function returned an error")
   926  				}
   927  				if !isEmpty(hierarchicalRef) {
   928  					t.Fatalf("got a non-empty hierarchical reference, but wanted empty since function returned an error")
   929  				}
   930  				return
   931  			}
   932  			if err != nil {
   933  				t.Fatalf("got unexpected error: %v", err)
   934  			}
   935  
   936  			if !test.Equals(t, tc.resourceRef, resourceRef) {
   937  				diff := cmp.Diff(tc.resourceRef, resourceRef)
   938  				t.Fatalf("unexpected diff in resulting resource reference (-want +got):\n%v", diff)
   939  			}
   940  			if !test.Equals(t, tc.hierarchicalRef, hierarchicalRef) {
   941  				diff := cmp.Diff(tc.hierarchicalRef, hierarchicalRef)
   942  				t.Fatalf("unexpected diff in resulting hierarchical reference (-want +got):\n%v", diff)
   943  			}
   944  		})
   945  	}
   946  }
   947  
   948  func TestSetHierarchicalReference(t *testing.T) {
   949  	tests := []struct {
   950  		name            string
   951  		resource        *k8s.Resource
   952  		hierarchicalRef *corekccv1alpha1.HierarchicalReference
   953  		val             string
   954  
   955  		expectedResource *k8s.Resource
   956  	}{
   957  		{
   958  			name: "add hierarchical reference if not present",
   959  			resource: &k8s.Resource{
   960  				Spec: map[string]interface{}{
   961  					"field": "val",
   962  				},
   963  			},
   964  			hierarchicalRef: &corekccv1alpha1.HierarchicalReference{
   965  				Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   966  				Key:  "projectRef",
   967  			},
   968  			val: "project-id",
   969  			expectedResource: &k8s.Resource{
   970  				Spec: map[string]interface{}{
   971  					"field": "val",
   972  					"projectRef": map[string]interface{}{
   973  						"external": "project-id",
   974  					},
   975  				},
   976  			},
   977  		},
   978  		{
   979  			name: "overwrite hierarchical reference if present",
   980  			resource: &k8s.Resource{
   981  				Spec: map[string]interface{}{
   982  					"projectRef": map[string]interface{}{
   983  						"external": "old-project-id",
   984  					},
   985  				},
   986  			},
   987  			hierarchicalRef: &corekccv1alpha1.HierarchicalReference{
   988  				Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
   989  				Key:  "projectRef",
   990  			},
   991  			val: "project-id",
   992  			expectedResource: &k8s.Resource{
   993  				Spec: map[string]interface{}{
   994  					"projectRef": map[string]interface{}{
   995  						"external": "project-id",
   996  					},
   997  				},
   998  			},
   999  		},
  1000  		{
  1001  			name:     "add hierarchical reference even if spec not present",
  1002  			resource: &k8s.Resource{},
  1003  			hierarchicalRef: &corekccv1alpha1.HierarchicalReference{
  1004  				Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
  1005  				Key:  "projectRef",
  1006  			},
  1007  			val: "project-id",
  1008  			expectedResource: &k8s.Resource{
  1009  				Spec: map[string]interface{}{
  1010  					"projectRef": map[string]interface{}{
  1011  						"external": "project-id",
  1012  					},
  1013  				},
  1014  			},
  1015  		},
  1016  	}
  1017  
  1018  	for _, tc := range tests {
  1019  		tc := tc
  1020  		t.Run(tc.name, func(t *testing.T) {
  1021  			t.Parallel()
  1022  			if err := k8s.SetHierarchicalReference(tc.resource, tc.hierarchicalRef, tc.val); err != nil {
  1023  				t.Fatalf("got unexpected error: %v", err)
  1024  			}
  1025  			if !test.Equals(t, tc.expectedResource, tc.resource) {
  1026  				diff := cmp.Diff(tc.expectedResource, tc.resource)
  1027  				t.Fatalf("unexpected diff in resource (-want +got):\n%v", diff)
  1028  			}
  1029  		})
  1030  	}
  1031  }
  1032  
  1033  func isEmpty(h corekccv1alpha1.HierarchicalReference) bool {
  1034  	empty := corekccv1alpha1.HierarchicalReference{}
  1035  	return reflect.DeepEqual(h, empty)
  1036  }
  1037  

View as plain text