sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/repository/components_test.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package repository
    18  
    19  import (
    20  	"testing"
    21  
    22  	. "github.com/onsi/gomega"
    23  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    24  
    25  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    26  	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
    27  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
    28  )
    29  
    30  func Test_inspectTargetNamespace(t *testing.T) {
    31  	type args struct {
    32  		objs []unstructured.Unstructured
    33  	}
    34  	tests := []struct {
    35  		name    string
    36  		args    args
    37  		want    string
    38  		wantErr bool
    39  	}{
    40  		{
    41  			name: "get targetNamespace if exists",
    42  			args: args{
    43  				objs: []unstructured.Unstructured{
    44  					{
    45  						Object: map[string]interface{}{
    46  							"kind": "Namespace",
    47  							"metadata": map[string]interface{}{
    48  								"name": "foo",
    49  							},
    50  						},
    51  					},
    52  				},
    53  			},
    54  			want: "foo",
    55  		},
    56  		{
    57  			name: "return empty if there is no targetNamespace",
    58  			args: args{
    59  				objs: []unstructured.Unstructured{},
    60  			},
    61  			want: "",
    62  		},
    63  		{
    64  			name: "fails if two Namespace exists",
    65  			args: args{
    66  				objs: []unstructured.Unstructured{
    67  					{
    68  						Object: map[string]interface{}{
    69  							"kind": "Namespace",
    70  							"metadata": map[string]interface{}{
    71  								"name": "foo",
    72  							},
    73  						},
    74  					},
    75  					{
    76  						Object: map[string]interface{}{
    77  							"kind": "Namespace",
    78  							"metadata": map[string]interface{}{
    79  								"name": "bar",
    80  							},
    81  						},
    82  					},
    83  				},
    84  			},
    85  			want:    "",
    86  			wantErr: true,
    87  		},
    88  	}
    89  	for _, tt := range tests {
    90  		t.Run(tt.name, func(t *testing.T) {
    91  			g := NewWithT(t)
    92  
    93  			got, err := inspectTargetNamespace(tt.args.objs)
    94  			if tt.wantErr {
    95  				g.Expect(err).To(HaveOccurred())
    96  				return
    97  			}
    98  			g.Expect(err).ToNot(HaveOccurred())
    99  
   100  			g.Expect(got).To(Equal(tt.want))
   101  		})
   102  	}
   103  }
   104  
   105  func Test_fixTargetNamespace(t *testing.T) {
   106  	tests := []struct {
   107  		name            string
   108  		objs            []unstructured.Unstructured
   109  		targetNamespace string
   110  		want            []unstructured.Unstructured
   111  		wantErr         bool
   112  	}{
   113  		{
   114  			name: "fix Namespace object if exists",
   115  			objs: []unstructured.Unstructured{
   116  				{
   117  					Object: map[string]interface{}{
   118  						"apiVersion": "v1",
   119  						"kind":       namespaceKind,
   120  						"metadata": map[string]interface{}{
   121  							"name": "foo",
   122  						},
   123  					},
   124  				},
   125  			},
   126  			targetNamespace: "bar",
   127  			want: []unstructured.Unstructured{
   128  				{
   129  					Object: map[string]interface{}{
   130  						"apiVersion": "v1",
   131  						"kind":       namespaceKind,
   132  						"metadata": map[string]interface{}{
   133  							"name": "bar",
   134  						},
   135  					},
   136  				},
   137  			},
   138  		},
   139  		{
   140  			name: "fix namespaced objects",
   141  			objs: []unstructured.Unstructured{
   142  				{
   143  					Object: map[string]interface{}{
   144  						"apiVersion": "apps/v1",
   145  						"kind":       "Deployment",
   146  						"metadata": map[string]interface{}{
   147  							"name":      "test",
   148  							"namespace": "system",
   149  						},
   150  					},
   151  				},
   152  				{
   153  					Object: map[string]interface{}{
   154  						"apiVersion": "v1",
   155  						"kind":       "Service",
   156  						"metadata": map[string]interface{}{
   157  							"name":      "capa-controller-manager-metrics-service",
   158  							"namespace": "capa-system",
   159  						},
   160  					},
   161  				},
   162  			},
   163  			targetNamespace: "bar",
   164  			want: []unstructured.Unstructured{
   165  				{
   166  					Object: map[string]interface{}{
   167  						"apiVersion": "apps/v1",
   168  						"kind":       "Deployment",
   169  						"metadata": map[string]interface{}{
   170  							"name":      "test",
   171  							"namespace": "bar",
   172  						},
   173  					},
   174  				},
   175  				{
   176  					Object: map[string]interface{}{
   177  						"apiVersion": "v1",
   178  						"kind":       "Service",
   179  						"metadata": map[string]interface{}{
   180  							"name":      "capa-controller-manager-metrics-service",
   181  							"namespace": "bar",
   182  						},
   183  					},
   184  				},
   185  			},
   186  		},
   187  		{
   188  			name: "ignore global objects",
   189  			objs: []unstructured.Unstructured{
   190  				{
   191  					Object: map[string]interface{}{
   192  						"kind": "ClusterRole",
   193  					},
   194  				},
   195  			},
   196  			targetNamespace: "bar",
   197  			want: []unstructured.Unstructured{
   198  				{
   199  					Object: map[string]interface{}{
   200  						"kind": "ClusterRole",
   201  						// no namespace
   202  					},
   203  				},
   204  			},
   205  		},
   206  		{
   207  			name: "fix v1beta1 webhook configs",
   208  			objs: []unstructured.Unstructured{
   209  				{
   210  					Object: map[string]interface{}{
   211  						"apiVersion": "admissionregistration.k8s.io/v1beta1",
   212  						"kind":       "MutatingWebhookConfiguration",
   213  						"metadata": map[string]interface{}{
   214  							"annotations": map[string]interface{}{
   215  								"cert-manager.io/inject-ca-from": "capi-webhook-system/capm3-serving-cert",
   216  							},
   217  							"name": "capm3-mutating-webhook-configuration",
   218  						},
   219  						"webhooks": []interface{}{
   220  							map[string]interface{}{
   221  								"clientConfig": map[string]interface{}{
   222  									"caBundle": "Cg==",
   223  									"service": map[string]interface{}{
   224  										"name":      "capm3-webhook-service",
   225  										"namespace": "capi-webhook-system",
   226  										"path":      "/mutate-infrastructure-cluster-x-k8s-io-v1alpha4-metal3cluster",
   227  									},
   228  								},
   229  							},
   230  						},
   231  					},
   232  				},
   233  			},
   234  			targetNamespace: "bar",
   235  			want: []unstructured.Unstructured{
   236  				{
   237  					Object: map[string]interface{}{
   238  						"apiVersion": "admissionregistration.k8s.io/v1beta1",
   239  						"kind":       "MutatingWebhookConfiguration",
   240  						"metadata": map[string]interface{}{
   241  							"annotations": map[string]interface{}{
   242  								"cert-manager.io/inject-ca-from": "bar/capm3-serving-cert",
   243  							},
   244  							"creationTimestamp": nil,
   245  							"name":              "capm3-mutating-webhook-configuration",
   246  						},
   247  						"webhooks": []interface{}{
   248  							map[string]interface{}{
   249  								"name": "",
   250  								"clientConfig": map[string]interface{}{
   251  									"service": map[string]interface{}{
   252  										"name":      "capm3-webhook-service",
   253  										"path":      "/mutate-infrastructure-cluster-x-k8s-io-v1alpha4-metal3cluster",
   254  										"namespace": "bar",
   255  									},
   256  									"caBundle": "Cg==",
   257  								},
   258  							},
   259  						},
   260  					},
   261  				},
   262  			},
   263  		},
   264  		{
   265  			name: "unable to fix v1beta2 webhook configs",
   266  			objs: []unstructured.Unstructured{
   267  				{
   268  					Object: map[string]interface{}{
   269  						"apiVersion": "admissionregistration.k8s.io/v1beta2",
   270  						"kind":       "MutatingWebhookConfiguration",
   271  						"metadata": map[string]interface{}{
   272  							"annotations": map[string]interface{}{
   273  								"cert-manager.io/inject-ca-from": "capi-webhook-system/capm3-serving-cert",
   274  							},
   275  							"name": "capm3-mutating-webhook-configuration",
   276  						},
   277  						"webhooks": []interface{}{
   278  							map[string]interface{}{
   279  								"clientConfig": map[string]interface{}{
   280  									"caBundle": "Cg==",
   281  									"service": map[string]interface{}{
   282  										"name":      "capm3-webhook-service",
   283  										"namespace": "capi-webhook-system",
   284  										"path":      "/mutate-infrastructure-cluster-x-k8s-io-v1alpha4-metal3cluster",
   285  									},
   286  								},
   287  							},
   288  						},
   289  					},
   290  				},
   291  			},
   292  			targetNamespace: "bar",
   293  			wantErr:         true,
   294  		}, {
   295  			name: "fix v1 webhook configs",
   296  			objs: []unstructured.Unstructured{
   297  				{
   298  					Object: map[string]interface{}{
   299  						"apiVersion": "admissionregistration.k8s.io/v1",
   300  						"kind":       "MutatingWebhookConfiguration",
   301  						"metadata": map[string]interface{}{
   302  							"annotations": map[string]interface{}{
   303  								"cert-manager.io/inject-ca-from": "capi-webhook-system/capm3-serving-cert",
   304  							},
   305  							"name": "capm3-mutating-webhook-configuration",
   306  						},
   307  						"webhooks": []interface{}{
   308  							map[string]interface{}{
   309  								"clientConfig": map[string]interface{}{
   310  									"caBundle": "Cg==",
   311  									"service": map[string]interface{}{
   312  										"name":      "capm3-webhook-service",
   313  										"namespace": "capi-webhook-system",
   314  										"path":      "/mutate-infrastructure-cluster-x-k8s-io-v1alpha4-metal3cluster",
   315  									},
   316  								},
   317  							},
   318  						},
   319  					},
   320  				},
   321  			},
   322  			targetNamespace: "bar",
   323  			want: []unstructured.Unstructured{
   324  				{
   325  					Object: map[string]interface{}{
   326  						"apiVersion": "admissionregistration.k8s.io/v1",
   327  						"kind":       "MutatingWebhookConfiguration",
   328  						"metadata": map[string]interface{}{
   329  							"annotations": map[string]interface{}{
   330  								"cert-manager.io/inject-ca-from": "bar/capm3-serving-cert",
   331  							},
   332  							"creationTimestamp": nil,
   333  							"name":              "capm3-mutating-webhook-configuration",
   334  						},
   335  						"webhooks": []interface{}{
   336  							map[string]interface{}{
   337  								"name":                    "",
   338  								"admissionReviewVersions": nil,
   339  								"clientConfig": map[string]interface{}{
   340  									"service": map[string]interface{}{
   341  										"name":      "capm3-webhook-service",
   342  										"path":      "/mutate-infrastructure-cluster-x-k8s-io-v1alpha4-metal3cluster",
   343  										"namespace": "bar",
   344  									},
   345  									"caBundle": "Cg==",
   346  								},
   347  								"sideEffects": nil,
   348  							},
   349  						},
   350  					},
   351  				},
   352  			},
   353  		},
   354  		{
   355  			name: "fix v1beta1 crd webhook namespace",
   356  			objs: []unstructured.Unstructured{
   357  				{
   358  					Object: map[string]interface{}{
   359  						"apiVersion": "apiextensions.k8s.io/v1beta1",
   360  						"kind":       "CustomResourceDefinition",
   361  						"metadata": map[string]interface{}{
   362  							"annotations": map[string]interface{}{
   363  								"cert-manager.io/inject-ca-from": "capi-webhook-system/capm3-serving-cert",
   364  							},
   365  							"name": "aCoolName",
   366  						},
   367  						"spec": map[string]interface{}{
   368  							"conversion": map[string]interface{}{
   369  								"strategy": "Webhook",
   370  								"webhookClientConfig": map[string]interface{}{
   371  									"caBundle": "Cg==",
   372  									"service": map[string]interface{}{
   373  										"name":      "capa-webhook-service",
   374  										"namespace": "capi-webhook-system",
   375  										"path":      "/convert",
   376  									},
   377  								},
   378  							},
   379  						},
   380  					},
   381  				},
   382  			},
   383  			targetNamespace: "bar",
   384  			want: []unstructured.Unstructured{
   385  				{
   386  					Object: map[string]interface{}{
   387  						"apiVersion": "apiextensions.k8s.io/v1beta1",
   388  						"kind":       "CustomResourceDefinition",
   389  						"metadata": map[string]interface{}{
   390  							"annotations": map[string]interface{}{
   391  								"cert-manager.io/inject-ca-from": "bar/capm3-serving-cert",
   392  							},
   393  							"creationTimestamp": nil,
   394  							"name":              "aCoolName",
   395  						},
   396  						"spec": map[string]interface{}{
   397  							"group": "",
   398  							"names": map[string]interface{}{"plural": "", "kind": ""},
   399  							"scope": "",
   400  							"conversion": map[string]interface{}{
   401  								"strategy": "Webhook",
   402  								"webhookClientConfig": map[string]interface{}{
   403  									"caBundle": "Cg==",
   404  									"service": map[string]interface{}{
   405  										"name":      "capa-webhook-service",
   406  										"namespace": "bar",
   407  										"path":      "/convert",
   408  									},
   409  								},
   410  							},
   411  						},
   412  						"status": map[string]interface{}{
   413  							"storedVersions": nil,
   414  							"conditions":     nil,
   415  							"acceptedNames":  map[string]interface{}{"kind": "", "plural": ""},
   416  						},
   417  					},
   418  				},
   419  			},
   420  		},
   421  		{
   422  			name: "unable to fix v1beta2 crd webhook namespace",
   423  			objs: []unstructured.Unstructured{
   424  				{
   425  					Object: map[string]interface{}{
   426  						"apiVersion": "apiextensions.k8s.io/v1beta2",
   427  						"kind":       "CustomResourceDefinition",
   428  						"metadata": map[string]interface{}{
   429  							"annotations": map[string]interface{}{
   430  								"cert-manager.io/inject-ca-from": "capi-webhook-system/capm3-serving-cert",
   431  							},
   432  							"name": "aCoolName",
   433  						},
   434  						"spec": map[string]interface{}{
   435  							"conversion": map[string]interface{}{
   436  								"strategy": "Webhook",
   437  								"webhookClientConfig": map[string]interface{}{
   438  									"caBundle": "Cg==",
   439  									"service": map[string]interface{}{
   440  										"name":      "capa-webhook-service",
   441  										"namespace": "capi-webhook-system",
   442  										"path":      "/convert",
   443  									},
   444  								},
   445  							},
   446  						},
   447  					},
   448  				},
   449  			},
   450  			targetNamespace: "bar",
   451  			wantErr:         true,
   452  		},
   453  		{
   454  			name: "fix v1 crd webhook namespace",
   455  			objs: []unstructured.Unstructured{
   456  				{
   457  					Object: map[string]interface{}{
   458  						"apiVersion": "apiextensions.k8s.io/v1",
   459  						"kind":       "CustomResourceDefinition",
   460  						"metadata": map[string]interface{}{
   461  							"annotations": map[string]interface{}{
   462  								"cert-manager.io/inject-ca-from": "capi-webhook-system/capm3-serving-cert",
   463  							},
   464  							"name": "aCoolName",
   465  						},
   466  						"spec": map[string]interface{}{
   467  							"conversion": map[string]interface{}{
   468  								"strategy": "Webhook",
   469  								"webhook": map[string]interface{}{
   470  									"clientConfig": map[string]interface{}{
   471  										"caBundle": "Cg==",
   472  										"service": map[string]interface{}{
   473  											"name":      "capa-webhook-service",
   474  											"namespace": "capi-webhook-system",
   475  											"path":      "/convert",
   476  										},
   477  									},
   478  								},
   479  							},
   480  						},
   481  					},
   482  				},
   483  			},
   484  			targetNamespace: "bar",
   485  			want: []unstructured.Unstructured{
   486  				{
   487  					Object: map[string]interface{}{
   488  						"apiVersion": "apiextensions.k8s.io/v1",
   489  						"kind":       "CustomResourceDefinition",
   490  						"metadata": map[string]interface{}{
   491  							"annotations": map[string]interface{}{
   492  								"cert-manager.io/inject-ca-from": "bar/capm3-serving-cert",
   493  							},
   494  							"creationTimestamp": nil,
   495  							"name":              "aCoolName",
   496  						},
   497  						"spec": map[string]interface{}{
   498  							"group":    "",
   499  							"names":    map[string]interface{}{"plural": "", "kind": ""},
   500  							"scope":    "",
   501  							"versions": nil,
   502  							"conversion": map[string]interface{}{
   503  								"strategy": "Webhook",
   504  								"webhook": map[string]interface{}{
   505  									"conversionReviewVersions": nil,
   506  									"clientConfig": map[string]interface{}{
   507  										"caBundle": "Cg==",
   508  										"service": map[string]interface{}{
   509  											"name":      "capa-webhook-service",
   510  											"namespace": "bar",
   511  											"path":      "/convert",
   512  										},
   513  									},
   514  								},
   515  							},
   516  						},
   517  						"status": map[string]interface{}{
   518  							"storedVersions": nil,
   519  							"conditions":     nil,
   520  							"acceptedNames":  map[string]interface{}{"kind": "", "plural": ""},
   521  						},
   522  					},
   523  				},
   524  			},
   525  		},
   526  		{
   527  			name: "fix cert-manager Certificate",
   528  			objs: []unstructured.Unstructured{
   529  				{
   530  					Object: map[string]interface{}{
   531  						"apiVersion": "cert-manager.io/v1",
   532  						"kind":       "Certificate",
   533  						"metadata": map[string]interface{}{
   534  							"name":      "capi-serving-cert",
   535  							"namespace": "capi-system",
   536  						},
   537  						"spec": map[string]interface{}{
   538  							"dnsNames": []interface{}{
   539  								"capi-webhook-service.capi-system.svc",
   540  								"capi-webhook-service.capi-system.svc.cluster.local",
   541  								"random-other-dns-name",
   542  							},
   543  							"issuerRef": map[string]interface{}{
   544  								"kind": "Issuer",
   545  								"name": "capi-selfsigned-issuer",
   546  							},
   547  							"secretName": "capi-webhook-service-cert",
   548  						},
   549  					},
   550  				},
   551  			},
   552  			targetNamespace: "bar",
   553  			want: []unstructured.Unstructured{
   554  				{
   555  					Object: map[string]interface{}{
   556  						"apiVersion": "cert-manager.io/v1",
   557  						"kind":       "Certificate",
   558  						"metadata": map[string]interface{}{
   559  							"name":      "capi-serving-cert",
   560  							"namespace": "bar",
   561  						},
   562  						"spec": map[string]interface{}{
   563  							"dnsNames": []interface{}{
   564  								"capi-webhook-service.bar.svc",
   565  								"capi-webhook-service.bar.svc.cluster.local",
   566  								"random-other-dns-name",
   567  							},
   568  							"issuerRef": map[string]interface{}{
   569  								"kind": "Issuer",
   570  								"name": "capi-selfsigned-issuer",
   571  							},
   572  							"secretName": "capi-webhook-service-cert",
   573  						},
   574  					},
   575  				},
   576  			},
   577  		},
   578  	}
   579  	for _, tt := range tests {
   580  		t.Run(tt.name, func(t *testing.T) {
   581  			g := NewWithT(t)
   582  
   583  			got, err := fixTargetNamespace(tt.objs, tt.targetNamespace)
   584  			if tt.wantErr {
   585  				g.Expect(err).To(HaveOccurred())
   586  				return
   587  			}
   588  
   589  			g.Expect(err).ToNot(HaveOccurred())
   590  			g.Expect(got).To(ContainElements(tt.want)) // skipping from test the automatically added namespace Object
   591  		})
   592  	}
   593  }
   594  
   595  func Test_addNamespaceIfMissing(t *testing.T) {
   596  	type args struct {
   597  		objs            []unstructured.Unstructured
   598  		targetNamespace string
   599  	}
   600  	tests := []struct {
   601  		name string
   602  		args args
   603  	}{
   604  		{
   605  			name: "don't add Namespace object if exists",
   606  			args: args{
   607  				objs: []unstructured.Unstructured{
   608  					{
   609  						Object: map[string]interface{}{
   610  							"kind": namespaceKind,
   611  							"metadata": map[string]interface{}{
   612  								"name": "foo",
   613  							},
   614  						},
   615  					},
   616  				},
   617  				targetNamespace: "foo",
   618  			},
   619  		},
   620  		{
   621  			name: "add Namespace object if it does not exists",
   622  			args: args{
   623  				objs:            []unstructured.Unstructured{},
   624  				targetNamespace: "bar",
   625  			},
   626  		},
   627  	}
   628  	for _, tt := range tests {
   629  		t.Run(tt.name, func(t *testing.T) {
   630  			g := NewWithT(t)
   631  
   632  			got := addNamespaceIfMissing(tt.args.objs, tt.args.targetNamespace)
   633  
   634  			wgot, err := inspectTargetNamespace(got)
   635  			g.Expect(err).ToNot(HaveOccurred())
   636  			g.Expect(wgot).To(Equal(tt.args.targetNamespace))
   637  		})
   638  	}
   639  }
   640  
   641  func Test_addCommonLabels(t *testing.T) {
   642  	type args struct {
   643  		objs         []unstructured.Unstructured
   644  		name         string
   645  		providerType clusterctlv1.ProviderType
   646  	}
   647  	tests := []struct {
   648  		name string
   649  		args args
   650  		want []unstructured.Unstructured
   651  	}{
   652  		{
   653  			name: "add labels",
   654  			args: args{
   655  				objs: []unstructured.Unstructured{
   656  					{
   657  						Object: map[string]interface{}{
   658  							"kind": "ClusterRole",
   659  						},
   660  					},
   661  				},
   662  				name:         "provider",
   663  				providerType: clusterctlv1.InfrastructureProviderType,
   664  			},
   665  			want: []unstructured.Unstructured{
   666  				{
   667  					Object: map[string]interface{}{
   668  						"kind": "ClusterRole",
   669  						"metadata": map[string]interface{}{
   670  							"labels": map[string]interface{}{
   671  								clusterctlv1.ClusterctlLabel: "",
   672  								clusterv1.ProviderNameLabel:  "infrastructure-provider",
   673  							},
   674  						},
   675  					},
   676  				},
   677  			},
   678  		},
   679  	}
   680  	for _, tt := range tests {
   681  		t.Run(tt.name, func(t *testing.T) {
   682  			g := NewWithT(t)
   683  
   684  			got := addCommonLabels(tt.args.objs, config.NewProvider(tt.args.name, "", tt.args.providerType))
   685  			g.Expect(got).To(BeComparableTo(tt.want))
   686  		})
   687  	}
   688  }
   689  
   690  func TestAlterComponents(t *testing.T) {
   691  	c := &components{
   692  		targetNamespace: "test-ns",
   693  		objs: []unstructured.Unstructured{
   694  			{
   695  				Object: map[string]interface{}{
   696  					"kind": "ClusterRole",
   697  				},
   698  			},
   699  		},
   700  	}
   701  	want := []unstructured.Unstructured{
   702  		{
   703  			Object: map[string]interface{}{
   704  				"kind": "ClusterRole",
   705  				"metadata": map[string]interface{}{
   706  					"labels": map[string]interface{}{
   707  						clusterctlv1.ClusterctlLabel: "",
   708  						clusterv1.ProviderNameLabel:  "infrastructure-provider",
   709  					},
   710  				},
   711  			},
   712  		},
   713  	}
   714  
   715  	alterFn := func(objs []unstructured.Unstructured) ([]unstructured.Unstructured, error) {
   716  		// reusing addCommonLabels to do an example modification.
   717  		return addCommonLabels(objs, config.NewProvider("provider", "", clusterctlv1.InfrastructureProviderType)), nil
   718  	}
   719  
   720  	g := NewWithT(t)
   721  	if err := AlterComponents(c, alterFn); err != nil {
   722  		t.Errorf("AlterComponents() error = %v", err)
   723  	}
   724  	g.Expect(c.objs).To(BeComparableTo(want))
   725  }