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

     1  /*
     2  Copyright 2020 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 cluster
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	. "github.com/onsi/gomega"
    26  	corev1 "k8s.io/api/core/v1"
    27  	rbacv1 "k8s.io/api/rbac/v1"
    28  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    29  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  
    34  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    35  	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
    36  	"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/scheme"
    37  	"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
    38  )
    39  
    40  func Test_providerComponents_Delete(t *testing.T) {
    41  	labels := map[string]string{
    42  		clusterv1.ProviderNameLabel: "infrastructure-infra",
    43  	}
    44  
    45  	crd := unstructured.Unstructured{}
    46  	crd.SetAPIVersion("apiextensions.k8s.io/v1beta1")
    47  	crd.SetKind("CustomResourceDefinition")
    48  	crd.SetName("crd1")
    49  	crd.SetLabels(labels)
    50  	providerInventory := unstructured.Unstructured{}
    51  	providerInventory.SetAPIVersion(clusterctlv1.GroupVersion.String())
    52  	providerInventory.SetKind("Provider")
    53  	providerInventory.SetName("providerOne")
    54  	providerInventory.SetLabels(labels)
    55  	mutatingWebhook := unstructured.Unstructured{}
    56  	mutatingWebhook.SetAPIVersion("admissionregistration.k8s.io/v1beta1")
    57  	mutatingWebhook.SetKind("MutatingWebhookConfiguration")
    58  	mutatingWebhook.SetName("mwh1")
    59  	mutatingWebhook.SetLabels(labels)
    60  
    61  	initObjs := []client.Object{
    62  		// Namespace (should be deleted only if includeNamespace)
    63  		&corev1.Namespace{
    64  			TypeMeta: metav1.TypeMeta{
    65  				Kind: "Namespace",
    66  			},
    67  			ObjectMeta: metav1.ObjectMeta{
    68  				Name:   "ns1",
    69  				Labels: labels,
    70  			},
    71  		},
    72  		// A namespaced provider component (should always be deleted)
    73  		&corev1.Pod{
    74  			TypeMeta: metav1.TypeMeta{
    75  				Kind: "Pod",
    76  			},
    77  			ObjectMeta: metav1.ObjectMeta{
    78  				Namespace: "ns1",
    79  				Name:      "pod1",
    80  				Labels:    labels,
    81  			},
    82  		},
    83  		// Another object in the namespace but not belonging to the provider (should go away only when deleting the namespace)
    84  		&corev1.Pod{
    85  			TypeMeta: metav1.TypeMeta{
    86  				Kind: "Pod",
    87  			},
    88  			ObjectMeta: metav1.ObjectMeta{
    89  				Namespace: "ns1",
    90  				Name:      "pod2",
    91  			},
    92  		},
    93  		// CRDs (should be deleted only if includeCRD)
    94  		&crd,
    95  		// Inventory should be deleted ony if skipInventory
    96  		&providerInventory,
    97  		&mutatingWebhook,
    98  		// A cluster-wide provider component (should always be deleted)
    99  		&rbacv1.ClusterRole{
   100  			TypeMeta: metav1.TypeMeta{
   101  				Kind: "ClusterRole",
   102  			},
   103  			ObjectMeta: metav1.ObjectMeta{
   104  				Name:   "ns1-cluster-role", // global objects belonging to the provider have a namespace prefix.
   105  				Labels: labels,
   106  			},
   107  		},
   108  		// Another cluster-wide object (should never be deleted)
   109  		&rbacv1.ClusterRole{
   110  			TypeMeta: metav1.TypeMeta{
   111  				Kind: "ClusterRole",
   112  			},
   113  			ObjectMeta: metav1.ObjectMeta{
   114  				Name: "some-cluster-role",
   115  			},
   116  		},
   117  		// Another object out of the provider namespace (should never be deleted)
   118  		&corev1.Pod{
   119  			TypeMeta: metav1.TypeMeta{
   120  				Kind: "Pod",
   121  			},
   122  			ObjectMeta: metav1.ObjectMeta{
   123  				Namespace: "ns2",
   124  				Name:      "pod3",
   125  				Labels:    labels,
   126  			},
   127  		},
   128  	}
   129  
   130  	type args struct {
   131  		provider         clusterctlv1.Provider
   132  		includeNamespace bool
   133  		includeCRD       bool
   134  		skipInventory    bool
   135  	}
   136  
   137  	type wantDiff struct {
   138  		object  corev1.ObjectReference
   139  		deleted bool
   140  	}
   141  
   142  	tests := []struct {
   143  		name     string
   144  		args     args
   145  		wantDiff []wantDiff
   146  		wantErr  bool
   147  	}{
   148  		{
   149  			name: "Delete provider while preserving Namespace and CRDs and providerInventory",
   150  			args: args{
   151  				provider:         clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "infrastructure-infra", Namespace: "ns1"}, ProviderName: "infra", Type: string(clusterctlv1.InfrastructureProviderType)},
   152  				includeNamespace: false,
   153  				includeCRD:       false,
   154  				skipInventory:    true,
   155  			},
   156  			wantDiff: []wantDiff{
   157  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Namespace", Name: "ns1"}, deleted: false},                                                      // namespace should be preserved
   158  				{object: corev1.ObjectReference{APIVersion: "apiextensions.k8s.io/v1beta1", Kind: "CustomResourceDefinition", Name: "crd1"}, deleted: false},            // crd should be preserved
   159  				{object: corev1.ObjectReference{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "MutatingWebhookConfiguration", Name: "mwh1"}, deleted: true}, // MutatingWebhookConfiguration goes away with the controller
   160  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod1"}, deleted: true},                                          // provider components should be deleted
   161  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod2"}, deleted: false},                                         // other objects in the namespace should not be deleted
   162  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns2", Name: "pod3"}, deleted: false},                                         // this object is in another namespace, and should never be touched by delete
   163  				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "ns1-cluster-role"}, deleted: true},              // cluster-wide provider components should be deleted
   164  				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "some-cluster-role"}, deleted: false},            // other cluster-wide objects should be preserved
   165  				{object: corev1.ObjectReference{APIVersion: clusterctlv1.GroupVersion.String(), Kind: "Provider", Name: "providerOne"}, deleted: false},                 // providerInventory should be preserved
   166  			},
   167  			wantErr: false,
   168  		},
   169  		{
   170  			name: "Delete provider and provider namespace, while preserving CRDs and providerInventory",
   171  			args: args{
   172  				provider:         clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "infrastructure-infra", Namespace: "ns1"}, ProviderName: "infra", Type: string(clusterctlv1.InfrastructureProviderType)},
   173  				includeNamespace: true,
   174  				includeCRD:       false,
   175  				skipInventory:    true,
   176  			},
   177  			wantDiff: []wantDiff{
   178  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Namespace", Name: "ns1"}, deleted: true},                                                       // namespace should be deleted
   179  				{object: corev1.ObjectReference{APIVersion: "apiextensions.k8s.io/v1beta1", Kind: "CustomResourceDefinition", Name: "crd1"}, deleted: false},            // crd should be preserved
   180  				{object: corev1.ObjectReference{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "MutatingWebhookConfiguration", Name: "mwh1"}, deleted: true}, // MutatingWebhookConfiguration goes away with the controller
   181  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod1"}, deleted: true},                                          // provider components should be deleted
   182  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod2"}, deleted: true},                                          // other objects in the namespace goes away when deleting the namespace
   183  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns2", Name: "pod3"}, deleted: false},                                         // this object is in another namespace, and should never be touched by delete
   184  				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "ns1-cluster-role"}, deleted: true},              // cluster-wide provider components should be deleted
   185  				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "some-cluster-role"}, deleted: false},            // other cluster-wide objects should be preserved
   186  				{object: corev1.ObjectReference{APIVersion: clusterctlv1.GroupVersion.String(), Kind: "Provider", Name: "providerOne"}, deleted: false},                 // providerInventory should be preserved
   187  			},
   188  			wantErr: false,
   189  		},
   190  		{
   191  			name: "Delete provider and provider CRDs, while preserving the provider namespace and the providerInventory",
   192  			args: args{
   193  				provider:         clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "infrastructure-infra", Namespace: "ns1"}, ProviderName: "infra", Type: string(clusterctlv1.InfrastructureProviderType)},
   194  				includeNamespace: false,
   195  				includeCRD:       true,
   196  				skipInventory:    true,
   197  			},
   198  			wantDiff: []wantDiff{
   199  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Namespace", Name: "ns1"}, deleted: false},                                                      // namespace should be preserved
   200  				{object: corev1.ObjectReference{APIVersion: "apiextensions.k8s.io/v1beta1", Kind: "CustomResourceDefinition", Name: "crd1"}, deleted: true},             // crd should be deleted
   201  				{object: corev1.ObjectReference{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "MutatingWebhookConfiguration", Name: "mwh1"}, deleted: true}, // MutatingWebhookConfiguration should be deleted
   202  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod1"}, deleted: true},                                          // provider components should be deleted
   203  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod2"}, deleted: false},                                         // other objects in the namespace should not be deleted
   204  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns2", Name: "pod3"}, deleted: false},                                         // this object is in another namespace, and should never be touched by delete
   205  				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "ns1-cluster-role"}, deleted: true},              // cluster-wide provider components should be deleted
   206  				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "some-cluster-role"}, deleted: false},            // other cluster-wide objects should be preserved
   207  				{object: corev1.ObjectReference{APIVersion: clusterctlv1.GroupVersion.String(), Kind: "Provider", Name: "providerOne"}, deleted: false},                 // providerInventory should be preserved
   208  			},
   209  			wantErr: false,
   210  		},
   211  		{
   212  			name: "Delete providerInventory and provider while preserving provider CRDs and provider namespace",
   213  			args: args{
   214  				provider:         clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "infrastructure-infra", Namespace: "ns1"}, ProviderName: "infra", Type: string(clusterctlv1.InfrastructureProviderType)},
   215  				includeNamespace: false,
   216  				includeCRD:       false,
   217  				skipInventory:    false,
   218  			},
   219  			wantDiff: []wantDiff{
   220  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Namespace", Name: "ns1"}, deleted: false},                                                      // namespace should be deleted
   221  				{object: corev1.ObjectReference{APIVersion: "apiextensions.k8s.io/v1beta1", Kind: "CustomResourceDefinition", Name: "crd1"}, deleted: false},            // crd should not be deleted
   222  				{object: corev1.ObjectReference{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "MutatingWebhookConfiguration", Name: "mwh1"}, deleted: true}, // MutatingWebhookConfiguration should be deleted
   223  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod1"}, deleted: true},                                          // provider components should be deleted
   224  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod2"}, deleted: false},                                         // other objects in the namespace should not be deleted
   225  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns2", Name: "pod3"}, deleted: false},                                         // this object is in another namespace, and should never be touched by delete
   226  				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "ns1-cluster-role"}, deleted: true},              // cluster-wide provider components should be deleted
   227  				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "some-cluster-role"}, deleted: false},            // other cluster-wide objects should be preserved
   228  				{object: corev1.ObjectReference{APIVersion: clusterctlv1.GroupVersion.String(), Kind: "Provider", Name: "providerOne"}, deleted: true},                  // providerInventory should be deleted
   229  			},
   230  			wantErr: false,
   231  		},
   232  		{
   233  			name: "Delete provider, provider namespace and provider CRDs and the providerInventory",
   234  			args: args{
   235  				provider:         clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "infrastructure-infra", Namespace: "ns1"}, ProviderName: "infra", Type: string(clusterctlv1.InfrastructureProviderType)},
   236  				includeNamespace: true,
   237  				includeCRD:       true,
   238  				skipInventory:    false,
   239  			},
   240  			wantDiff: []wantDiff{
   241  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Namespace", Name: "ns1"}, deleted: true},                                                       // namespace should be deleted
   242  				{object: corev1.ObjectReference{APIVersion: "apiextensions.k8s.io/v1beta1", Kind: "CustomResourceDefinition", Name: "crd1"}, deleted: true},             // crd should be deleted
   243  				{object: corev1.ObjectReference{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "MutatingWebhookConfiguration", Name: "mwh1"}, deleted: true}, // MutatingWebhookConfiguration should be deleted
   244  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod1"}, deleted: true},                                          // provider components should be deleted
   245  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod2"}, deleted: true},                                          // other objects in the namespace goes away when deleting the namespace
   246  				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns2", Name: "pod3"}, deleted: false},                                         // this object is in another namespace, and should never be touched by delete
   247  				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "ns1-cluster-role"}, deleted: true},              // cluster-wide provider components should be deleted
   248  				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "some-cluster-role"}, deleted: false},            // other cluster-wide objects should be preserved
   249  				{object: corev1.ObjectReference{APIVersion: clusterctlv1.GroupVersion.String(), Kind: "Provider", Name: "providerOne"}, deleted: true},                  // providerInventory should be deleted
   250  			},
   251  			wantErr: false,
   252  		},
   253  	}
   254  	for _, tt := range tests {
   255  		t.Run(tt.name, func(t *testing.T) {
   256  			g := NewWithT(t)
   257  			proxy := test.NewFakeProxy().WithObjs(initObjs...)
   258  
   259  			c := newComponentsClient(proxy)
   260  
   261  			err := c.Delete(context.Background(), DeleteOptions{
   262  				Provider:         tt.args.provider,
   263  				IncludeNamespace: tt.args.includeNamespace,
   264  				IncludeCRDs:      tt.args.includeCRD,
   265  				SkipInventory:    tt.args.skipInventory,
   266  			})
   267  			if tt.wantErr {
   268  				g.Expect(err).To(HaveOccurred())
   269  				return
   270  			}
   271  
   272  			g.Expect(err).ToNot(HaveOccurred())
   273  
   274  			cs, err := proxy.NewClient(context.Background())
   275  			g.Expect(err).ToNot(HaveOccurred())
   276  
   277  			for _, want := range tt.wantDiff {
   278  				obj := &unstructured.Unstructured{}
   279  				obj.SetAPIVersion(want.object.APIVersion)
   280  				obj.SetKind(want.object.Kind)
   281  
   282  				key := client.ObjectKey{
   283  					Namespace: want.object.Namespace,
   284  					Name:      want.object.Name,
   285  				}
   286  
   287  				err := cs.Get(context.Background(), key, obj)
   288  				if err != nil && !apierrors.IsNotFound(err) {
   289  					t.Fatalf("Failed to get %v from the cluster: %v", key, err)
   290  				}
   291  
   292  				if !want.deleted && apierrors.IsNotFound(err) {
   293  					t.Errorf("%v deleted, expect NOT deleted", key)
   294  				}
   295  
   296  				if want.deleted && !apierrors.IsNotFound(err) {
   297  					if want.object.Namespace == tt.args.provider.Namespace && tt.args.includeNamespace { // Ignoring namespaced object that should be deleted by the namespace controller.
   298  						continue
   299  					}
   300  					t.Errorf("%v not deleted, expect deleted", key)
   301  				}
   302  			}
   303  		})
   304  	}
   305  }
   306  
   307  func Test_providerComponents_DeleteCoreProviderWebhookNamespace(t *testing.T) {
   308  	t.Run("deletes capi-webhook-system namespace", func(t *testing.T) {
   309  		g := NewWithT(t)
   310  		labels := map[string]string{
   311  			"foo": "bar",
   312  		}
   313  		initObjs := []client.Object{
   314  			&corev1.Namespace{
   315  				TypeMeta: metav1.TypeMeta{
   316  					Kind: "Namespace",
   317  				},
   318  				ObjectMeta: metav1.ObjectMeta{
   319  					Name:   "capi-webhook-system",
   320  					Labels: labels,
   321  				},
   322  			},
   323  		}
   324  
   325  		proxy := test.NewFakeProxy().WithObjs(initObjs...)
   326  		proxyClient, _ := proxy.NewClient(context.Background())
   327  		var nsList corev1.NamespaceList
   328  
   329  		// assert length before deleting
   330  		_ = proxyClient.List(context.Background(), &nsList)
   331  		g.Expect(nsList.Items).Should(HaveLen(1))
   332  
   333  		c := newComponentsClient(proxy)
   334  		err := c.DeleteWebhookNamespace(context.Background())
   335  		g.Expect(err).To(Not(HaveOccurred()))
   336  
   337  		// assert length after deleting
   338  		_ = proxyClient.List(context.Background(), &nsList)
   339  		g.Expect(nsList.Items).Should(BeEmpty())
   340  	})
   341  }
   342  
   343  func Test_providerComponents_Create(t *testing.T) {
   344  	labelsOne := map[string]string{
   345  		clusterv1.ProviderNameLabel: "infrastructure-infra",
   346  	}
   347  	commonObjects := []client.Object{
   348  		// Namespace for the provider
   349  		&corev1.Namespace{
   350  			TypeMeta: metav1.TypeMeta{
   351  				Kind:       "Namespace",
   352  				APIVersion: "v1",
   353  			},
   354  			ObjectMeta: metav1.ObjectMeta{
   355  				Name:   "ns1",
   356  				Labels: labelsOne,
   357  			},
   358  		},
   359  		// A cluster-wide provider component.
   360  		&rbacv1.ClusterRole{
   361  			TypeMeta: metav1.TypeMeta{
   362  				Kind:       "ClusterRole",
   363  				APIVersion: rbacv1.SchemeGroupVersion.WithKind("ClusterRole").GroupVersion().String(),
   364  			},
   365  			ObjectMeta: metav1.ObjectMeta{
   366  				Name:   "ns1-cluster-role", // global objects belonging to the provider have a namespace prefix.
   367  				Labels: labelsOne,
   368  			},
   369  		},
   370  	}
   371  	// A namespaced provider component as a pod.
   372  	podOne := &corev1.Pod{
   373  		TypeMeta: metav1.TypeMeta{
   374  			Kind:       "Pod",
   375  			APIVersion: "v1",
   376  		},
   377  		ObjectMeta: metav1.ObjectMeta{
   378  			Namespace: "ns1",
   379  			Name:      "pod1",
   380  			Labels:    labelsOne,
   381  		},
   382  		Spec: corev1.PodSpec{
   383  			Containers: []corev1.Container{
   384  				{
   385  					Image: "pod1-v1",
   386  				},
   387  			},
   388  		},
   389  	}
   390  	// podTwo is the same as podTwo but has an image titled pod1-v2.
   391  	podTwo := &corev1.Pod{
   392  		TypeMeta: metav1.TypeMeta{
   393  			Kind:       "Pod",
   394  			APIVersion: "v1",
   395  		},
   396  		ObjectMeta: metav1.ObjectMeta{
   397  			Namespace: "ns1",
   398  			Name:      "pod1",
   399  			Labels:    labelsOne,
   400  		},
   401  		Spec: corev1.PodSpec{
   402  			Containers: []corev1.Container{
   403  				{
   404  					Image: "pod1-v2",
   405  				},
   406  			},
   407  		},
   408  	}
   409  	type args struct {
   410  		objectsToCreate []client.Object
   411  		initObjects     []client.Object
   412  	}
   413  
   414  	tests := []struct {
   415  		name    string
   416  		args    args
   417  		want    []client.Object
   418  		wantErr bool
   419  	}{
   420  		{
   421  			name: "Create Provider Pod, Namespace and ClusterRole",
   422  			args: args{
   423  				objectsToCreate: append(commonObjects, podOne),
   424  				initObjects:     []client.Object{},
   425  			},
   426  			want:    append(commonObjects, podOne),
   427  			wantErr: false,
   428  		},
   429  		{
   430  			name: "Upgrade Provider Pod, Namespace and ClusterRole",
   431  			args: args{
   432  				objectsToCreate: append(commonObjects, podTwo),
   433  				initObjects:     append(commonObjects, podOne),
   434  			},
   435  			want:    append(commonObjects, podTwo),
   436  			wantErr: false,
   437  		},
   438  	}
   439  	for _, tt := range tests {
   440  		t.Run(tt.name, func(t *testing.T) {
   441  			g := NewWithT(t)
   442  
   443  			proxy := test.NewFakeProxy().WithObjs(tt.args.initObjects...)
   444  			c := newComponentsClient(proxy)
   445  			var unstructuredObjectsToCreate []unstructured.Unstructured
   446  			for _, obj := range tt.args.objectsToCreate {
   447  				uns := &unstructured.Unstructured{}
   448  				if err := scheme.Scheme.Convert(obj, uns, nil); err != nil {
   449  					g.Expect(fmt.Errorf("%v %v could not be converted to unstructured", err.Error(), obj)).ToNot(HaveOccurred())
   450  				}
   451  				unstructuredObjectsToCreate = append(unstructuredObjectsToCreate, *uns)
   452  			}
   453  			err := c.Create(context.Background(), unstructuredObjectsToCreate)
   454  			if tt.wantErr {
   455  				g.Expect(err).To(HaveOccurred())
   456  				return
   457  			}
   458  
   459  			g.Expect(err).ToNot(HaveOccurred())
   460  
   461  			cs, err := proxy.NewClient(context.Background())
   462  			g.Expect(err).ToNot(HaveOccurred())
   463  
   464  			for _, item := range tt.want {
   465  				obj := &unstructured.Unstructured{}
   466  				obj.SetKind(item.GetObjectKind().GroupVersionKind().Kind)
   467  				obj.SetAPIVersion(item.GetObjectKind().GroupVersionKind().GroupVersion().String())
   468  				key := client.ObjectKey{
   469  					Namespace: item.GetNamespace(),
   470  					Name:      item.GetName(),
   471  				}
   472  
   473  				err := cs.Get(context.Background(), key, obj)
   474  
   475  				if err != nil && !apierrors.IsNotFound(err) {
   476  					t.Fatalf("Failed to get %v from the cluster: %v", key, err)
   477  				}
   478  				g.Expect(obj.GetNamespace()).To(Equal(item.GetNamespace()), cmp.Diff(obj.GetNamespace(), item.GetNamespace()))
   479  				g.Expect(obj.GetName()).To(Equal(item.GetName()), cmp.Diff(obj.GetName(), item.GetName()))
   480  				g.Expect(obj.GetAPIVersion()).To(Equal(item.GetObjectKind().GroupVersionKind().GroupVersion().String()), cmp.Diff(obj.GetAPIVersion(), item.GetObjectKind().GroupVersionKind().GroupVersion().String()))
   481  				if item.GetObjectKind().GroupVersionKind().Kind == "Pod" {
   482  					p1, okp1 := item.(*corev1.Pod)
   483  					if !(okp1) {
   484  						g.Expect(fmt.Errorf("%v %v could retrieve pod", err.Error(), obj)).ToNot(HaveOccurred())
   485  					}
   486  					p2 := &corev1.Pod{}
   487  					if err := scheme.Scheme.Convert(obj, p2, nil); err != nil {
   488  						g.Expect(fmt.Errorf("%v %v could not be converted to unstructured", err.Error(), obj)).ToNot(HaveOccurred())
   489  					}
   490  					if len(p1.Spec.Containers) == 0 || len(p2.Spec.Containers) == 0 {
   491  						g.Expect(fmt.Errorf("%v %v could not be converted to unstructured", err.Error(), obj)).ToNot(HaveOccurred())
   492  					}
   493  					g.Expect(p1.Spec.Containers[0].Image).To(Equal(p2.Spec.Containers[0].Image), cmp.Diff(obj.GetNamespace(), item.GetNamespace()))
   494  				}
   495  			}
   496  		})
   497  	}
   498  }
   499  
   500  func Test_providerComponents_ValidateNoObjectsExist(t *testing.T) {
   501  	labels := map[string]string{
   502  		clusterv1.ProviderNameLabel: "infrastructure-infra",
   503  	}
   504  
   505  	crd := &apiextensionsv1.CustomResourceDefinition{
   506  		TypeMeta: metav1.TypeMeta{
   507  			Kind:       "CustomResourceDefinition",
   508  			APIVersion: apiextensionsv1.SchemeGroupVersion.Identifier(),
   509  		},
   510  		ObjectMeta: metav1.ObjectMeta{
   511  			Name:   "crd1",
   512  			Labels: labels,
   513  		},
   514  		Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   515  			Group: "some.group",
   516  			Names: apiextensionsv1.CustomResourceDefinitionNames{
   517  				ListKind: "SomeCRDList",
   518  				Kind:     "SomeCRD",
   519  			},
   520  			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   521  				{Name: "v1", Storage: true},
   522  			},
   523  		},
   524  	}
   525  	crd.ObjectMeta.Labels[clusterctlv1.ClusterctlLabel] = ""
   526  
   527  	cr := &unstructured.Unstructured{}
   528  	cr.SetAPIVersion("some.group/v1")
   529  	cr.SetKind("SomeCRD")
   530  	cr.SetName("cr1")
   531  
   532  	tests := []struct {
   533  		name     string
   534  		provider clusterctlv1.Provider
   535  		initObjs []client.Object
   536  		wantErr  bool
   537  	}{
   538  		{
   539  			name:     "No objects exist",
   540  			provider: clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "infrastructure-infra", Namespace: "ns1"}, ProviderName: "infra", Type: string(clusterctlv1.InfrastructureProviderType)},
   541  			initObjs: []client.Object{},
   542  			wantErr:  false,
   543  		},
   544  		{
   545  			name:     "CRD exists but no objects",
   546  			provider: clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "infrastructure-infra", Namespace: "ns1"}, ProviderName: "infra", Type: string(clusterctlv1.InfrastructureProviderType)},
   547  			initObjs: []client.Object{
   548  				crd,
   549  			},
   550  			wantErr: false,
   551  		},
   552  		{
   553  			name:     "CRD exists but and also objects",
   554  			provider: clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "infrastructure-infra", Namespace: "ns1"}, ProviderName: "infra", Type: string(clusterctlv1.InfrastructureProviderType)},
   555  			initObjs: []client.Object{
   556  				crd,
   557  				cr,
   558  			},
   559  			wantErr: true,
   560  		},
   561  	}
   562  	for _, tt := range tests {
   563  		t.Run(tt.name, func(t *testing.T) {
   564  			proxy := test.NewFakeProxy().WithObjs(tt.initObjs...)
   565  
   566  			c := newComponentsClient(proxy)
   567  
   568  			if err := c.ValidateNoObjectsExist(context.Background(), tt.provider); (err != nil) != tt.wantErr {
   569  				t.Errorf("providerComponents.ValidateNoObjectsExist() error = %v, wantErr %v", err, tt.wantErr)
   570  			}
   571  		})
   572  	}
   573  }