Repository: kubernetes-sigs/cluster-api-operator

Issue:
Add more condition validations for providers in e2e tests
<!-- NOTE: ⚠️ For larger proposals, we follow the CAEP process as outlined in https://sigs.k8s.io/cluster-api/CONTRIBUTING.md. -->

**User Story**

As a developer I would like to add more (probably all possible conditions) in some e2e tests.

**Detailed Description**
Right now, e2e tests, for example in `air_gapped_test.go` just single `operatorv1.ProviderInstalledCondition` is validated. But, to avoid potential errors it would be nice to validate all possible conditions in test.

These checks can be done in just one test, so as not to overload the system and not to fix a lot of code.

This would be able to detect cases like those described in #755 ticket as early as possible

[A clear and concise description of what you want to happen.]

**Anything else you would like to add:**
/assign

[Miscellaneous information that will assist in solving the issue.]

/kind feature


Hints from issue comments:
@furkatgofurov7: /triage accepted

+1, we could validate it in a single test 👍🏼 

Solution patch:
diff --git a/api/v1alpha2/conditions_consts.go b/api/v1alpha2/conditions_consts.go
index 4a4c40c..355af99 100644
--- a/api/v1alpha2/conditions_consts.go
+++ b/api/v1alpha2/conditions_consts.go
@@ -56,7 +56,7 @@ const (
 	// WaitingForCoreProviderReadyReason documents that the provider is waiting for the core provider to be ready.
 	WaitingForCoreProviderReadyReason = "WaitingForCoreProviderReady"
 
-	// InvalidGithubTokenReason documents that the provided github token is invalid.
+	// InvalidGithubTokenReason documents that the provided GitHub token is invalid.
 	InvalidGithubTokenReason = "InvalidGithubTokenError"
 
 	// NoDeploymentAvailableConditionReason documents that there is no Available condition for provider deployment yet.
diff --git a/index.yaml b/index.yaml
index c0100f0..64787a8 100644
--- a/index.yaml
+++ b/index.yaml
@@ -1,6 +1,26 @@
 apiVersion: v1
 entries:
   cluster-api-operator:
+  - apiVersion: v2
+    appVersion: 0.18.1
+    created: "2025-04-02T11:43:16.092682+03:00"
+    description: Cluster API Operator
+    digest: f157851bc2aeb90fbfde3343930eb350b339fc7349936699e2f539d46ac0a083
+    name: cluster-api-operator
+    type: application
+    urls:
+    - https://github.com/kubernetes-sigs/cluster-api-operator/releases/download/v0.18.1/cluster-api-operator-0.18.1.tgz
+    version: 0.18.1
+  - apiVersion: v2
+    appVersion: 0.18.0
+    created: "2025-03-28T12:52:26.014183+02:00"
+    description: Cluster API Operator
+    digest: b2aa7e2389772f5cfe31fbf51d12ef4696302cda1143d58dc5a1ed5a599ffd3f
+    name: cluster-api-operator
+    type: application
+    urls:
+    - https://github.com/kubernetes-sigs/cluster-api-operator/releases/download/v0.18.0/cluster-api-operator-0.18.0.tgz
+    version: 0.18.0
   - apiVersion: v2
     appVersion: 0.17.1
     created: "2025-03-12T19:30:41.723785+02:00"
@@ -286,4 +306,4 @@ entries:
     urls:
     - https://github.com/kubernetes-sigs/cluster-api-operator/releases/download/v0.2.0/cluster-api-operator-0.2.0.tgz
     version: 0.2.0
-generated: "2025-03-12T19:30:41.723914+02:00"
+generated: "2025-04-02T11:43:16.092789+03:00"
diff --git a/internal/controller/coreprovider_to_providers.go b/internal/controller/coreprovider_to_providers.go
index dbfdcfe..1adffda 100644
--- a/internal/controller/coreprovider_to_providers.go
+++ b/internal/controller/coreprovider_to_providers.go
@@ -34,7 +34,7 @@ import (
 // It lists all the providers and if its PreflightCheckCondition is not True, this object will be added to the resulting request.
 // This means that notifications will only be sent to those objects that have not pass PreflightCheck.
 func newCoreProviderToProviderFuncMapForProviderList(k8sClient client.Client, providerList genericprovider.GenericProviderList) handler.MapFunc {
-	providerListType := fmt.Sprintf("%t", providerList)
+	providerListType := fmt.Sprintf("%T", providerList)
 
 	return func(ctx context.Context, obj client.Object) []reconcile.Request {
 		log := ctrl.LoggerFrom(ctx).WithValues("provider", map[string]string{"name": obj.GetName(), "namespace": obj.GetNamespace()}, "providerListType", providerListType)
diff --git a/internal/controller/genericprovider_controller.go b/internal/controller/genericprovider_controller.go
index 715437d..d2fc49e 100644
--- a/internal/controller/genericprovider_controller.go
+++ b/internal/controller/genericprovider_controller.go
@@ -164,7 +164,7 @@ func (r *GenericProviderReconciler) Reconcile(ctx context.Context, req reconcile
 
 	r.Provider.SetAnnotations(annotations)
 
-	return res, err
+	return res, ignoreCoreProviderWaitError(err)
 }
 
 func patchProvider(ctx context.Context, provider operatorv1.GenericProvider, patchHelper *patch.Helper, options ...patch.Option) error {
@@ -206,7 +206,7 @@ func (r *GenericProviderReconciler) reconcile(ctx context.Context, provider gene
 
 		if !res.IsZero() || err != nil {
 			// the steps are sequential, so we must be complete before progressing.
-			return res, ignoreCoreProviderWaitError(err)
+			return res, err
 		}
 	}
 
diff --git a/internal/controller/secrets_to_providers.go b/internal/controller/secrets_to_providers.go
index 75f927a..8f4aaf9 100644
--- a/internal/controller/secrets_to_providers.go
+++ b/internal/controller/secrets_to_providers.go
@@ -38,7 +38,7 @@ const (
 // It lists all the providers matching spec.configSecret.name values with the secret name querying by index.
 // If the provider references a secret without a namespace, it will assume the secret is in the same namespace as the provider.
 func newSecretToProviderFuncMapForProviderList(k8sClient client.Client, providerList genericprovider.GenericProviderList) handler.MapFunc {
-	providerListType := fmt.Sprintf("%t", providerList)
+	providerListType := fmt.Sprintf("%T", providerList)
 
 	return func(ctx context.Context, secret client.Object) []reconcile.Request {
 		log := ctrl.LoggerFrom(ctx).WithValues("secret", map[string]string{"name": secret.GetName(), "namespace": secret.GetNamespace()}, "providerListType", providerListType)
diff --git a/plugins/clusterctl-operator.yaml b/plugins/clusterctl-operator.yaml
index c22bdb0..288a4f3 100644
--- a/plugins/clusterctl-operator.yaml
+++ b/plugins/clusterctl-operator.yaml
@@ -3,7 +3,7 @@ kind: Plugin
 metadata:
   name: operator
 spec:
-  version: v0.17.1
+  version: v0.18.1
   homepage: https://github.com/kubernetes-sigs/cluster-api-operator
   shortDescription: Use this clusterctl plugin to bootstrap a management cluster for Cluster API with the Cluster API Operator.
   description: |
@@ -13,36 +13,36 @@ spec:
       matchLabels:
         os: darwin
         arch: amd64
-    uri: https://github.com/kubernetes-sigs/cluster-api-operator/releases/download/v0.17.1/clusterctl-operator_v0.17.1_darwin_amd64.tar.gz
-    sha256: ba58fbdf5b79689573e87443801b6a89d3720bdca519cfc9808a83db13c5eef4
+    uri: https://github.com/kubernetes-sigs/cluster-api-operator/releases/download/v0.18.1/clusterctl-operator_v0.18.1_darwin_amd64.tar.gz
+    sha256: 49ff47935465deb8446b957ffa551008e603102d568d12dfaf00ef76a3bdda53
     bin: bin/clusterctl-operator
   - selector:
       matchLabels:
         os: darwin
         arch: arm64
-    uri: https://github.com/kubernetes-sigs/cluster-api-operator/releases/download/v0.17.1/clusterctl-operator_v0.17.1_darwin_arm64.tar.gz
-    sha256: 6bc7002f6b96320f51835925a43302085b8e11bf4ab3e515da46ca665c223703
+    uri: https://github.com/kubernetes-sigs/cluster-api-operator/releases/download/v0.18.1/clusterctl-operator_v0.18.1_darwin_arm64.tar.gz
+    sha256: e966756f41a1f0b4328902d15e7972d06999a959f758247e5e6a2e4a5e5d8a7b
     bin: bin/clusterctl-operator
   - selector:
       matchLabels:
         os: linux
         arch: amd64
-    uri: https://github.com/kubernetes-sigs/cluster-api-operator/releases/download/v0.17.1/clusterctl-operator_v0.17.1_linux_amd64.tar.gz
-    sha256: e2b9b2bb0208e2612ac3cbeaa2cda3881128394b7ca6c1644cf946170f2dac04
+    uri: https://github.com/kubernetes-sigs/cluster-api-operator/releases/download/v0.18.1/clusterctl-operator_v0.18.1_linux_amd64.tar.gz
+    sha256: 9ccd83943dc83240d313842e2f4bedf5c8f412c0462f7acfdff8852411c78210
     bin: bin/clusterctl-operator
   - selector:
       matchLabels:
         os: linux
         arch: arm64
-    uri: https://github.com/kubernetes-sigs/cluster-api-operator/releases/download/v0.17.1/clusterctl-operator_v0.17.1_linux_arm64.tar.gz
-    sha256: f980e00fa92dd9ec5647794d51c8064d38be78c0d8d58212874f3b07eb7bdba7
+    uri: https://github.com/kubernetes-sigs/cluster-api-operator/releases/download/v0.18.1/clusterctl-operator_v0.18.1_linux_arm64.tar.gz
+    sha256: 011c0eeb4663613f9ffa3379a19d0c21ff36f868a846149131800f11aefa219d
     bin: bin/clusterctl-operator
   - selector:
       matchLabels:
         os: windows
         arch: amd64
-    uri: https://github.com/kubernetes-sigs/cluster-api-operator/releases/download/v0.17.1/clusterctl-operator_v0.17.1_windows_amd64.tar.gz
-    sha256: 42376a61146fc73394d92392744c22f652ba94afe1a80890c77b020e1b438254
+    uri: https://github.com/kubernetes-sigs/cluster-api-operator/releases/download/v0.18.1/clusterctl-operator_v0.18.1_windows_amd64.tar.gz
+    sha256: f7655dde21786dc50605d66de88db155af06beb67c600719dde3b3667189c707
     bin: bin/clusterctl-operator.exe
 
 



Test patch:
diff --git a/internal/controller/genericprovider_controller_test.go b/internal/controller/genericprovider_controller_test.go
index 786cfa9..f6d6f4b 100644
--- a/internal/controller/genericprovider_controller_test.go
+++ b/internal/controller/genericprovider_controller_test.go
@@ -31,6 +31,7 @@ import (
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
 	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
 	"sigs.k8s.io/cluster-api/util/conditions"
+	"sigs.k8s.io/cluster-api/util/patch"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"
@@ -200,7 +201,7 @@ func TestConfigSecretChangesAreAppliedToTheDeployment(t *testing.T) {
 
 	g.Eventually(
 		testDeploymentLabelValueGetter(ns.Name, testDeploymentName),
-		30*time.Second,
+		timeout,
 	).Should(BeEquivalentTo("initial-value"))
 
 	t.Log("Provider deployment deployed")
@@ -213,7 +214,7 @@ func TestConfigSecretChangesAreAppliedToTheDeployment(t *testing.T) {
 
 	g.Eventually(
 		testDeploymentLabelValueGetter(ns.Name, testDeploymentName),
-		30*time.Second,
+		timeout,
 	).Should(BeEquivalentTo("updated-value"))
 }
 
@@ -293,6 +294,17 @@ func TestReconcilerPreflightConditions(t *testing.T) {
 				g.Expect(env.CreateAndWait(ctx, p)).To(Succeed())
 			}
 
+			defer func() {
+				objs := []client.Object{
+					dummyConfigMap(tc.namespace),
+				}
+				for _, p := range tc.providers {
+					objs = append(objs, p)
+				}
+
+				g.Expect(env.CleanupAndWait(ctx, objs...)).To(Succeed())
+			}()
+
 			g.Eventually(func() bool {
 				for _, p := range tc.providers {
 					if err := env.Get(ctx, client.ObjectKeyFromObject(p), p); err != nil {
@@ -306,20 +318,6 @@ func TestReconcilerPreflightConditions(t *testing.T) {
 
 				return false
 			}, timeout).Should(BeEquivalentTo(true))
-
-			objs := []client.Object{}
-			for _, p := range tc.providers {
-				objs = append(objs, p)
-			}
-
-			objs = append(objs, &corev1.ConfigMap{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      testCurrentVersion,
-					Namespace: tc.namespace,
-				},
-			})
-
-			g.Expect(env.CleanupAndWait(ctx, objs...)).To(Succeed())
 		})
 	}
 }
@@ -395,6 +393,11 @@ releaseSeries:
 			t.Log("creating test provider", provider.GetName())
 			g.Expect(env.CreateAndWait(ctx, provider)).To(Succeed())
 
+			defer func() {
+				// Clean up
+				g.Expect(env.CleanupAndWait(ctx, []client.Object{provider, dummyFutureConfigMap(namespace, currentVersion), dummyFutureConfigMap(namespace, tc.newVersion)}...)).To(Succeed())
+			}()
+
 			g.Eventually(func() bool {
 				if err := env.Get(ctx, client.ObjectKeyFromObject(provider), provider); err != nil {
 					return false
@@ -509,24 +512,6 @@ releaseSeries:
 
 				return allSet
 			}, 2*time.Second).Should(BeTrue())
-
-			// Clean up
-			objs := []client.Object{provider}
-			objs = append(objs, &corev1.ConfigMap{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      currentVersion,
-					Namespace: namespace,
-				},
-			})
-
-			objs = append(objs, &corev1.ConfigMap{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      tc.newVersion,
-					Namespace: namespace,
-				},
-			})
-
-			g.Expect(env.CleanupAndWait(ctx, objs...)).To(Succeed())
 		})
 	}
 }
@@ -573,9 +558,7 @@ func TestProviderShouldNotBeInstalledWhenCoreProviderNotReady(t *testing.T) {
 			return false
 		}
 
-		providerInstalled := conditions.Get(coreProvider, operatorv1.ProviderInstalledCondition)
-
-		return providerInstalled != nil && providerInstalled.Status == corev1.ConditionFalse
+		return conditions.Has(coreProvider, operatorv1.ProviderInstalledCondition) && conditions.IsFalse(coreProvider, operatorv1.ProviderInstalledCondition)
 	}, timeout).Should(BeEquivalentTo(true))
 
 	g.Consistently(func() bool {
@@ -589,6 +572,88 @@ func TestProviderShouldNotBeInstalledWhenCoreProviderNotReady(t *testing.T) {
 	}, timeout/3).Should(BeEquivalentTo(true))
 }
 
+func TestReconcilerPreflightConditionsFromCoreProviderEvents(t *testing.T) {
+	namespace := "test-core-provider-events"
+	coreProvider := &operatorv1.CoreProvider{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "cluster-api",
+		},
+		Spec: operatorv1.CoreProviderSpec{
+			ProviderSpec: operatorv1.ProviderSpec{
+				Version: testCurrentVersion,
+			},
+		},
+	}
+	infrastructureProvider := &operatorv1.InfrastructureProvider{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "vsphere",
+		},
+		Spec: operatorv1.InfrastructureProviderSpec{
+			ProviderSpec: operatorv1.ProviderSpec{
+				Version: testCurrentVersion,
+			},
+		},
+	}
+
+	g := NewWithT(t)
+	t.Log("Ensure namespace exists", namespace)
+	g.Expect(env.EnsureNamespaceExists(ctx, namespace)).To(Succeed())
+
+	g.Expect(env.CreateAndWait(ctx, dummyConfigMap(namespace))).To(Succeed())
+
+	for _, p := range []genericprovider.GenericProvider{coreProvider, infrastructureProvider} {
+		insertDummyConfig(p)
+		p.SetNamespace(namespace)
+		t.Log("creating test provider", p.GetName())
+		g.Expect(env.CreateAndWait(ctx, p)).To(Succeed())
+	}
+
+	defer func() {
+		g.Expect(env.CleanupAndWait(ctx, []client.Object{coreProvider, infrastructureProvider, dummyConfigMap(namespace)}...)).To(Succeed())
+	}()
+
+	g.Eventually(func() bool {
+		if err := env.Get(ctx, client.ObjectKeyFromObject(infrastructureProvider), infrastructureProvider); err != nil {
+			return false
+		}
+
+		if conditions.Has(infrastructureProvider, operatorv1.PreflightCheckCondition) && conditions.IsFalse(infrastructureProvider, operatorv1.PreflightCheckCondition) {
+			return true
+		}
+
+		return false
+	}, timeout).Should(BeEquivalentTo(true))
+
+	g.Eventually(func() bool {
+		if err := env.Get(ctx, client.ObjectKeyFromObject(coreProvider), coreProvider); err != nil {
+			return false
+		}
+
+		if conditions.IsTrue(coreProvider, operatorv1.PreflightCheckCondition) && conditions.IsTrue(coreProvider, operatorv1.ProviderInstalledCondition) {
+			return true
+		}
+
+		return false
+	}, timeout).Should(BeEquivalentTo(true))
+
+	patchHelper, err := patch.NewHelper(coreProvider, env)
+	g.Expect(err).ToNot(HaveOccurred())
+	conditions.MarkTrue(coreProvider, clusterv1.ReadyCondition)
+	g.Expect(patchHelper.Patch(ctx, coreProvider)).To(Succeed())
+
+	g.Eventually(func() bool {
+		if err := env.Get(ctx, client.ObjectKeyFromObject(infrastructureProvider), infrastructureProvider); err != nil {
+			return false
+		}
+
+		if conditions.IsTrue(infrastructureProvider, operatorv1.PreflightCheckCondition) {
+			return true
+		}
+
+		return false
+	}, timeout).Should(BeEquivalentTo(true))
+}
+
 func TestProviderConfigSecretChanges(t *testing.T) {
 	testCases := []struct {
 		name           string
@@ -859,6 +924,11 @@ func TestProviderSpecChanges(t *testing.T) {
 			t.Log("creating test provider", provider.GetName())
 			g.Expect(env.CreateAndWait(ctx, provider.DeepCopy())).To(Succeed())
 
+			defer func() {
+				// Clean up
+				g.Expect(env.Cleanup(ctx, provider, dummyConfigMap(namespace))).To(Succeed())
+			}()
+
 			g.Eventually(generateExpectedResultChecker(provider, specHash, corev1.ConditionTrue), timeout).Should(BeEquivalentTo(true))
 
 			g.Eventually(func() error {
@@ -886,9 +956,6 @@ func TestProviderSpecChanges(t *testing.T) {
 			} else {
 				g.Eventually(generateExpectedResultChecker(provider, "", corev1.ConditionFalse), timeout).Should(BeEquivalentTo(true))
 			}
-
-			// Clean up
-			g.Expect(env.Cleanup(ctx, provider, ns)).To(Succeed())
 		})
 	}
 }
diff --git a/test/e2e/air_gapped_test.go b/test/e2e/air_gapped_test.go
index 2623779..019b392 100644
--- a/test/e2e/air_gapped_test.go
+++ b/test/e2e/air_gapped_test.go
@@ -20,6 +20,7 @@ package e2e
 
 import (
 	"os"
+	"path/filepath"
 
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
@@ -29,18 +30,48 @@ import (
 	"k8s.io/utils/ptr"
 	operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"
 	. "sigs.k8s.io/cluster-api-operator/test/framework"
+	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
 	"sigs.k8s.io/cluster-api/test/framework"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/yaml"
 )
 
-var _ = Describe("Install Controlplane, Core, Bootstrap Providers in an air-gapped environment", Ordered, func() {
-	It("should successfully create config maps with Controlplane, Core, and Bootstrap Provider manifests", func() {
+var namespaces = []string{cabpkSystemNamespace, cacpkSystemNamespace, capiSystemNamespace}
+
+var _ = Describe("Install ControlPlane, Core, Bootstrap providers in an air-gapped environment", Ordered, func() {
+	var (
+		configMaps       []corev1.ConfigMap
+		bootstrapCluster client.Client
+		coreProvider     *operatorv1.CoreProvider
+	)
+
+	BeforeAll(func() {
+		bootstrapCluster = bootstrapClusterProxy.GetClient()
+		coreProvider = &operatorv1.CoreProvider{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      coreProviderName,
+				Namespace: capiSystemNamespace,
+			},
+			Spec: operatorv1.CoreProviderSpec{
+				ProviderSpec: operatorv1.ProviderSpec{
+					FetchConfig: &operatorv1.FetchConfiguration{
+						Selector: &metav1.LabelSelector{
+							MatchLabels: map[string]string{
+								operatorv1.ConfigMapNameLabel:        coreProviderName,
+								operatorv1.ConfigMapTypeLabel:        "core",
+								operatorv1.ConfigMapVersionLabelName: "v1.7.7",
+							},
+						},
+					},
+					Version: previousCAPIVersion,
+				},
+			},
+		}
+
 		// Ensure that there are no Cluster API installed
 		deleteClusterAPICRDs(bootstrapClusterProxy)
 
-		bootstrapCluster := bootstrapClusterProxy.GetClient()
-		configMaps := []corev1.ConfigMap{}
+		By("should successfully create ConfigMaps with ControlPlane, Core, and Bootstrap provider manifests")
 		configMapFiles := []string{
 			"core-cluster-api-v1.7.7.yaml",
 			"core-cluster-api-v1.8.0.yaml",
@@ -51,7 +82,7 @@ var _ = Describe("Install Controlplane, Core, Bootstrap Providers in an air-gapp
 		}
 
 		for _, fileName := range configMapFiles {
-			providerComponents, err := os.ReadFile(customManifestsFolder + fileName)
+			providerComponents, err := os.ReadFile(filepath.Join(customManifestsFolder, fileName))
 			Expect(err).ToNot(HaveOccurred(), "Failed to read the provider manifests file")
 
 			var configMap corev1.ConfigMap
@@ -62,7 +93,7 @@ var _ = Describe("Install Controlplane, Core, Bootstrap Providers in an air-gapp
 		}
 
 		By("Creating provider namespaces")
-		for _, namespaceName := range []string{cabpkSystemNamespace, cacpkSystemNamespace, capiSystemNamespace} {
+		for _, namespaceName := range namespaces {
 			namespace := &corev1.Namespace{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: namespaceName,
@@ -75,52 +106,57 @@ var _ = Describe("Install Controlplane, Core, Bootstrap Providers in an air-gapp
 		for _, cm := range configMaps {
 			Expect(bootstrapCluster.Create(ctx, &cm)).To(Succeed())
 		}
-	})
-
-	It("should successfully create a CoreProvider from a config map", func() {
-		bootstrapCluster := bootstrapClusterProxy.GetClient()
-		coreProvider := &operatorv1.CoreProvider{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      coreProviderName,
-				Namespace: capiSystemNamespace,
-			},
-			Spec: operatorv1.CoreProviderSpec{
-				ProviderSpec: operatorv1.ProviderSpec{
-					FetchConfig: &operatorv1.FetchConfiguration{
-						Selector: &metav1.LabelSelector{
-							MatchLabels: map[string]string{
-								"provider.cluster.x-k8s.io/name":    "cluster-api",
-								"provider.cluster.x-k8s.io/type":    "core",
-								"provider.cluster.x-k8s.io/version": "v1.7.7",
-							},
-						},
-					},
-					Version: previousCAPIVersion,
-				},
-			},
-		}
 
+		By("Installing Core provider")
 		Expect(bootstrapCluster.Create(ctx, coreProvider)).To(Succeed())
 
-		By("Waiting for the core provider deployment to be ready")
+		By("Waiting for Core provider to be ready")
+		WaitFor(ctx, For(coreProvider).In(bootstrapCluster).ToSatisfy(
+			HaveStatusConditionsTrue(coreProvider, operatorv1.PreflightCheckCondition, operatorv1.ProviderInstalledCondition, clusterv1.ReadyCondition),
+		), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
+
+		By("Validating that status.InstalledVersion is set")
+		Expect(ptr.Equal(coreProvider.Status.InstalledVersion, ptr.To(coreProvider.Spec.Version))).To(BeTrue())
+
+		By("Waiting for the Core provider Deployment to be ready")
 		framework.WaitForDeploymentsAvailable(ctx, framework.WaitForDeploymentsAvailableInput{
 			Getter:     bootstrapClusterProxy.GetClient(),
 			Deployment: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: coreProviderDeploymentName, Namespace: capiSystemNamespace}},
 		}, e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
-		By("Waiting for core provider to be ready")
-		WaitFor(ctx, For(coreProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&coreProvider.Status.Conditions, operatorv1.ProviderInstalledCondition),
-		), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
+		DeferCleanup(func() {
+			By("Deleting Core provider")
+			Expect(bootstrapCluster.Delete(ctx, coreProvider)).To(Succeed())
+
+			By("Waiting for the Core provider Deployment to be deleted")
+			WaitForDelete(ctx, For(&appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{
+				Name:      coreProviderDeploymentName,
+				Namespace: capiSystemNamespace,
+			}}).In(bootstrapCluster), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
-		By("Waiting for status.InstalledVersion to be set")
-		WaitFor(ctx, For(coreProvider).In(bootstrapCluster).ToSatisfy(func() bool {
-			return ptr.Equal(coreProvider.Status.InstalledVersion, ptr.To(coreProvider.Spec.Version))
-		}), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
+			By("Waiting for the Core provider object to be deleted")
+			WaitForDelete(
+				ctx, For(coreProvider).In(bootstrapCluster),
+				e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
+
+			By("Deleting ConfigMaps with ControlPlane, Core, and Bootstrap provider manifests")
+			for _, cm := range configMaps {
+				Expect(bootstrapCluster.Delete(ctx, &cm)).To(Succeed())
+			}
+
+			By("Deleting provider namespaces")
+			for _, namespaceName := range namespaces {
+				namespace := &corev1.Namespace{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: namespaceName,
+					},
+				}
+				Expect(bootstrapCluster.Delete(ctx, namespace)).To(Succeed())
+			}
+		})
 	})
 
-	It("should successfully create a BootstrapProvider from a config map", func() {
-		bootstrapCluster := bootstrapClusterProxy.GetClient()
+	It("should successfully create, upgrade (v1.7.7 -> v1.8.0) and delete a BootstrapProvider from a ConfigMap", func() {
 		bootstrapProvider := &operatorv1.BootstrapProvider{
 			ObjectMeta: metav1.ObjectMeta{
 				Name:      customProviderName,
@@ -131,9 +167,9 @@ var _ = Describe("Install Controlplane, Core, Bootstrap Providers in an air-gapp
 					FetchConfig: &operatorv1.FetchConfiguration{
 						Selector: &metav1.LabelSelector{
 							MatchLabels: map[string]string{
-								"provider.cluster.x-k8s.io/name":    "kubeadm",
-								"provider.cluster.x-k8s.io/type":    "bootstrap",
-								"provider.cluster.x-k8s.io/version": "v1.7.7",
+								operatorv1.ConfigMapNameLabel:        "kubeadm",
+								operatorv1.ConfigMapTypeLabel:        "bootstrap",
+								operatorv1.ConfigMapVersionLabelName: "v1.7.7",
 							},
 						},
 					},
@@ -144,25 +180,56 @@ var _ = Describe("Install Controlplane, Core, Bootstrap Providers in an air-gapp
 
 		Expect(bootstrapCluster.Create(ctx, bootstrapProvider)).To(Succeed())
 
-		By("Waiting for the bootstrap provider deployment to be ready")
+		By("Waiting for BootstrapProvider to be ready")
+		WaitFor(ctx, For(bootstrapProvider).In(bootstrapCluster).ToSatisfy(
+			HaveStatusConditionsTrue(bootstrapProvider, operatorv1.PreflightCheckCondition, operatorv1.ProviderInstalledCondition, clusterv1.ReadyCondition),
+		), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
+
+		By("Waiting for the BootstrapProvider Deployment to be ready")
 		framework.WaitForDeploymentsAvailable(ctx, framework.WaitForDeploymentsAvailableInput{
 			Getter:     bootstrapClusterProxy.GetClient(),
 			Deployment: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: bootstrapProviderDeploymentName, Namespace: cabpkSystemNamespace}},
 		}, e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
-		By("Waiting for bootstrap provider to be ready")
+		By("Validating that status.InstalledVersion is set")
+		Expect(ptr.Equal(bootstrapProvider.Status.InstalledVersion, ptr.To(bootstrapProvider.Spec.Version))).To(BeTrue())
+
+		By("Updating the BootstrapProvider to new Custer API version")
+		patch := client.MergeFrom(bootstrapProvider.DeepCopy())
+		bootstrapProvider.Spec.Version = nextCAPIVersion
+		bootstrapProvider.Spec.FetchConfig.Selector.MatchLabels[operatorv1.ConfigMapVersionLabelName] = nextCAPIVersion
+		Expect(bootstrapCluster.Patch(ctx, bootstrapProvider, patch)).To(Succeed())
+
+		By("Waiting for BootstrapProvider to be ready")
 		WaitFor(ctx, For(bootstrapProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&bootstrapProvider.Status.Conditions, operatorv1.ProviderInstalledCondition),
+			HaveStatusConditionsTrue(bootstrapProvider, operatorv1.PreflightCheckCondition, operatorv1.ProviderInstalledCondition, operatorv1.ProviderUpgradedCondition, clusterv1.ReadyCondition),
 		), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
-		By("Waiting for status.InstalledVersion to be set")
-		WaitFor(ctx, For(bootstrapProvider).In(bootstrapCluster).ToSatisfy(func() bool {
-			return ptr.Equal(bootstrapProvider.Status.InstalledVersion, ptr.To(bootstrapProvider.Spec.Version))
-		}), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
+		By("Waiting for the BootstrapProvider Deployment to be ready")
+		framework.WaitForDeploymentsAvailable(ctx, framework.WaitForDeploymentsAvailableInput{
+			Getter:     bootstrapClusterProxy.GetClient(),
+			Deployment: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: bootstrapProviderDeploymentName, Namespace: cabpkSystemNamespace}},
+		}, e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
+
+		By("Validating that status.InstalledVersion is set")
+		Expect(ptr.Equal(bootstrapProvider.Status.InstalledVersion, ptr.To(bootstrapProvider.Spec.Version))).To(BeTrue())
+
+		By("Deleting BootstrapProvider provider")
+		Expect(bootstrapCluster.Delete(ctx, bootstrapProvider)).To(Succeed())
+
+		By("Waiting for the BootstrapProvider Deployment to be deleted")
+		WaitForDelete(ctx, For(&appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{
+			Name:      bootstrapProviderDeploymentName,
+			Namespace: cabpkSystemNamespace,
+		}}).In(bootstrapCluster), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
+
+		By("Waiting for the BootstrapProvider object to be deleted")
+		WaitForDelete(
+			ctx, For(bootstrapProvider).In(bootstrapCluster),
+			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 	})
 
-	It("should successfully create a ControlPlaneProvider from a config map", func() {
-		bootstrapCluster := bootstrapClusterProxy.GetClient()
+	It("should successfully create, upgrade (v1.7.7 -> v1.8.0) and delete a ControlPlaneProvider from a ConfigMap", func() {
 		controlPlaneProvider := &operatorv1.ControlPlaneProvider{
 			ObjectMeta: metav1.ObjectMeta{
 				Name:      customProviderName,
@@ -173,9 +240,9 @@ var _ = Describe("Install Controlplane, Core, Bootstrap Providers in an air-gapp
 					FetchConfig: &operatorv1.FetchConfiguration{
 						Selector: &metav1.LabelSelector{
 							MatchLabels: map[string]string{
-								"provider.cluster.x-k8s.io/name":    "kubeadm",
-								"provider.cluster.x-k8s.io/type":    "controlplane",
-								"provider.cluster.x-k8s.io/version": "v1.7.7",
+								operatorv1.ConfigMapNameLabel:        "kubeadm",
+								operatorv1.ConfigMapTypeLabel:        "controlplane",
+								operatorv1.ConfigMapVersionLabelName: "v1.7.7",
 							},
 						},
 					},
@@ -186,246 +253,76 @@ var _ = Describe("Install Controlplane, Core, Bootstrap Providers in an air-gapp
 
 		Expect(bootstrapCluster.Create(ctx, controlPlaneProvider)).To(Succeed())
 
-		By("Waiting for the controlplane provider deployment to be ready")
-		framework.WaitForDeploymentsAvailable(ctx, framework.WaitForDeploymentsAvailableInput{
-			Getter:     bootstrapClusterProxy.GetClient(),
-			Deployment: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: cpProviderDeploymentName, Namespace: cacpkSystemNamespace}},
-		}, e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
-
-		By("Waiting for controlplane provider to be ready")
+		By("Waiting for ControlPlaneProvider to be ready")
 		WaitFor(ctx, For(controlPlaneProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&controlPlaneProvider.Status.Conditions, operatorv1.ProviderInstalledCondition),
+			HaveStatusConditionsTrue(controlPlaneProvider, operatorv1.PreflightCheckCondition, operatorv1.ProviderInstalledCondition, clusterv1.ReadyCondition),
 		), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
-		By("Waiting for status.InstalledVersion to be set")
-		WaitFor(ctx, For(controlPlaneProvider).In(bootstrapCluster).ToSatisfy(func() bool {
-			return ptr.Equal(controlPlaneProvider.Status.InstalledVersion, ptr.To(controlPlaneProvider.Spec.Version))
-		}), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
-	})
-
-	It("should successfully upgrade BootstrapProvider, ControlPlaneProvider, CoreProvider (v1.7.7 -> v1.8.0)", func() {
-		bootstrapCluster := bootstrapClusterProxy.GetClient()
-
-		bootstrapProvider := &operatorv1.BootstrapProvider{}
-		coreProvider := &operatorv1.CoreProvider{}
-		controlPlaneProvider := &operatorv1.ControlPlaneProvider{}
-
-		bootstrapKey := client.ObjectKey{Namespace: cabpkSystemNamespace, Name: customProviderName}
-		Expect(bootstrapCluster.Get(ctx, bootstrapKey, bootstrapProvider)).To(Succeed())
-
-		coreKey := client.ObjectKey{Namespace: capiSystemNamespace, Name: coreProviderName}
-		Expect(bootstrapCluster.Get(ctx, coreKey, coreProvider)).To(Succeed())
-
-		cpKey := client.ObjectKey{Namespace: cacpkSystemNamespace, Name: customProviderName}
-		Expect(bootstrapCluster.Get(ctx, cpKey, controlPlaneProvider)).To(Succeed())
-
-		bootstrapProvider.Spec.Version = nextCAPIVersion
-		bootstrapProvider.Spec.FetchConfig.Selector.MatchLabels["provider.cluster.x-k8s.io/version"] = nextCAPIVersion
-		coreProvider.Spec.Version = nextCAPIVersion
-		coreProvider.Spec.FetchConfig.Selector.MatchLabels["provider.cluster.x-k8s.io/version"] = nextCAPIVersion
-		controlPlaneProvider.Spec.Version = nextCAPIVersion
-		controlPlaneProvider.Spec.FetchConfig.Selector.MatchLabels["provider.cluster.x-k8s.io/version"] = nextCAPIVersion
-
-		Expect(bootstrapCluster.Update(ctx, bootstrapProvider)).To(Succeed())
-		Expect(bootstrapCluster.Update(ctx, coreProvider)).To(Succeed())
-		Expect(bootstrapCluster.Update(ctx, controlPlaneProvider)).To(Succeed())
-
-		By("Waiting for the bootstrap provider deployment to be ready")
+		By("Waiting for the ControlPlaneProvider Deployment to be ready")
 		framework.WaitForDeploymentsAvailable(ctx, framework.WaitForDeploymentsAvailableInput{
 			Getter:     bootstrapClusterProxy.GetClient(),
-			Deployment: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: bootstrapProviderDeploymentName, Namespace: cabpkSystemNamespace}},
+			Deployment: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: cpProviderDeploymentName, Namespace: cacpkSystemNamespace}},
 		}, e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
-		By("Waiting for bootstrap provider to be ready")
-		WaitFor(ctx, For(bootstrapProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&bootstrapProvider.Status.Conditions, operatorv1.ProviderInstalledCondition),
-		), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
-
-		By("Waiting for bootstrap provider status.InstalledVersion to be set")
-		WaitFor(ctx, For(bootstrapProvider).In(bootstrapCluster).ToSatisfy(func() bool {
-			return ptr.Equal(bootstrapProvider.Status.InstalledVersion, ptr.To(bootstrapProvider.Spec.Version))
-		}), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
+		By("Validating that status.InstalledVersion is set")
+		Expect(ptr.Equal(controlPlaneProvider.Status.InstalledVersion, ptr.To(controlPlaneProvider.Spec.Version))).To(BeTrue())
 
-		By("Waiting for the core provider deployment to be ready")
-		framework.WaitForDeploymentsAvailable(ctx, framework.WaitForDeploymentsAvailableInput{
-			Getter:     bootstrapClusterProxy.GetClient(),
-			Deployment: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: coreProviderDeploymentName, Namespace: capiSystemNamespace}},
-		}, e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
+		By("Updating the ControlPlaneProvider to new Custer API version")
+		patch := client.MergeFrom(controlPlaneProvider.DeepCopy())
+		controlPlaneProvider.Spec.Version = nextCAPIVersion
+		controlPlaneProvider.Spec.FetchConfig.Selector.MatchLabels[operatorv1.ConfigMapVersionLabelName] = nextCAPIVersion
+		Expect(bootstrapCluster.Patch(ctx, controlPlaneProvider, patch)).To(Succeed())
 
-		By("Waiting for core provider to be ready")
-		WaitFor(ctx, For(coreProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&coreProvider.Status.Conditions, operatorv1.ProviderInstalledCondition),
+		By("Waiting for ControlPlaneProvider to be ready")
+		WaitFor(ctx, For(controlPlaneProvider).In(bootstrapCluster).ToSatisfy(
+			HaveStatusConditionsTrue(controlPlaneProvider, operatorv1.PreflightCheckCondition, operatorv1.ProviderInstalledCondition, operatorv1.ProviderUpgradedCondition, clusterv1.ReadyCondition),
 		), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
-		By("Waiting for status.InstalledVersion to be set")
-		WaitFor(ctx, For(coreProvider).In(bootstrapCluster).ToSatisfy(func() bool {
-			return ptr.Equal(coreProvider.Status.InstalledVersion, ptr.To(coreProvider.Spec.Version))
-		}), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
-
-		By("Waiting for the controlplane provider deployment to be ready")
+		By("Waiting for the ControlPlaneProvider Deployment to be ready")
 		framework.WaitForDeploymentsAvailable(ctx, framework.WaitForDeploymentsAvailableInput{
 			Getter:     bootstrapClusterProxy.GetClient(),
 			Deployment: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: cpProviderDeploymentName, Namespace: cacpkSystemNamespace}},
 		}, e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
-		By("Waiting for controlplane provider to be ready")
-		WaitFor(ctx, For(controlPlaneProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&coreProvider.Status.Conditions, operatorv1.ProviderInstalledCondition),
-		), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
-
-		By("Waiting for status.InstalledVersion to be set")
-		WaitFor(ctx, For(controlPlaneProvider).In(bootstrapCluster).ToSatisfy(func() bool {
-			return ptr.Equal(controlPlaneProvider.Status.InstalledVersion, ptr.To(controlPlaneProvider.Spec.Version))
-		}), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
-	})
-
-	It("should successfully delete a BootstrapProvider", func() {
-		bootstrapCluster := bootstrapClusterProxy.GetClient()
-		bootstrapProvider := &operatorv1.BootstrapProvider{ObjectMeta: metav1.ObjectMeta{
-			Name:      customProviderName,
-			Namespace: cabpkSystemNamespace,
-		}}
-
-		Expect(bootstrapCluster.Delete(ctx, bootstrapProvider)).To(Succeed())
-
-		By("Waiting for the bootstrap provider deployment to be deleted")
-		WaitForDelete(ctx, For(&appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{
-			Name:      bootstrapProviderDeploymentName,
-			Namespace: cabpkSystemNamespace,
-		}}).In(bootstrapCluster), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
-
-		By("Waiting for the bootstrap provider object to be deleted")
-		WaitForDelete(
-			ctx, For(bootstrapProvider).In(bootstrapCluster),
-			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
-	})
-
-	It("should successfully delete config maps with Bootstrap Provider manifests", func() {
-		bootstrapCluster := bootstrapClusterProxy.GetClient()
-		configMaps := []corev1.ConfigMap{}
-
-		for _, fileName := range []string{"bootstrap-kubeadm-v1.7.7.yaml", "bootstrap-kubeadm-v1.8.0.yaml"} {
-			bootstrapProviderComponents, err := os.ReadFile(customManifestsFolder + fileName)
-			Expect(err).ToNot(HaveOccurred(), "Failed to read the bootstrap provider manifests file")
-
-			var configMap corev1.ConfigMap
-
-			Expect(yaml.Unmarshal(bootstrapProviderComponents, &configMap)).To(Succeed())
-
-			configMaps = append(configMaps, configMap)
-		}
-
-		By("Deleting config maps with bootstrap provider manifests")
-		for _, cm := range configMaps {
-			Expect(bootstrapCluster.Delete(ctx, &cm)).To(Succeed())
-		}
-
-		By("Deleting capkb-system namespace")
-		namespace := &corev1.Namespace{
-			ObjectMeta: metav1.ObjectMeta{
-				Name: cabpkSystemNamespace,
-			},
-		}
-		Expect(bootstrapCluster.Delete(ctx, namespace)).To(Succeed())
-	})
-
-	It("should successfully delete a CoreProvider", func() {
-		bootstrapCluster := bootstrapClusterProxy.GetClient()
-		coreProvider := &operatorv1.CoreProvider{ObjectMeta: metav1.ObjectMeta{
-			Name:      coreProviderName,
-			Namespace: capiSystemNamespace,
-		}}
+		By("Validating that status.InstalledVersion is set")
+		Expect(ptr.Equal(controlPlaneProvider.Status.InstalledVersion, ptr.To(controlPlaneProvider.Spec.Version))).To(BeTrue())
 
-		Expect(bootstrapCluster.Delete(ctx, coreProvider)).To(Succeed())
+		By("Deleting ControlPlaneProvider provider")
+		Expect(bootstrapCluster.Delete(ctx, controlPlaneProvider)).To(Succeed())
 
-		By("Waiting for the core provider deployment to be deleted")
-		WaitForDelete(ctx, For(&appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{
-			Name:      coreProviderDeploymentName,
-			Namespace: capiSystemNamespace,
-		}}).In(bootstrapCluster), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
-
-		By("Waiting for the core provider object to be deleted")
-		WaitForDelete(
-			ctx, For(coreProvider).In(bootstrapCluster),
-			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
-	})
-
-	It("should successfully delete config maps with Core Provider manifests", func() {
-		bootstrapCluster := bootstrapClusterProxy.GetClient()
-		configMaps := []corev1.ConfigMap{}
-
-		for _, fileName := range []string{"core-cluster-api-v1.7.7.yaml", "core-cluster-api-v1.8.0.yaml"} {
-			coreProviderComponents, err := os.ReadFile(customManifestsFolder + fileName)
-			Expect(err).ToNot(HaveOccurred(), "Failed to read the core provider manifests file")
-
-			var configMap corev1.ConfigMap
-
-			Expect(yaml.Unmarshal(coreProviderComponents, &configMap)).To(Succeed())
-
-			configMaps = append(configMaps, configMap)
-		}
-
-		By("Deleting config maps with core provider manifests")
-		for _, cm := range configMaps {
-			Expect(bootstrapCluster.Delete(ctx, &cm)).To(Succeed())
-		}
-
-		By("Deleting capi-system namespace")
-		namespace := &corev1.Namespace{
-			ObjectMeta: metav1.ObjectMeta{
-				Name: capiSystemNamespace,
-			},
-		}
-		Expect(bootstrapCluster.Delete(ctx, namespace)).To(Succeed())
-	})
-
-	It("should successfully delete a ControlPlaneProvider", func() {
-		bootstrapCluster := bootstrapClusterProxy.GetClient()
-		ControlPlaneProvider := &operatorv1.ControlPlaneProvider{ObjectMeta: metav1.ObjectMeta{
-			Name:      customProviderName,
-			Namespace: cacpkSystemNamespace,
-		}}
-
-		Expect(bootstrapCluster.Delete(ctx, ControlPlaneProvider)).To(Succeed())
-
-		By("Waiting for the controlplane provider deployment to be deleted")
+		By("Waiting for the ControlPlaneProvider Deployment to be deleted")
 		WaitForDelete(ctx, For(&appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{
 			Name:      cpProviderDeploymentName,
 			Namespace: cacpkSystemNamespace,
 		}}).In(bootstrapCluster), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
-		By("Waiting for the controlplane provider object to be deleted")
+		By("Waiting for the ControlPlaneProvider object to be deleted")
 		WaitForDelete(
-			ctx, For(ControlPlaneProvider).In(bootstrapCluster),
+			ctx, For(controlPlaneProvider).In(bootstrapCluster),
 			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 	})
 
-	It("should successfully delete config maps with ControlPlane Provider manifests", func() {
-		bootstrapCluster := bootstrapClusterProxy.GetClient()
-		configMaps := []corev1.ConfigMap{}
-
-		for _, fileName := range []string{"controlplane-kubeadm-v1.7.7.yaml", "controlplane-kubeadm-v1.8.0.yaml"} {
-			controlPlaneProviderComponents, err := os.ReadFile(customManifestsFolder + fileName)
-			Expect(err).ToNot(HaveOccurred(), "Failed to read the controlplane provider manifests file")
+	It("should successfully upgrade CoreProvider (v1.7.7 -> v1.8.0)", func() {
+		Expect(bootstrapCluster.Get(ctx, client.ObjectKeyFromObject(coreProvider), coreProvider)).To(Succeed())
 
-			var configMap corev1.ConfigMap
-
-			Expect(yaml.Unmarshal(controlPlaneProviderComponents, &configMap)).To(Succeed())
+		By("Updating the CoreProvider to new Custer API version")
+		patch := client.MergeFrom(coreProvider.DeepCopy())
+		coreProvider.Spec.Version = nextCAPIVersion
+		coreProvider.Spec.FetchConfig.Selector.MatchLabels[operatorv1.ConfigMapVersionLabelName] = nextCAPIVersion
+		Expect(bootstrapCluster.Patch(ctx, coreProvider, patch)).To(Succeed())
 
-			configMaps = append(configMaps, configMap)
-		}
+		By("Waiting for CoreProvider to be ready")
+		WaitFor(ctx, For(coreProvider).In(bootstrapCluster).ToSatisfy(
+			HaveStatusConditionsTrue(coreProvider, operatorv1.PreflightCheckCondition, operatorv1.ProviderInstalledCondition, operatorv1.ProviderUpgradedCondition, clusterv1.ReadyCondition),
+		), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
-		By("Deleting config maps with controlplane provider manifests")
-		for _, cm := range configMaps {
-			Expect(bootstrapCluster.Delete(ctx, &cm)).To(Succeed())
-		}
+		By("Waiting for the CoreProvider Deployment to be ready")
+		framework.WaitForDeploymentsAvailable(ctx, framework.WaitForDeploymentsAvailableInput{
+			Getter:     bootstrapClusterProxy.GetClient(),
+			Deployment: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: coreProviderDeploymentName, Namespace: capiSystemNamespace}},
+		}, e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
-		By("Deleting capkcp-system namespace")
-		namespace := &corev1.Namespace{
-			ObjectMeta: metav1.ObjectMeta{
-				Name: cacpkSystemNamespace,
-			},
-		}
-		Expect(bootstrapCluster.Delete(ctx, namespace)).To(Succeed())
+		By("Validating that status.InstalledVersion is set")
+		Expect(ptr.Equal(coreProvider.Status.InstalledVersion, ptr.To(coreProvider.Spec.Version))).To(BeTrue())
 	})
 })
diff --git a/test/e2e/compressed_manifests_test.go b/test/e2e/compressed_manifests_test.go
index adffa1e..752f607 100644
--- a/test/e2e/compressed_manifests_test.go
+++ b/test/e2e/compressed_manifests_test.go
@@ -64,7 +64,7 @@ var _ = Describe("Create and delete a provider with manifests that don't fit the
 
 		By("Waiting for core provider to be ready")
 		WaitFor(ctx, For(coreProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&coreProvider.Status.Conditions, operatorv1.ProviderInstalledCondition),
+			HaveStatusConditionsTrue(coreProvider, operatorv1.ProviderInstalledCondition),
 		), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
 		By("Waiting for status.InstalledVersion to be set")
@@ -91,7 +91,7 @@ var _ = Describe("Create and delete a provider with manifests that don't fit the
 
 		By("Waiting for the infrastructure provider to be ready")
 		WaitFor(ctx, For(infraProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&infraProvider.Status.Conditions, operatorv1.ProviderInstalledCondition),
+			HaveStatusConditionsTrue(infraProvider, operatorv1.ProviderInstalledCondition),
 		), e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
 		By("Waiting for status.InstalledVersion to be set")
@@ -165,7 +165,7 @@ var _ = Describe("Create and delete a provider with manifests that don't fit the
 
 		By("Waiting for the infrastructure provider to be ready")
 		WaitFor(ctx, For(infraProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&infraProvider.Status.Conditions, operatorv1.ProviderInstalledCondition)),
+			HaveStatusConditionsTrue(infraProvider, operatorv1.ProviderInstalledCondition)),
 			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
 		By("Waiting for status.InstalledVersion to be set")
diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go
index 9fa2b31..e08196b 100644
--- a/test/e2e/e2e_suite_test.go
+++ b/test/e2e/e2e_suite_test.go
@@ -31,9 +31,9 @@ import (
 	appsv1 "k8s.io/api/apps/v1"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
 	configclient "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
 
-	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/klog/v2"
 	operatorv1alpha1 "sigs.k8s.io/cluster-api-operator/api/v1alpha1"
@@ -347,22 +347,13 @@ func ensureCertManager(clusterProxy framework.ClusterProxy, config *clusterctl.E
 }
 
 func deleteClusterAPICRDs(clusterProxy framework.ClusterProxy) {
-	// To get all Cluster API CRDs we need filter them by labels:
-	//   cluster.x-k8s.io/provider: cluster-api
-	//   clusterctl.cluster.x-k8s.io: ""
-	crds := &apiextensionsv1.CustomResourceDefinitionList{}
-	Expect(clusterProxy.GetClient().List(ctx, crds, &client.ListOptions{
-		LabelSelector: labels.SelectorFromSet(
-			map[string]string{
-				clusterv1.ProviderNameLabel:   configclient.ClusterAPIProviderName,
-				"clusterctl.cluster.x-k8s.io": "",
-			},
-		),
+	// To remove all Cluster API CRDs we need filter them by labels:
+	//  cluster.x-k8s.io/provider: cluster-api
+	//  clusterctl.cluster.x-k8s.io: ""
+	Expect(clusterProxy.GetClient().DeleteAllOf(ctx, &apiextensionsv1.CustomResourceDefinition{}, client.MatchingLabels{
+		clusterv1.ProviderNameLabel:  configclient.ClusterAPIProviderName,
+		clusterctlv1.ClusterctlLabel: "",
 	})).To(Succeed())
-
-	for _, crd := range crds.Items {
-		Expect(clusterProxy.GetClient().Delete(ctx, &crd)).To(Succeed())
-	}
 }
 
 func initHelmChart() {
diff --git a/test/e2e/helm_test.go b/test/e2e/helm_test.go
index cf07b55..0922436 100644
--- a/test/e2e/helm_test.go
+++ b/test/e2e/helm_test.go
@@ -80,7 +80,7 @@ var _ = Describe("Create a proper set of manifests when using helm charts", func
 
 		By("Waiting for core provider to be ready")
 		WaitFor(ctx, For(coreProvider).In(clusterProxy).ToSatisfy(
-			HaveStatusCondition(&coreProvider.Status.Conditions, operatorv1.ProviderInstalledCondition),
+			HaveStatusConditionsTrue(coreProvider, operatorv1.ProviderInstalledCondition),
 		), e2eConfig.GetIntervals(helmClusterProxy.GetName(), "wait-controllers")...)
 
 		By("Waiting for status.InstalledVersion to be set")
@@ -107,7 +107,7 @@ var _ = Describe("Create a proper set of manifests when using helm charts", func
 
 		By("Waiting for bootstrap provider to be ready")
 		WaitFor(ctx, For(bootstrapProvider).In(clusterProxy).ToSatisfy(
-			HaveStatusCondition(&bootstrapProvider.Status.Conditions, operatorv1.ProviderInstalledCondition)),
+			HaveStatusConditionsTrue(bootstrapProvider, operatorv1.ProviderInstalledCondition)),
 			e2eConfig.GetIntervals(helmClusterProxy.GetName(), "wait-controllers")...)
 
 		By("Waiting for status.InstalledVersion to be set")
diff --git a/test/e2e/helpers_test.go b/test/e2e/helpers_test.go
index 93cb38e..8c7d488 100644
--- a/test/e2e/helpers_test.go
+++ b/test/e2e/helpers_test.go
@@ -54,6 +54,6 @@ const (
 	ipamProviderURL            = "https://github.com/kubernetes-sigs/cluster-api-ipam-provider-in-cluster/releases/latest/ipam-components.yaml"
 	ipamProviderDeploymentName = "capi-ipam-in-cluster-controller-manager"
 
-	customManifestsFolder = "resources/"
+	customManifestsFolder = "resources"
 	customProviderName    = "kubeadm-custom"
 )
diff --git a/test/e2e/minimal_configuration_test.go b/test/e2e/minimal_configuration_test.go
index 1afb3c1..4e4ce67 100644
--- a/test/e2e/minimal_configuration_test.go
+++ b/test/e2e/minimal_configuration_test.go
@@ -109,7 +109,7 @@ metadata:
 
 		By("Waiting for core provider to be ready")
 		WaitFor(ctx, For(coreProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&coreProvider.Status.Conditions, operatorv1.ProviderInstalledCondition)),
+			HaveStatusConditionsTrue(coreProvider, operatorv1.ProviderInstalledCondition)),
 			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
 		By("Waiting for status.InstalledVersion to be set")
@@ -151,7 +151,7 @@ metadata:
 
 		By("Waiting for bootstrap provider to be ready")
 		WaitFor(ctx, For(bootstrapProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&bootstrapProvider.Status.Conditions, operatorv1.ProviderInstalledCondition)),
+			HaveStatusConditionsTrue(bootstrapProvider, operatorv1.ProviderInstalledCondition)),
 			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
 		By("Waiting for status.InstalledVersion to be set")
@@ -186,7 +186,7 @@ metadata:
 
 		By("Waiting for the control plane provider to be ready")
 		WaitFor(ctx, For(cpProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&cpProvider.Status.Conditions, operatorv1.ProviderInstalledCondition)),
+			HaveStatusConditionsTrue(cpProvider, operatorv1.ProviderInstalledCondition)),
 			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
 		By("Waiting for status.InstalledVersion to be set")
@@ -221,7 +221,7 @@ metadata:
 
 		By("Waiting for the infrastructure provider to be ready")
 		WaitFor(ctx, For(infraProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&infraProvider.Status.Conditions, operatorv1.ProviderInstalledCondition)),
+			HaveStatusConditionsTrue(infraProvider, operatorv1.ProviderInstalledCondition)),
 			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
 		By("Waiting for status.InstalledVersion to be set")
@@ -263,7 +263,7 @@ metadata:
 
 		By("Waiting for the addon provider to be ready")
 		WaitFor(ctx, For(addonProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&addonProvider.Status.Conditions, operatorv1.ProviderInstalledCondition)),
+			HaveStatusConditionsTrue(addonProvider, operatorv1.ProviderInstalledCondition)),
 			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
 		By("Waiting for status.InstalledVersion to be set")
@@ -307,7 +307,7 @@ metadata:
 
 		By("Waiting for the ipam provider to be ready")
 		WaitFor(ctx, For(ipamProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&ipamProvider.Status.Conditions, operatorv1.ProviderInstalledCondition)),
+			HaveStatusConditionsTrue(ipamProvider, operatorv1.ProviderInstalledCondition)),
 			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
 		By("Waiting for status.InstalledVersion to be set")
@@ -342,7 +342,7 @@ metadata:
 
 		By("Waiting for core provider to be ready")
 		WaitFor(ctx, For(coreProvider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&coreProvider.Status.Conditions, operatorv1.ProviderInstalledCondition)),
+			HaveStatusConditionsTrue(coreProvider, operatorv1.ProviderInstalledCondition)),
 			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
 		By("Waiting for the core provide status.InstalledVersion to be set")
@@ -423,7 +423,7 @@ metadata:
 
 		By("Waiting for the custom provider to be ready")
 		WaitFor(ctx, For(provider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&provider.Status.Conditions, operatorv1.ProviderInstalledCondition)),
+			HaveStatusConditionsTrue(provider, operatorv1.ProviderInstalledCondition)),
 			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
 		By("Waiting for status.InstalledVersion to be set")
@@ -510,7 +510,7 @@ metadata:
 
 		By("Waiting for the docker provider to be ready")
 		WaitFor(ctx, For(provider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&provider.Status.Conditions, operatorv1.ProviderInstalledCondition)),
+			HaveStatusConditionsTrue(provider, operatorv1.ProviderInstalledCondition)),
 			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
 		By("Waiting for status.InstalledVersion to be set")
@@ -597,7 +597,7 @@ metadata:
 
 		By("Waiting for the docker provider to be ready")
 		WaitFor(ctx, For(provider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&provider.Status.Conditions, operatorv1.ProviderInstalledCondition)),
+			HaveStatusConditionsTrue(provider, operatorv1.ProviderInstalledCondition)),
 			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
 		By("Waiting for status.InstalledVersion to be set")
@@ -616,7 +616,7 @@ metadata:
 
 		By("Waiting for the docker provider to be ready")
 		WaitFor(ctx, For(provider).In(bootstrapCluster).ToSatisfy(
-			HaveStatusCondition(&provider.Status.Conditions, operatorv1.ProviderInstalledCondition)),
+			HaveStatusConditionsTrue(provider, operatorv1.ProviderInstalledCondition)),
 			e2eConfig.GetIntervals(bootstrapClusterProxy.GetName(), "wait-controllers")...)
 
 		Expect(bootstrapCluster.Delete(ctx, provider)).To(Succeed())
diff --git a/test/framework/conditions.go b/test/framework/conditions.go
index 45aeede..312b8f8 100644
--- a/test/framework/conditions.go
+++ b/test/framework/conditions.go
@@ -22,20 +22,26 @@ import (
 	"fmt"
 
 	. "github.com/onsi/ginkgo/v2" //nolint:stylecheck
-	corev1 "k8s.io/api/core/v1"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
+	capiconditions "sigs.k8s.io/cluster-api/util/conditions"
 )
 
-func HaveStatusCondition(conditions *clusterv1.Conditions, condition clusterv1.ConditionType) Condition {
+func HaveStatusConditionsTrue(getter capiconditions.Getter, conditions ...clusterv1.ConditionType) Condition {
 	return func() bool {
-		By(fmt.Sprintf("Checking if %s condition is set...", condition))
+		if len(conditions) == 0 {
+			By("Empty condition list provided. Can't be validated...")
 
-		for _, c := range *conditions {
-			if c.Type == condition && c.Status == corev1.ConditionTrue {
-				return true
+			return false
+		}
+
+		for _, condition := range conditions {
+			By(fmt.Sprintf("Checking if %s condition is set...", condition))
+
+			if !capiconditions.IsTrue(getter, condition) {
+				return false
 			}
 		}
 
-		return false
+		return true
 	}
 }