Repository: syself/cluster-api-provider-hetzner
Pull request #1106 (2024-01-03 10:46:20)

:seedling: support externally managed control planes
**What this PR does / why we need it**:

This PR enables provisioning of Hetzner-based worker nodes using an externally managed Control Plane.

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes https://github.com/syself/cluster-api-provider-hetzner/issues/845

**Special notes for your reviewer**:

This PR is related to the PR https://github.com/syself/cluster-api-provider-hetzner/pull/848. With this code I was able to create a HetznerCluster with an external control plane and hetzner worker nodes.

**TODOs**:

- [ ] squash commits
- [ ] include documentation
- [ ] add unit tests



janiskemper: Thanks for the PR! Looking forward to this!
rpahli: @janiskemper I updated the PR, can you pls have a look.
rpahli: ok I added some tests :-)
janiskemper: Cool! Can you run `make verify` and `make test-unit` to see whether everything works? 

Then we can squash and rebase.

After that, this will be good to merge!
rpahli: ok I run `make verify` and `make test-unit` and all looks good. 

One question from my side some python scripts use `#!/usr/bin/env python`. I changed it to `#!/usr/bin/env python3` locally. Is there a plan to migrate als scripts to `python3`?
janiskemper: @kranurag7 what about python3?

Patch:
diff --git a/api/v1beta1/conditions_const.go b/api/v1beta1/conditions_const.go
index 43a518c3..b9ba708b 100644
--- a/api/v1beta1/conditions_const.go
+++ b/api/v1beta1/conditions_const.go
@@ -123,6 +123,8 @@ const (
 	TargetClusterCreateFailedReason = "TargetClusterCreateFailed"
 	// TargetClusterControlPlaneNotReadyReason indicates that the target cluster's control plane is not ready yet.
 	TargetClusterControlPlaneNotReadyReason = "TargetClusterControlPlaneNotReady"
+	// ControlPlaneEndpointSetCondition indicates that the control plane is set.
+	ControlPlaneEndpointSetCondition = "ControlPlaneEndpointSet"
 )
 
 const (
@@ -130,6 +132,8 @@ const (
 	TargetClusterSecretReadyCondition clusterv1.ConditionType = "TargetClusterSecretReady"
 	// TargetSecretSyncFailedReason indicates that the target secret could not be synced.
 	TargetSecretSyncFailedReason = "TargetSecretSyncFailed"
+	// ControlPlaneEndpointNotSetReason indicates that the control plane endpoint is not set.
+	ControlPlaneEndpointNotSetReason = "ControlPlaneEndpointNotSet"
 )
 
 const (
diff --git a/api/v1beta1/hetznercluster_types.go b/api/v1beta1/hetznercluster_types.go
index a7a63d25..c3ddf8f4 100644
--- a/api/v1beta1/hetznercluster_types.go
+++ b/api/v1beta1/hetznercluster_types.go
@@ -26,6 +26,9 @@ const (
 	// resources associated with HetznerCluster before removing it from the
 	// apiserver.
 	ClusterFinalizer = "hetznercluster.infrastructure.cluster.x-k8s.io"
+	// AllowEmptyControlPlaneAddressAnnotation allows HetznerCluster Webhook
+	// to skip some validation steps for external managed controle planes.
+	AllowEmptyControlPlaneAddressAnnotation = "capi.syself.com/allow-empty-control-plane-address"
 )
 
 // HetznerClusterSpec defines the desired state of HetznerCluster.
@@ -43,7 +46,7 @@ type HetznerClusterSpec struct {
 	SSHKeys HetznerSSHKeys `json:"sshKeys"`
 	// ControlPlaneEndpoint represents the endpoint used to communicate with the control plane.
 	// +optional
-	ControlPlaneEndpoint *clusterv1.APIEndpoint `json:"controlPlaneEndpoint"`
+	ControlPlaneEndpoint *clusterv1.APIEndpoint `json:"controlPlaneEndpoint,omitempty"`
 
 	// ControlPlaneLoadBalancer is optional configuration for customizing control plane behavior. Naming convention is from upstream cluster-api project.
 	ControlPlaneLoadBalancer LoadBalancerSpec `json:"controlPlaneLoadBalancer,omitempty"`
diff --git a/api/v1beta1/hetznercluster_webhook.go b/api/v1beta1/hetznercluster_webhook.go
index 0a40f326..0d4c5a18 100644
--- a/api/v1beta1/hetznercluster_webhook.go
+++ b/api/v1beta1/hetznercluster_webhook.go
@@ -73,7 +73,12 @@ func (r *HetznerCluster) ValidateCreate() (admission.Warnings, error) {
 	hetznerclusterlog.V(1).Info("validate create", "name", r.Name)
 	var allErrs field.ErrorList
 
-	if len(r.Spec.ControlPlaneRegions) == 0 {
+	allowEmptyControlPlaneAddress := false
+	if r.Annotations != nil {
+		allowEmptyControlPlaneAddress = r.Annotations[AllowEmptyControlPlaneAddressAnnotation] == "true"
+	}
+
+	if !allowEmptyControlPlaneAddress && len(r.Spec.ControlPlaneRegions) == 0 {
 		allErrs = append(allErrs, field.Invalid(
 			field.NewPath("spec", "controlPlaneRegions"),
 			r.Spec.ControlPlaneRegions,
@@ -108,8 +113,9 @@ func (r *HetznerCluster) ValidateCreate() (admission.Warnings, error) {
 		}
 	}
 
-	// Check whether controlPlaneEndpoint is specified if controlPlaneLoadBalancer is not enabled
-	if !r.Spec.ControlPlaneLoadBalancer.Enabled {
+	// Check whether controlPlaneEndpoint is specified if allow empty is not set or false
+
+	if !allowEmptyControlPlaneAddress && !r.Spec.ControlPlaneLoadBalancer.Enabled {
 		if r.Spec.ControlPlaneEndpoint == nil ||
 			r.Spec.ControlPlaneEndpoint.Host == "" ||
 			r.Spec.ControlPlaneEndpoint.Port == 0 {
diff --git a/controllers/hetznercluster_controller.go b/controllers/hetznercluster_controller.go
index da72da5e..d4ef150c 100644
--- a/controllers/hetznercluster_controller.go
+++ b/controllers/hetznercluster_controller.go
@@ -203,33 +203,7 @@ func (r *HetznerClusterReconciler) reconcileNormal(ctx context.Context, clusterS
 		return reconcile.Result{}, fmt.Errorf("failed to reconcile placement groups for HetznerCluster %s/%s: %w", hetznerCluster.Namespace, hetznerCluster.Name, err)
 	}
 
-	if hetznerCluster.Spec.ControlPlaneLoadBalancer.Enabled {
-		if hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4 != "<nil>" {
-			defaultHost := hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4
-			defaultPort := int32(hetznerCluster.Spec.ControlPlaneLoadBalancer.Port)
-
-			if hetznerCluster.Spec.ControlPlaneEndpoint == nil {
-				hetznerCluster.Spec.ControlPlaneEndpoint = &clusterv1.APIEndpoint{
-					Host: defaultHost,
-					Port: defaultPort,
-				}
-			} else {
-				if hetznerCluster.Spec.ControlPlaneEndpoint.Host == "" {
-					hetznerCluster.Spec.ControlPlaneEndpoint.Host = defaultHost
-				}
-				if hetznerCluster.Spec.ControlPlaneEndpoint.Port == 0 {
-					hetznerCluster.Spec.ControlPlaneEndpoint.Port = defaultPort
-				}
-			}
-
-			hetznerCluster.Status.Ready = true
-		}
-	} else if hetznerCluster.Spec.ControlPlaneEndpoint != nil {
-		hetznerCluster.Status.Ready = true
-	}
-
-	// update control plane endpoint
-	hetznerCluster.Status.Ready = setControlPlaneEndpoint(hetznerCluster)
+	processControlPlaneEndpoint(hetznerCluster)
 
 	// delete deprecated conditions of old clusters
 	conditions.Delete(clusterScope.HetznerCluster, infrav1.DeprecatedHetznerClusterTargetClusterReadyCondition)
@@ -267,8 +241,7 @@ func (r *HetznerClusterReconciler) reconcileNormal(ctx context.Context, clusterS
 	return reconcile.Result{}, nil
 }
 
-// setControlPlaneEndpoint updates hetznerCluster.Spec.ControlPlaneEndpoint and returns whether the hetznerCluster is ready or not.
-func setControlPlaneEndpoint(hetznerCluster *infrav1.HetznerCluster) bool {
+func processControlPlaneEndpoint(hetznerCluster *infrav1.HetznerCluster) {
 	if hetznerCluster.Spec.ControlPlaneLoadBalancer.Enabled {
 		if hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4 != "<nil>" {
 			defaultHost := hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4
@@ -287,12 +260,31 @@ func setControlPlaneEndpoint(hetznerCluster *infrav1.HetznerCluster) bool {
 					hetznerCluster.Spec.ControlPlaneEndpoint.Port = defaultPort
 				}
 			}
-			return true
+			conditions.MarkTrue(hetznerCluster, infrav1.ControlPlaneEndpointSetCondition)
+			hetznerCluster.Status.Ready = true
+		} else {
+			msg := "enabled LoadBalancer but load balancer not ready yet"
+			conditions.MarkFalse(hetznerCluster,
+				infrav1.ControlPlaneEndpointSetCondition,
+				infrav1.ControlPlaneEndpointNotSetReason,
+				clusterv1.ConditionSeverityWarning,
+				msg)
+			hetznerCluster.Status.Ready = false
+		}
+	} else {
+		if hetznerCluster.Spec.ControlPlaneEndpoint != nil && hetznerCluster.Spec.ControlPlaneEndpoint.Host != "" && hetznerCluster.Spec.ControlPlaneEndpoint.Port != 0 {
+			conditions.MarkTrue(hetznerCluster, infrav1.ControlPlaneEndpointSetCondition)
+			hetznerCluster.Status.Ready = true
+		} else {
+			msg := "disabled LoadBalancer and not yet provided ControlPlane endpoint"
+			conditions.MarkFalse(hetznerCluster,
+				infrav1.ControlPlaneEndpointSetCondition,
+				infrav1.ControlPlaneEndpointNotSetReason,
+				clusterv1.ConditionSeverityWarning,
+				msg)
+			hetznerCluster.Status.Ready = false
 		}
-	} else if hetznerCluster.Spec.ControlPlaneEndpoint != nil {
-		return true
 	}
-	return false
 }
 
 func (r *HetznerClusterReconciler) reconcileDelete(ctx context.Context, clusterScope *scope.ClusterScope) (reconcile.Result, error) {
diff --git a/docs/README.md b/docs/README.md
index 7c74b326..c1448c2d 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,12 +1,14 @@
 # Documentation Index
 
+This is the official documentation of Cluster API Provider Hetzner. Before starting with this documentation, you should have a basic understanding of Cluster API. Cluster API is a Kubernetes sub-project focused on providing a declarative API to manage Kubernetes clusters. Refer to the Cluster API quick start guide from their [official documentation](https://cluster-api.sigs.k8s.io/user/quick-start.html).
+
 ## Quick start
 
-- [Getting started](topics/quickstart.md )
-- [Cluster API quick start](https://cluster-api.sigs.k8s.io/user/quick-start.html)
+- [Preparation](topics/preparation.md)
+- [Getting started](topics/quickstart.md)
 
 ## Topics
-- [Preparation](topics/preparation.md)
+
 - [Managing SSH Keys](topics/managing-ssh-keys.md)
 - [Node Images](topics/node-image.md)
 - [Production Environment](topics/production-environment.md)
diff --git a/images/caph/Dockerfile b/images/caph/Dockerfile
index 9f05f2ab..78607e79 100644
--- a/images/caph/Dockerfile
+++ b/images/caph/Dockerfile
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # Build the manager binary
-FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.21.6-bullseye@sha256:fa52abd182d334cfcdffdcc934e21fcfbc71c3cde568e606193ae7db045b1b8d as build
+FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.21.6-bullseye@sha256:a8712f27d9ac742e7bded8f81f7547c5635e855e8b80302e8fc0ce424f559295 as build
 ARG TARGETOS TARGETARCH
 
 COPY . /src/cluster-api-provider-hetzner


Test patch:
diff --git a/controllers/hetznercluster_controller_test.go b/controllers/hetznercluster_controller_test.go
index c32c15c7..ab052885 100644
--- a/controllers/hetznercluster_controller_test.go
+++ b/controllers/hetznercluster_controller_test.go
@@ -443,6 +443,78 @@ var _ = Describe("Hetzner ClusterReconciler", func() {
 					return isPresentAndFalseWithReason(key, instance, infrav1.LoadBalancerReadyCondition, infrav1.LoadBalancerFailedToOwnReason)
 				}, timeout, time.Second).Should(BeTrue())
 			})
+
+			It("should work with capi.syself.com/allow-empty-control-plane-address annotation error condition", func() {
+				instance.Annotations = make(map[string]string)
+				instance.Annotations[infrav1.AllowEmptyControlPlaneAddressAnnotation] = "true"
+				instance.Spec.ControlPlaneLoadBalancer.Enabled = false
+				instance.Spec.ControlPlaneEndpoint = nil
+				Expect(testEnv.Create(ctx, instance)).To(Succeed())
+
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, key, instance); err != nil {
+						return false
+					}
+
+					return isPresentAndFalseWithReason(key, instance, infrav1.ControlPlaneEndpointSetCondition, infrav1.ControlPlaneEndpointNotSetReason)
+				}, timeout, time.Second).Should(BeTrue())
+			})
+
+			It("should work with capi.syself.com/allow-empty-control-plane-address annotation error condition custom port", func() {
+				instance.Annotations = make(map[string]string)
+				instance.Annotations[infrav1.AllowEmptyControlPlaneAddressAnnotation] = "true"
+				instance.Spec.ControlPlaneLoadBalancer.Enabled = false
+				instance.Spec.ControlPlaneEndpoint = &clusterv1.APIEndpoint{
+					Host: "",
+					Port: 1234,
+				}
+				Expect(testEnv.Create(ctx, instance)).To(Succeed())
+
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, key, instance); err != nil {
+						return false
+					}
+
+					return isPresentAndFalseWithReason(key, instance, infrav1.ControlPlaneEndpointSetCondition, infrav1.ControlPlaneEndpointNotSetReason)
+				}, timeout, time.Second).Should(BeTrue())
+			})
+
+			It("should work with capi.syself.com/allow-empty-control-plane-address annotation success condition", func() {
+				instance.Annotations = make(map[string]string)
+				instance.Annotations[infrav1.AllowEmptyControlPlaneAddressAnnotation] = "true"
+				instance.Spec.ControlPlaneLoadBalancer.Enabled = false
+				instance.Spec.ControlPlaneEndpoint = &clusterv1.APIEndpoint{
+					Host: "localhost",
+					Port: 6443,
+				}
+				Expect(testEnv.Create(ctx, instance)).To(Succeed())
+
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, key, instance); err != nil {
+						return false
+					}
+
+					return isPresentAndTrue(key, instance, infrav1.ControlPlaneEndpointSetCondition)
+				}, timeout, time.Second).Should(BeTrue())
+			})
+
+			It("should work with enabled load balancer success", func() {
+				instance.Annotations = make(map[string]string)
+				instance.Spec.ControlPlaneLoadBalancer.Enabled = true
+				instance.Spec.ControlPlaneEndpoint = &clusterv1.APIEndpoint{
+					Host: "localhost",
+					Port: 6443,
+				}
+				Expect(testEnv.Create(ctx, instance)).To(Succeed())
+
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, key, instance); err != nil {
+						return false
+					}
+
+					return isPresentAndTrue(key, instance, infrav1.ControlPlaneEndpointSetCondition)
+				}, timeout, time.Second).Should(BeTrue())
+			})
 		})
 
 		Context("HetznerMachines belonging to the cluster", func() {
@@ -778,6 +850,35 @@ var _ = Describe("HetznerCluster validation", func() {
 			Expect(testEnv.Create(ctx, hetznerCluster)).To(Succeed())
 		})
 
+		It("should succeed with capi.syself.com/allow-empty-control-plane-address annotation", func() {
+			hetznerCluster.Annotations = make(map[string]string)
+			hetznerCluster.Annotations[infrav1.AllowEmptyControlPlaneAddressAnnotation] = "true"
+			hetznerCluster.Spec.ControlPlaneRegions = []infrav1.Region{}
+			hetznerCluster.Spec.ControlPlaneLoadBalancer.Enabled = false
+			hetznerCluster.Spec.ControlPlaneEndpoint.Port = 443
+			hetznerCluster.Spec.ControlPlaneEndpoint.Host = "localhost"
+			Expect(testEnv.Create(ctx, hetznerCluster)).To(Succeed())
+		})
+
+		It("should succeed with capi.syself.com/allow-empty-control-plane-address annotation empty host", func() {
+			hetznerCluster.Annotations = make(map[string]string)
+			hetznerCluster.Annotations[infrav1.AllowEmptyControlPlaneAddressAnnotation] = "true"
+			hetznerCluster.Spec.ControlPlaneRegions = []infrav1.Region{}
+			hetznerCluster.Spec.ControlPlaneLoadBalancer.Enabled = false
+			hetznerCluster.Spec.ControlPlaneEndpoint.Port = 443
+			hetznerCluster.Spec.ControlPlaneEndpoint.Host = ""
+			Expect(testEnv.Create(ctx, hetznerCluster)).To(Succeed())
+		})
+
+		It("should succeed with capi.syself.com/allow-empty-control-plane-address annotation empty ControlPlaneEndpoint", func() {
+			hetznerCluster.Annotations = make(map[string]string)
+			hetznerCluster.Annotations[infrav1.AllowEmptyControlPlaneAddressAnnotation] = "true"
+			hetznerCluster.Spec.ControlPlaneRegions = []infrav1.Region{}
+			hetznerCluster.Spec.ControlPlaneLoadBalancer.Enabled = false
+			hetznerCluster.Spec.ControlPlaneEndpoint = nil
+			Expect(testEnv.Create(ctx, hetznerCluster)).To(Succeed())
+		})
+
 		It("should fail without a wrong controlPlaneRegion name", func() {
 			hetznerCluster.Spec.ControlPlaneRegions = append(hetznerCluster.Spec.ControlPlaneRegions, infrav1.Region("wrong-region"))
 			Expect(testEnv.Create(ctx, hetznerCluster)).ToNot(Succeed())
@@ -853,13 +954,13 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
 			},
 		}
 
-		got := setControlPlaneEndpoint(hetznerCluster)
+		processControlPlaneEndpoint(hetznerCluster)
 
 		if hetznerCluster.Spec.ControlPlaneEndpoint != nil {
 			t.Fatalf("ControlPlaneEndpoint must be nil")
 		}
 
-		if got != false {
+		if hetznerCluster.Status.Ready != false {
 			t.Fatal("return value should be false")
 		}
 	})
@@ -872,12 +973,12 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
 				},
 				ControlPlaneEndpoint: &clusterv1.APIEndpoint{
 					Host: "xyz",
-					Port: 0,
+					Port: 1234,
 				},
 			},
 		}
 
-		got := setControlPlaneEndpoint(hetznerCluster)
+		processControlPlaneEndpoint(hetznerCluster)
 
 		if hetznerCluster.Spec.ControlPlaneEndpoint == nil {
 			t.Fatalf("ControlPlaneEndpoint must not be nil")
@@ -887,11 +988,11 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
 			t.Fatalf("Wrong input for host. Got: %s, Want: 'xyz'", hetznerCluster.Spec.ControlPlaneEndpoint.Host)
 		}
 
-		if hetznerCluster.Spec.ControlPlaneEndpoint.Port != 0 {
-			t.Fatalf("Value of Port should not change. Got: %d, Want: 0", hetznerCluster.Spec.ControlPlaneEndpoint.Port)
+		if hetznerCluster.Spec.ControlPlaneEndpoint.Port != 1234 {
+			t.Fatalf("Value of Port should not change. Got: %d, Want: 1234", hetznerCluster.Spec.ControlPlaneEndpoint.Port)
 		}
 
-		if got != true {
+		if hetznerCluster.Status.Ready != true {
 			t.Fatalf("return value should be true")
 		}
 	})
@@ -911,15 +1012,24 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
 			},
 		}
 
-		got := setControlPlaneEndpoint(hetznerCluster)
+		processControlPlaneEndpoint(hetznerCluster)
 
 		if hetznerCluster.Spec.ControlPlaneEndpoint != nil {
 			t.Fatalf("ControlPlaneEndpoint should not change. It should remain nil")
 		}
 
-		if got != false {
+		if hetznerCluster.Status.Ready != false {
 			t.Fatalf("return value should be false")
 		}
+
+		if !conditions.Has(hetznerCluster, infrav1.ControlPlaneEndpointSetCondition) {
+			t.Fatalf("ControlPlaneEndpointSetCondition should exist")
+		}
+
+		condition := conditions.Get(hetznerCluster, infrav1.ControlPlaneEndpointSetCondition)
+		if condition.Status != corev1.ConditionFalse {
+			t.Fatalf("condition status should be false")
+		}
 	})
 
 	t.Run("return true if load balancer is enabled, IPv4 is not nil, and ControlPlaneEndpoint is nil. Values of ControlPlaneEndpoint.Host and ControlPlaneEndpoint.Port will get updated", func(t *testing.T) {
@@ -938,7 +1048,7 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
 			},
 		}
 
-		got := setControlPlaneEndpoint(hetznerCluster)
+		processControlPlaneEndpoint(hetznerCluster)
 
 		if hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4 != "xyz" {
 			t.Fatalf("Wrong input for hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4. Got: %s, Want: 'xyz'", hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4)
@@ -958,7 +1068,7 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
 			t.Fatalf("Wrong value for Port set. Got: %d, Want: %d", hetznerCluster.Spec.ControlPlaneEndpoint.Port, int32(hetznerCluster.Spec.ControlPlaneLoadBalancer.Port))
 		}
 
-		if got != true {
+		if hetznerCluster.Status.Ready != true {
 			t.Fatalf("return value should be true")
 		}
 	})
@@ -982,7 +1092,7 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
 			},
 		}
 
-		got := setControlPlaneEndpoint(hetznerCluster)
+		processControlPlaneEndpoint(hetznerCluster)
 
 		if hetznerCluster.Spec.ControlPlaneEndpoint.Host != hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4 {
 			t.Fatalf("Wrong value for Host set. Got: %s, Want: %s", hetznerCluster.Spec.ControlPlaneEndpoint.Host, hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4)
@@ -992,7 +1102,7 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
 			t.Fatalf("Wrong value for Port set. Got: %d, Want: %d", hetznerCluster.Spec.ControlPlaneEndpoint.Port, int32(hetznerCluster.Spec.ControlPlaneLoadBalancer.Port))
 		}
 
-		if got != true {
+		if hetznerCluster.Status.Ready != true {
 			t.Fatalf("return value should be true")
 		}
 	})
@@ -1016,7 +1126,7 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
 			},
 		}
 
-		got := setControlPlaneEndpoint(hetznerCluster)
+		processControlPlaneEndpoint(hetznerCluster)
 
 		if hetznerCluster.Spec.ControlPlaneEndpoint.Host != "xyz" {
 			t.Fatalf("Wrong value for Host set. Got: %s, Want: 'xyz'", hetznerCluster.Spec.ControlPlaneEndpoint.Host)
@@ -1026,7 +1136,7 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
 			t.Fatalf("Wrong value for Port set. Got: %d, Want: %d", hetznerCluster.Spec.ControlPlaneEndpoint.Port, int32(hetznerCluster.Spec.ControlPlaneLoadBalancer.Port))
 		}
 
-		if got != true {
+		if hetznerCluster.Status.Ready != true {
 			t.Fatalf("return value should be true")
 		}
 	})
@@ -1050,7 +1160,7 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
 			},
 		}
 
-		got := setControlPlaneEndpoint(hetznerCluster)
+		processControlPlaneEndpoint(hetznerCluster)
 
 		if hetznerCluster.Spec.ControlPlaneEndpoint.Host != hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4 {
 			t.Fatalf("Wrong value for Host set. Got: %s, Want: %s", hetznerCluster.Spec.ControlPlaneEndpoint.Host, hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4)
@@ -1060,7 +1170,7 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
 			t.Fatalf("Wrong value for Port set. Got: %d, Want: 21", hetznerCluster.Spec.ControlPlaneEndpoint.Port)
 		}
 
-		if got != true {
+		if hetznerCluster.Status.Ready != true {
 			t.Fatalf("return value should be true")
 		}
 	})
@@ -1084,7 +1194,7 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
 			},
 		}
 
-		got := setControlPlaneEndpoint(hetznerCluster)
+		processControlPlaneEndpoint(hetznerCluster)
 
 		if hetznerCluster.Spec.ControlPlaneEndpoint.Host != "xyz" {
 			t.Fatalf("Wrong value for Host set. Got: %s, Want: 'xyz'", hetznerCluster.Spec.ControlPlaneEndpoint.Host)
@@ -1094,7 +1204,7 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
 			t.Fatalf("Wrong value for Port set. Got: %d, Want: 21", hetznerCluster.Spec.ControlPlaneEndpoint.Port)
 		}
 
-		if got != true {
+		if hetznerCluster.Status.Ready != true {
 			t.Fatalf("return value should be true")
 		}
 	})