Repository: kubernetes-sigs/cluster-api
Pull request #11923: 🌱 Add v1beta2 conditions to DevMachine and DevCluster with Docker backend
Author: login: fabriziopandini
Labels: name: cncf-cla: yes
description: Indicates the PR's author has signed the CNCF CLA.
name: lgtm
description: "Looks good to me", indicates that a PR is ready to be merged.
name: size/L
description: Denotes a PR that changes 100-499 lines, ignoring generated files.
name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: area/provider/infrastructure-docker
description: Issues or PRs related to the docker infrastructure provider
Created: 2025-03-03T18:56:55Z | Merged: 2025-03-04T21:05:45Z

rif https://github.com/kubernetes-sigs/cluster-api/issues/11474 

Area example:
/area provider/infrastructure-docker


Diff:
diff --git a/api/v1beta1/v1beta2_condition_consts.go b/api/v1beta1/v1beta2_condition_consts.go
index dd22b0a70990..c00c0fc4cd6d 100644
--- a/api/v1beta1/v1beta2_condition_consts.go
+++ b/api/v1beta1/v1beta2_condition_consts.go
@@ -209,4 +209,7 @@ const (
 
 	// ProvisionedV1Beta2Reason documents an object or a piece of infrastructure being provisioned.
 	ProvisionedV1Beta2Reason = "Provisioned"
+
+	// NotProvisionedV1Beta2Reason documents an object or a piece of infrastructure is not provisioned.
+	NotProvisionedV1Beta2Reason = "NotProvisioned"
 )
diff --git a/test/infrastructure/docker/api/v1beta1/devcluster_types.go b/test/infrastructure/docker/api/v1beta1/devcluster_types.go
index 502ceda84dc4..248296c97f2c 100644
--- a/test/infrastructure/docker/api/v1beta1/devcluster_types.go
+++ b/test/infrastructure/docker/api/v1beta1/devcluster_types.go
@@ -28,6 +28,46 @@ const (
 	ListenerAnnotationName = "inmemorycluster.infrastructure.cluster.x-k8s.io/listener"
 )
 
+// DevCluster's v1Beta2 conditions that apply to all the supported backends.
+
+// DevCluster's Ready condition and corresponding reasons that will be used in v1Beta2 API version.
+const (
+	// DevClusterReadyV1Beta2Condition is true if
+	// - The DevCluster's is using a docker backend and LoadBalancerAvailable is true.
+	DevClusterReadyV1Beta2Condition = clusterv1.ReadyV1Beta2Condition
+
+	// DevClusterReadyV1Beta2Reason surfaces when the DevCluster readiness criteria is met.
+	DevClusterReadyV1Beta2Reason = clusterv1.ReadyV1Beta2Reason
+
+	// DevClusterNotReadyV1Beta2Reason surfaces when the DevCluster readiness criteria is not met.
+	DevClusterNotReadyV1Beta2Reason = clusterv1.NotReadyV1Beta2Reason
+
+	// DevClusterReadyUnknownV1Beta2Reason surfaces when at least one DevCluster readiness criteria is unknown
+	// and no DevCluster readiness criteria is not met.
+	DevClusterReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
+)
+
+// DevCluster's v1Beta2 conditions that apply to the docker backend.
+
+// LoadBalancerAvailable condition and corresponding reasons that will be used in v1Beta2 API version for a DevCluster's docker backend.
+const (
+	// DevClusterDockerLoadBalancerAvailableV1Beta2Condition documents the availability of the container that implements
+	// the load balancer for a DevCluster's docker backend..
+	DevClusterDockerLoadBalancerAvailableV1Beta2Condition string = "LoadBalancerAvailable"
+
+	// DevClusterDockerLoadBalancerNotAvailableV1Beta2Reason surfaces when the container that implements
+	// the load balancer for a DevCluster's docker backend is not available.
+	DevClusterDockerLoadBalancerNotAvailableV1Beta2Reason = clusterv1.NotAvailableV1Beta2Reason
+
+	// DevClusterDockerLoadBalancerAvailableV1Beta2Reason surfaces when the container that implements
+	// the load balancer for a DevCluster's docker backend is available.
+	DevClusterDockerLoadBalancerAvailableV1Beta2Reason = clusterv1.AvailableV1Beta2Reason
+
+	// DevClusterDockerLoadBalancerDeletingV1Beta2Reason surfaces when the container that implements
+	// the load balancer for a DevCluster's docker backend is deleting.
+	DevClusterDockerLoadBalancerDeletingV1Beta2Reason = clusterv1.DeletingV1Beta2Reason
+)
+
 // DevClusterSpec defines the desired state of the DevCluster infrastructure.
 type DevClusterSpec struct {
 	// controlPlaneEndpoint represents the endpoint used to communicate with the control plane.
diff --git a/test/infrastructure/docker/api/v1beta1/devmachine_types.go b/test/infrastructure/docker/api/v1beta1/devmachine_types.go
index e2deb0dc22db..4d4f0318705c 100644
--- a/test/infrastructure/docker/api/v1beta1/devmachine_types.go
+++ b/test/infrastructure/docker/api/v1beta1/devmachine_types.go
@@ -70,8 +70,10 @@ const (
 
 // DevMachine's Ready condition and corresponding reasons that will be used in v1Beta2 API version.
 const (
-	// DevMachineReadyV1Beta2Condition is true if the DevMachine's VMProvisioned, NodeProvisioned,
-	// EtcdProvisioned (if present) and APIServerProvisioned (if present) conditions are true.
+	// DevMachineReadyV1Beta2Condition is true if
+	// - The DevMachine's is using a docker backend and LoadBalancerAvailable is true.
+	// - The DevMachine's is using an in memory backend and VMProvisioned, NodeProvisioned,
+	//	 EtcdProvisioned (if present) and APIServerProvisioned (if present) conditions are true
 	DevMachineReadyV1Beta2Condition = clusterv1.ReadyV1Beta2Condition
 
 	// DevMachineReadyV1Beta2Reason surfaces when the DevMachine readiness criteria is met.
@@ -85,6 +87,61 @@ const (
 	DevMachineReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
 )
 
+// DevMachine's v1Beta2 conditions that apply to the docker backend.
+
+// ContainerProvisioned condition and corresponding reasons that will be used in v1Beta2 API version for a DevMachine's docker backend.
+const (
+	// DevMachineDockerContainerProvisionedV1Beta2Condition documents the status of the provisioning of the container
+	// generated by a DevMachine's docker backend.
+	//
+	// NOTE as a difference from other providers, container provisioning and bootstrap are directly managed
+	// by the DevMachine's docker backend controller (not by cloud-init).
+	DevMachineDockerContainerProvisionedV1Beta2Condition string = "ContainerProvisioned"
+
+	// DevMachineDockerContainerWaitingForClusterInfrastructureReadyV1Beta2Reason documents the container for a DevMachine's docker backend waiting for the cluster
+	// infrastructure to be ready.
+	DevMachineDockerContainerWaitingForClusterInfrastructureReadyV1Beta2Reason = clusterv1.WaitingForClusterInfrastructureReadyV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForControlPlaneInitializedV1Beta2Reason documents a container for a DevMachine's docker backend waiting
+	// for the control plane to be initialized.
+	DevMachineDockerContainerWaitingForControlPlaneInitializedV1Beta2Reason = clusterv1.WaitingForControlPlaneInitializedV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForBootstrapDataV1Beta2Reason documents a container for a DevMachine's docker backend waiting for the bootstrap
+	// data to be ready.
+	DevMachineDockerContainerWaitingForBootstrapDataV1Beta2Reason = clusterv1.WaitingForBootstrapDataV1Beta2Reason
+
+	// DevMachineDockerContainerProvisionedV1Beta2Reason documents the container for a DevMachine's docker backend is provisioned.
+	DevMachineDockerContainerProvisionedV1Beta2Reason = clusterv1.ProvisionedV1Beta2Reason
+
+	// DevMachineDockerContainerNotProvisionedV1Beta2Reason documents the container for a DevMachine's docker
+	// backend is not provisioned.
+	DevMachineDockerContainerNotProvisionedV1Beta2Reason = clusterv1.NotProvisionedV1Beta2Reason
+
+	// DevMachineDockerContainerDeletingV1Beta2Reason surfaces when the container for a DevMachine's docker
+	// backend is deleting.
+	DevMachineDockerContainerDeletingV1Beta2Reason = clusterv1.DeletingV1Beta2Reason
+)
+
+// BootstrapExecSucceeded condition and corresponding reasons that will be used in v1Beta2 API version for a DevMachine's docker backend.
+const (
+	// DevMachineDockerContainerBootstrapExecSucceededV1Beta2Condition provides an observation of the DevMachine's docker backend bootstrap process.
+	// It is set based on successful execution of bootstrap commands and on the existence of
+	// the /run/cluster-api/bootstrap-success.complete file.
+	// The condition gets generated after ContainerProvisioned condition is True.
+	//
+	// NOTE as a difference from other providers, container provisioning and bootstrap are directly managed
+	// by the DevMachine's docker backend controller (not by cloud-init).
+	DevMachineDockerContainerBootstrapExecSucceededV1Beta2Condition string = "BootstrapExecSucceeded"
+
+	// DevMachineDockerContainerBootstrapExecSucceededV1Beta2Reason documents the container for a DevMachine's docker backend having
+	// completed bootstrap exec commands.
+	DevMachineDockerContainerBootstrapExecSucceededV1Beta2Reason string = "Succeeded"
+
+	// DevMachineDockerContainerBootstrapExecNotSucceededV1Beta2Reason documents the container for a DevMachine's docker backend not having
+	// completed bootstrap exec commands.
+	DevMachineDockerContainerBootstrapExecNotSucceededV1Beta2Reason string = "NotSucceeded"
+)
+
 // DevMachine's v1Beta2 conditions that apply to the in memory backend.
 
 // VirtualMachineProvisioned condition and corresponding reasons that will be used in v1Beta2 API version for a DevMachine's in memory backend.
diff --git a/test/infrastructure/docker/internal/controllers/backends/docker/dockercluster_backend.go b/test/infrastructure/docker/internal/controllers/backends/docker/dockercluster_backend.go
index d29dc323c33d..3b7449019783 100644
--- a/test/infrastructure/docker/internal/controllers/backends/docker/dockercluster_backend.go
+++ b/test/infrastructure/docker/internal/controllers/backends/docker/dockercluster_backend.go
@@ -19,9 +19,12 @@ package docker
 
 import (
 	"context"
+	"fmt"
 	"strconv"
+	"time"
 
 	"github.com/pkg/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -31,6 +34,7 @@ import (
 	infrav1 "sigs.k8s.io/cluster-api/test/infrastructure/docker/api/v1beta1"
 	"sigs.k8s.io/cluster-api/test/infrastructure/docker/internal/docker"
 	"sigs.k8s.io/cluster-api/util/conditions"
+	v1beta2conditions "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
 	"sigs.k8s.io/cluster-api/util/patch"
 )
 
@@ -38,9 +42,6 @@ import (
 type ClusterBackEndReconciler struct {
 	client.Client
 	ContainerRuntime container.Runtime
-
-	NewPatchHelperFunc  func(obj client.Object, crClient client.Client) (*patch.Helper, error)
-	PatchDevClusterFunc func(ctx context.Context, patchHelper *patch.Helper, dockerCluster *infrav1.DevCluster) error
 }
 
 // ReconcileNormal handle docker backend for DevCluster not yet deleted.
@@ -61,12 +62,24 @@ func (r *ClusterBackEndReconciler) ReconcileNormal(ctx context.Context, cluster
 		strconv.Itoa(dockerCluster.Spec.ControlPlaneEndpoint.Port))
 	if err != nil {
 		conditions.MarkFalse(dockerCluster, infrav1.LoadBalancerAvailableCondition, infrav1.LoadBalancerProvisioningFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
+		v1beta2conditions.Set(dockerCluster, metav1.Condition{
+			Type:    infrav1.DevClusterDockerLoadBalancerAvailableV1Beta2Condition,
+			Status:  metav1.ConditionFalse,
+			Reason:  infrav1.DevClusterDockerLoadBalancerNotAvailableV1Beta2Reason,
+			Message: fmt.Sprintf("Failed to create helper for managing the externalLoadBalancer: %v", err),
+		})
 		return ctrl.Result{}, errors.Wrapf(err, "failed to create helper for managing the externalLoadBalancer")
 	}
 
 	// Create the docker container hosting the load balancer.
 	if err := externalLoadBalancer.Create(ctx); err != nil {
 		conditions.MarkFalse(dockerCluster, infrav1.LoadBalancerAvailableCondition, infrav1.LoadBalancerProvisioningFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
+		v1beta2conditions.Set(dockerCluster, metav1.Condition{
+			Type:    infrav1.DevClusterDockerLoadBalancerAvailableV1Beta2Condition,
+			Status:  metav1.ConditionFalse,
+			Reason:  infrav1.DevClusterDockerLoadBalancerNotAvailableV1Beta2Reason,
+			Message: fmt.Sprintf("Failed to create load balancer: %v", err),
+		})
 		return ctrl.Result{}, errors.Wrap(err, "failed to create load balancer")
 	}
 
@@ -74,6 +87,12 @@ func (r *ClusterBackEndReconciler) ReconcileNormal(ctx context.Context, cluster
 	lbIP, err := externalLoadBalancer.IP(ctx)
 	if err != nil {
 		conditions.MarkFalse(dockerCluster, infrav1.LoadBalancerAvailableCondition, infrav1.LoadBalancerProvisioningFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
+		v1beta2conditions.Set(dockerCluster, metav1.Condition{
+			Type:    infrav1.DevClusterDockerLoadBalancerAvailableV1Beta2Condition,
+			Status:  metav1.ConditionFalse,
+			Reason:  infrav1.DevClusterDockerLoadBalancerNotAvailableV1Beta2Reason,
+			Message: fmt.Sprintf("Failed to get ip for the load balancer: %v", err),
+		})
 		return ctrl.Result{}, errors.Wrap(err, "failed to get ip for the load balancer")
 	}
 
@@ -86,6 +105,11 @@ func (r *ClusterBackEndReconciler) ReconcileNormal(ctx context.Context, cluster
 	// Mark the dockerCluster ready
 	dockerCluster.Status.Ready = true
 	conditions.MarkTrue(dockerCluster, infrav1.LoadBalancerAvailableCondition)
+	v1beta2conditions.Set(dockerCluster, metav1.Condition{
+		Type:   infrav1.DevClusterDockerLoadBalancerAvailableV1Beta2Condition,
+		Status: metav1.ConditionTrue,
+		Reason: infrav1.DevClusterDockerLoadBalancerAvailableV1Beta2Reason,
+	})
 
 	return ctrl.Result{}, nil
 }
@@ -103,28 +127,26 @@ func (r *ClusterBackEndReconciler) ReconcileDelete(ctx context.Context, cluster
 		strconv.Itoa(dockerCluster.Spec.ControlPlaneEndpoint.Port))
 	if err != nil {
 		conditions.MarkFalse(dockerCluster, infrav1.LoadBalancerAvailableCondition, infrav1.LoadBalancerProvisioningFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
+		v1beta2conditions.Set(dockerCluster, metav1.Condition{
+			Type:    infrav1.DevClusterDockerLoadBalancerAvailableV1Beta2Condition,
+			Status:  metav1.ConditionFalse,
+			Reason:  infrav1.DevClusterDockerLoadBalancerNotAvailableV1Beta2Reason,
+			Message: fmt.Sprintf("Failed to create helper for managing the externalLoadBalancer: %v", err),
+		})
+
 		return ctrl.Result{}, errors.Wrapf(err, "failed to create helper for managing the externalLoadBalancer")
 	}
 
-	// Set the LoadBalancerAvailableCondition reporting delete is started, and issue a patch in order to make
+	// Set the LoadBalancerAvailableCondition reporting delete is started, and requeue in order to make
 	// this visible to the users.
-	// NB. The operation in docker is fast, so there is the chance the user will not notice the status change;
-	// nevertheless we are issuing a patch so we can test a pattern that will be used by other providers as well
-	newPatchHelperFunc := r.NewPatchHelperFunc
-	if newPatchHelperFunc == nil {
-		newPatchHelperFunc = patch.NewHelper
-	}
-	patchHelper, err := newPatchHelperFunc(dockerCluster, r.Client)
-	if err != nil {
-		return ctrl.Result{}, err
-	}
-	conditions.MarkFalse(dockerCluster, infrav1.LoadBalancerAvailableCondition, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
-	patchDevClusterFunc := r.PatchDevClusterFunc
-	if patchDevClusterFunc == nil {
-		patchDevClusterFunc = r.PatchDevCluster
-	}
-	if err := patchDevClusterFunc(ctx, patchHelper, dockerCluster); err != nil {
-		return ctrl.Result{}, errors.Wrap(err, "failed to patch DockerCluster")
+	if conditions.GetReason(dockerCluster, infrav1.LoadBalancerAvailableCondition) != clusterv1.DeletingReason {
+		conditions.MarkFalse(dockerCluster, infrav1.LoadBalancerAvailableCondition, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
+		v1beta2conditions.Set(dockerCluster, metav1.Condition{
+			Type:   infrav1.DevClusterDockerLoadBalancerAvailableV1Beta2Condition,
+			Status: metav1.ConditionFalse,
+			Reason: infrav1.DevClusterDockerLoadBalancerDeletingV1Beta2Reason,
+		})
+		return ctrl.Result{RequeueAfter: 1 * time.Second}, nil
 	}
 
 	// Delete the docker container hosting the load balancer
@@ -152,6 +174,24 @@ func (r *ClusterBackEndReconciler) PatchDevCluster(ctx context.Context, patchHel
 		),
 		conditions.WithStepCounterIf(dockerCluster.ObjectMeta.DeletionTimestamp.IsZero()),
 	)
+	if err := v1beta2conditions.SetSummaryCondition(dockerCluster, dockerCluster, infrav1.DevClusterReadyV1Beta2Condition,
+		v1beta2conditions.ForConditionTypes{
+			infrav1.DevClusterDockerLoadBalancerAvailableV1Beta2Condition,
+		},
+		// Using a custom merge strategy to override reasons applied during merge.
+		v1beta2conditions.CustomMergeStrategy{
+			MergeStrategy: v1beta2conditions.DefaultMergeStrategy(
+				// Use custom reasons.
+				v1beta2conditions.ComputeReasonFunc(v1beta2conditions.GetDefaultComputeMergeReasonFunc(
+					infrav1.DevClusterNotReadyV1Beta2Reason,
+					infrav1.DevClusterReadyUnknownV1Beta2Reason,
+					infrav1.DevClusterReadyV1Beta2Reason,
+				)),
+			),
+		},
+	); err != nil {
+		return errors.Wrapf(err, "failed to set %s condition", infrav1.DevClusterReadyV1Beta2Condition)
+	}
 
 	// Patch the object, ignoring conflicts on the conditions owned by this controller.
 	return patchHelper.Patch(
@@ -161,5 +201,9 @@ func (r *ClusterBackEndReconciler) PatchDevCluster(ctx context.Context, patchHel
 			clusterv1.ReadyCondition,
 			infrav1.LoadBalancerAvailableCondition,
 		}},
+		patch.WithOwnedV1Beta2Conditions{Conditions: []string{
+			infrav1.DevClusterReadyV1Beta2Condition,
+			infrav1.DevClusterDockerLoadBalancerAvailableV1Beta2Condition,
+		}},
 	)
 }
diff --git a/test/infrastructure/docker/internal/controllers/backends/docker/dockermachine_backend.go b/test/infrastructure/docker/internal/controllers/backends/docker/dockermachine_backend.go
index d980d74650d1..c244dee8a8e7 100644
--- a/test/infrastructure/docker/internal/controllers/backends/docker/dockermachine_backend.go
+++ b/test/infrastructure/docker/internal/controllers/backends/docker/dockermachine_backend.go
@@ -41,6 +41,7 @@ import (
 	"sigs.k8s.io/cluster-api/test/infrastructure/docker/internal/docker"
 	"sigs.k8s.io/cluster-api/util"
 	"sigs.k8s.io/cluster-api/util/conditions"
+	v1beta2conditions "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
 	"sigs.k8s.io/cluster-api/util/labels"
 	"sigs.k8s.io/cluster-api/util/patch"
 )
@@ -50,9 +51,6 @@ type MachineBackendReconciler struct {
 	client.Client
 	ContainerRuntime container.Runtime
 	ClusterCache     clustercache.ClusterCache
-
-	NewPatchHelperFunc  func(obj client.Object, crClient client.Client) (*patch.Helper, error)
-	PatchDevMachineFunc func(ctx context.Context, patchHelper *patch.Helper, dockerMachine *infrav1.DevMachine, _ bool) error
 }
 
 // ReconcileNormal handle docker backend for DevMachines not yet deleted.
@@ -74,6 +72,11 @@ func (r *MachineBackendReconciler) ReconcileNormal(ctx context.Context, cluster
 	if !cluster.Status.InfrastructureReady {
 		log.Info("Waiting for DockerCluster Controller to create cluster infrastructure")
 		conditions.MarkFalse(dockerMachine, infrav1.ContainerProvisionedCondition, infrav1.WaitingForClusterInfrastructureReason, clusterv1.ConditionSeverityInfo, "")
+		v1beta2conditions.Set(dockerMachine, metav1.Condition{
+			Type:   infrav1.DevMachineDockerContainerProvisionedV1Beta2Condition,
+			Status: metav1.ConditionFalse,
+			Reason: infrav1.DevMachineDockerContainerWaitingForClusterInfrastructureReadyV1Beta2Reason,
+		})
 		return ctrl.Result{}, nil
 	}
 
@@ -118,12 +121,24 @@ func (r *MachineBackendReconciler) ReconcileNormal(ctx context.Context, cluster
 
 		if externalMachine.Exists() {
 			conditions.MarkTrue(dockerMachine, infrav1.ContainerProvisionedCondition)
+			v1beta2conditions.Set(dockerMachine, metav1.Condition{
+				Type:   infrav1.DevMachineDockerContainerProvisionedV1Beta2Condition,
+				Status: metav1.ConditionTrue,
+				Reason: infrav1.DevMachineDockerContainerProvisionedV1Beta2Reason,
+			})
+
 			// Setting machine address is required after move, because status.Address field is not retained during move.
 			if err := setMachineAddress(ctx, dockerMachine, externalMachine); err != nil {
 				return ctrl.Result{}, errors.Wrap(err, "failed to set the machine address")
 			}
 		} else {
-			conditions.MarkFalse(dockerMachine, infrav1.ContainerProvisionedCondition, infrav1.ContainerDeletedReason, clusterv1.ConditionSeverityError, fmt.Sprintf("Container %s does not exists anymore", externalMachine.Name()))
+			conditions.MarkFalse(dockerMachine, infrav1.ContainerProvisionedCondition, infrav1.ContainerDeletedReason, clusterv1.ConditionSeverityError, fmt.Sprintf("Container %s does not exist anymore", externalMachine.Name()))
+			v1beta2conditions.Set(dockerMachine, metav1.Condition{
+				Type:    infrav1.DevMachineDockerContainerProvisionedV1Beta2Condition,
+				Status:  metav1.ConditionFalse,
+				Reason:  infrav1.DevMachineDockerContainerNotProvisionedV1Beta2Reason,
+				Message: fmt.Sprintf("Container %s does not exist anymore", externalMachine.Name()),
+			})
 		}
 		return ctrl.Result{}, nil
 	}
@@ -133,11 +148,21 @@ func (r *MachineBackendReconciler) ReconcileNormal(ctx context.Context, cluster
 		if !util.IsControlPlaneMachine(machine) && !conditions.IsTrue(cluster, clusterv1.ControlPlaneInitializedCondition) {
 			log.Info("Waiting for the control plane to be initialized")
 			conditions.MarkFalse(dockerMachine, infrav1.ContainerProvisionedCondition, clusterv1.WaitingForControlPlaneAvailableReason, clusterv1.ConditionSeverityInfo, "")
+			v1beta2conditions.Set(dockerMachine, metav1.Condition{
+				Type:   infrav1.DevMachineDockerContainerProvisionedV1Beta2Condition,
+				Status: metav1.ConditionFalse,
+				Reason: infrav1.DevMachineDockerContainerWaitingForControlPlaneInitializedV1Beta2Reason,
+			})
 			return ctrl.Result{}, nil
 		}
 
 		log.Info("Waiting for the Bootstrap provider controller to set bootstrap data")
 		conditions.MarkFalse(dockerMachine, infrav1.ContainerProvisionedCondition, infrav1.WaitingForBootstrapDataReason, clusterv1.ConditionSeverityInfo, "")
+		v1beta2conditions.Set(dockerMachine, metav1.Condition{
+			Type:   infrav1.DevMachineDockerContainerProvisionedV1Beta2Condition,
+			Status: metav1.ConditionFalse,
+			Reason: infrav1.DevMachineDockerContainerWaitingForBootstrapDataV1Beta2Reason,
+		})
 		return ctrl.Result{}, nil
 	}
 
@@ -179,32 +204,28 @@ func (r *MachineBackendReconciler) ReconcileNormal(ctx context.Context, cluster
 		dockerMachine.Status.Backend.Docker.LoadBalancerConfigured = true
 	}
 
-	// Update the ContainerProvisionedCondition condition
-	// NOTE: it is required to create the patch helper before this change otherwise it wont surface if
-	// we issue a patch down in the code (because if we create patch helper after this point the ContainerProvisionedCondition=True exists both on before and after).
-	newPatchHelperFunc := r.NewPatchHelperFunc
-	if newPatchHelperFunc == nil {
-		newPatchHelperFunc = patch.NewHelper
+	// Update the ContainerProvisioned and BootstrapExecSucceeded condition if not already in the correct state.
+	requeue := false
+	if !conditions.IsTrue(dockerMachine, infrav1.ContainerProvisionedCondition) {
+		conditions.MarkTrue(dockerMachine, infrav1.ContainerProvisionedCondition)
+		v1beta2conditions.Set(dockerMachine, metav1.Condition{
+			Type:   infrav1.DevMachineDockerContainerProvisionedV1Beta2Condition,
+			Status: metav1.ConditionTrue,
+			Reason: infrav1.DevMachineDockerContainerProvisionedV1Beta2Reason,
+		})
+		requeue = true
 	}
-	patchHelper, err := newPatchHelperFunc(dockerMachine, r.Client)
-	if err != nil {
-		return ctrl.Result{}, err
-	}
-	conditions.MarkTrue(dockerMachine, infrav1.ContainerProvisionedCondition)
-
-	// At, this stage, we are ready for bootstrap. However, if the BootstrapExecSucceededCondition is missing we add it and we
-	// issue an patch so the user can see the change of state before the bootstrap actually starts.
-	// NOTE: usually controller should not rely on status they are setting, but on the observed state; however
-	// in this case we are doing this because we explicitly want to give a feedback to users.
 	if !conditions.Has(dockerMachine, infrav1.BootstrapExecSucceededCondition) {
 		conditions.MarkFalse(dockerMachine, infrav1.BootstrapExecSucceededCondition, infrav1.BootstrappingReason, clusterv1.ConditionSeverityInfo, "")
-		patchDevMachineFunc := r.PatchDevMachineFunc
-		if patchDevMachineFunc == nil {
-			patchDevMachineFunc = r.PatchDevMachine
-		}
-		if err := patchDevMachineFunc(ctx, patchHelper, dockerMachine, util.IsControlPlaneMachine(machine)); err != nil {
-			return ctrl.Result{}, errors.Wrap(err, "failed to patch DockerMachine")
-		}
+		v1beta2conditions.Set(dockerMachine, metav1.Condition{
+			Type:   infrav1.DevMachineDockerContainerBootstrapExecSucceededV1Beta2Condition,
+			Status: metav1.ConditionFalse,
+			Reason: infrav1.DevMachineDockerContainerBootstrapExecNotSucceededV1Beta2Reason,
+		})
+		requeue = true
+	}
+	if requeue {
+		return ctrl.Result{RequeueAfter: 1 * time.Second}, nil
 	}
 
 	// if the machine isn't bootstrapped, only then run bootstrap scripts
@@ -254,12 +275,24 @@ func (r *MachineBackendReconciler) ReconcileNormal(ctx context.Context, cluster
 			// Run the bootstrap script. Simulates cloud-init/Ignition.
 			if err := externalMachine.ExecBootstrap(timeoutCtx, bootstrapData, format, version, dockerMachine.Spec.Backend.Docker.CustomImage); err != nil {
 				conditions.MarkFalse(dockerMachine, infrav1.BootstrapExecSucceededCondition, infrav1.BootstrapFailedReason, clusterv1.ConditionSeverityWarning, "Repeating bootstrap")
+				v1beta2conditions.Set(dockerMachine, metav1.Condition{
+					Type:    infrav1.DevMachineDockerContainerBootstrapExecSucceededV1Beta2Condition,
+					Status:  metav1.ConditionFalse,
+					Reason:  infrav1.DevMachineDockerContainerBootstrapExecNotSucceededV1Beta2Reason,
+					Message: "Failed to exec DockerMachine bootstrap",
+				})
 				return ctrl.Result{}, errors.Wrap(err, "failed to exec DockerMachine bootstrap")
 			}
 
 			// Check for bootstrap success
 			if err := externalMachine.CheckForBootstrapSuccess(timeoutCtx, true); err != nil {
 				conditions.MarkFalse(dockerMachine, infrav1.BootstrapExecSucceededCondition, infrav1.BootstrapFailedReason, clusterv1.ConditionSeverityWarning, "Repeating bootstrap")
+				v1beta2conditions.Set(dockerMachine, metav1.Condition{
+					Type:    infrav1.DevMachineDockerContainerBootstrapExecSucceededV1Beta2Condition,
+					Status:  metav1.ConditionFalse,
+					Reason:  infrav1.DevMachineDockerContainerBootstrapExecNotSucceededV1Beta2Reason,
+					Message: "Failed to check for existence of bootstrap success file at /run/cluster-api/bootstrap-success.complete",
+				})
 				return ctrl.Result{}, errors.Wrap(err, "failed to check for existence of bootstrap success file at /run/cluster-api/bootstrap-success.complete")
 			}
 		}
@@ -268,6 +301,11 @@ func (r *MachineBackendReconciler) ReconcileNormal(ctx context.Context, cluster
 
 	// Update the BootstrapExecSucceededCondition condition
 	conditions.MarkTrue(dockerMachine, infrav1.BootstrapExecSucceededCondition)
+	v1beta2conditions.Set(dockerMachine, metav1.Condition{
+		Type:   infrav1.DevMachineDockerContainerBootstrapExecSucceededV1Beta2Condition,
+		Status: metav1.ConditionTrue,
+		Reason: infrav1.DevMachineDockerContainerBootstrapExecSucceededV1Beta2Reason,
+	})
 
 	if err := setMachineAddress(ctx, dockerMachine, externalMachine); err != nil {
 		log.Error(err, "Failed to set the machine address")
@@ -282,7 +320,7 @@ func (r *MachineBackendReconciler) ReconcileNormal(ctx context.Context, cluster
 	// Machine will never get a node ref as ProviderID is required to set the node ref, so we would get a deadlock.
 	if cluster.Spec.ControlPlaneRef != nil &&
 		!conditions.IsTrue(cluster, clusterv1.ControlPlaneInitializedCondition) {
-		return ctrl.Result{RequeueAfter: 15 * time.Second}, nil
+		return ctrl.Result{RequeueAfter: 5 * time.Second}, nil
 	}
 
 	// Usually a cloud provider will do this, but there is no docker-cloud provider.
@@ -303,7 +341,6 @@ func (r *MachineBackendReconciler) ReconcileNormal(ctx context.Context, cluster
 	providerID := externalMachine.ProviderID()
 	dockerMachine.Spec.ProviderID = &providerID
 	dockerMachine.Status.Ready = true
-	conditions.MarkTrue(dockerMachine, infrav1.ContainerProvisionedCondition)
 
 	return ctrl.Result{}, nil
 }
@@ -362,22 +399,13 @@ func (r *MachineBackendReconciler) ReconcileDelete(ctx context.Context, cluster
 	// this visible to the users.
 	// NB. The operation in docker is fast, so there is the chance the user will not notice the status change;
 	// nevertheless we are issuing a patch so we can test a pattern that will be used by other providers as well
-	newPatchHelperFunc := r.NewPatchHelperFunc
-	if newPatchHelperFunc == nil {
-		newPatchHelperFunc = patch.NewHelper
-	}
-	patchHelper, err := newPatchHelperFunc(dockerMachine, r.Client)
-	if err != nil {
-		return ctrl.Result{}, err
-	}
-	conditions.MarkFalse(dockerMachine, infrav1.ContainerProvisionedCondition, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
-
-	patchDevMachineFunc := r.PatchDevMachineFunc
-	if patchDevMachineFunc == nil {
-		patchDevMachineFunc = r.PatchDevMachine
-	}
-	if err := patchDevMachineFunc(ctx, patchHelper, dockerMachine, util.IsControlPlaneMachine(machine)); err != nil {
-		return ctrl.Result{}, errors.Wrap(err, "failed to patch DockerMachine")
+	if conditions.GetReason(dockerMachine, infrav1.ContainerProvisionedCondition) != clusterv1.DeletingReason {
+		conditions.MarkFalse(dockerMachine, infrav1.ContainerProvisionedCondition, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
+		v1beta2conditions.Set(dockerCluster, metav1.Condition{
+			Type:   infrav1.DevMachineDockerContainerProvisionedV1Beta2Condition,
+			Status: metav1.ConditionFalse,
+			Reason: infrav1.DevMachineDockerContainerDeletingV1Beta2Reason,
+		})
 	}
 
 	// delete the machine
@@ -412,6 +440,25 @@ func (r *MachineBackendReconciler) PatchDevMachine(ctx context.Context, patchHel
 		),
 		conditions.WithStepCounterIf(dockerMachine.ObjectMeta.DeletionTimestamp.IsZero() && dockerMachine.Spec.ProviderID == nil),
 	)
+	if err := v1beta2conditions.SetSummaryCondition(dockerMachine, dockerMachine, infrav1.DevMachineReadyV1Beta2Condition,
+		v1beta2conditions.ForConditionTypes{
+			infrav1.DevMachineDockerContainerProvisionedV1Beta2Condition,
+			infrav1.DevMachineDockerContainerBootstrapExecSucceededV1Beta2Condition,
+		},
+		// Using a custom merge strategy to override reasons applied during merge.
+		v1beta2conditions.CustomMergeStrategy{
+			MergeStrategy: v1beta2conditions.DefaultMergeStrategy(
+				// Use custom reasons.
+				v1beta2conditions.ComputeReasonFunc(v1beta2conditions.GetDefaultComputeMergeReasonFunc(
+					infrav1.DevMachineNotReadyV1Beta2Reason,
+					infrav1.DevMachineReadyUnknownV1Beta2Reason,
+					infrav1.DevMachineReadyV1Beta2Reason,
+				)),
+			),
+		},
+	); err != nil {
+		return errors.Wrapf(err, "failed to set %s condition", infrav1.DevMachineReadyV1Beta2Condition)
+	}
 
 	// Patch the object, ignoring conflicts on the conditions owned by this controller.
 	return patchHelper.Patch(
@@ -422,6 +469,11 @@ func (r *MachineBackendReconciler) PatchDevMachine(ctx context.Context, patchHel
 			infrav1.ContainerProvisionedCondition,
 			infrav1.BootstrapExecSucceededCondition,
 		}},
+		patch.WithOwnedV1Beta2Conditions{Conditions: []string{
+			infrav1.DevMachineReadyV1Beta2Condition,
+			infrav1.DevMachineDockerContainerProvisionedV1Beta2Condition,
+			infrav1.DevMachineDockerContainerBootstrapExecSucceededV1Beta2Condition,
+		}},
 	)
 }
 
diff --git a/test/infrastructure/docker/internal/controllers/dockercluster_controller.go b/test/infrastructure/docker/internal/controllers/dockercluster_controller.go
index bf23e568f7ee..55d24277aaa7 100644
--- a/test/infrastructure/docker/internal/controllers/dockercluster_controller.go
+++ b/test/infrastructure/docker/internal/controllers/dockercluster_controller.go
@@ -19,7 +19,6 @@ package controllers
 
 import (
 	"context"
-	"fmt"
 
 	"github.com/pkg/errors"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -36,6 +35,7 @@ import (
 	dockerbackend "sigs.k8s.io/cluster-api/test/infrastructure/docker/internal/controllers/backends/docker"
 	"sigs.k8s.io/cluster-api/util"
 	"sigs.k8s.io/cluster-api/util/conditions"
+	v1beta2conditions "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
 	"sigs.k8s.io/cluster-api/util/finalizers"
 	"sigs.k8s.io/cluster-api/util/patch"
 	"sigs.k8s.io/cluster-api/util/paused"
@@ -145,20 +145,6 @@ func (r *DockerClusterReconciler) SetupWithManager(ctx context.Context, mgr ctrl
 	r.backendReconciler = &dockerbackend.ClusterBackEndReconciler{
 		Client:           r.Client,
 		ContainerRuntime: r.ContainerRuntime,
-		NewPatchHelperFunc: func(obj client.Object, crClient client.Client) (*patch.Helper, error) {
-			devCluster, ok := obj.(*infrav1.DevCluster)
-			if !ok {
-				panic(fmt.Sprintf("Expected obj to be *infrav1.DevCluster, got %T", obj))
-			}
-			dockerCluster := &infrav1.DockerCluster{}
-			devClusterToDockerCluster(devCluster, dockerCluster)
-			return patch.NewHelper(dockerCluster, crClient)
-		},
-		PatchDevClusterFunc: func(ctx context.Context, patchHelper *patch.Helper, devCluster *infrav1.DevCluster) error {
-			dockerCluster := &infrav1.DockerCluster{}
-			devClusterToDockerCluster(devCluster, dockerCluster)
-			return patchDockerCluster(ctx, patchHelper, dockerCluster)
-		},
 	}
 
 	return nil
@@ -173,6 +159,24 @@ func patchDockerCluster(ctx context.Context, patchHelper *patch.Helper, dockerCl
 		),
 		conditions.WithStepCounterIf(dockerCluster.ObjectMeta.DeletionTimestamp.IsZero()),
 	)
+	if err := v1beta2conditions.SetSummaryCondition(dockerCluster, dockerCluster, infrav1.DevClusterReadyV1Beta2Condition,
+		v1beta2conditions.ForConditionTypes{
+			infrav1.DevClusterDockerLoadBalancerAvailableV1Beta2Condition,
+		},
+		// Using a custom merge strategy to override reasons applied during merge.
+		v1beta2conditions.CustomMergeStrategy{
+			MergeStrategy: v1beta2conditions.DefaultMergeStrategy(
+				// Use custom reasons.
+				v1beta2conditions.ComputeReasonFunc(v1beta2conditions.GetDefaultComputeMergeReasonFunc(
+					infrav1.DevClusterNotReadyV1Beta2Reason,
+					infrav1.DevClusterReadyUnknownV1Beta2Reason,
+					infrav1.DevClusterReadyV1Beta2Reason,
+				)),
+			),
+		},
+	); err != nil {
+		return errors.Wrapf(err, "failed to set %s condition", infrav1.DevClusterReadyV1Beta2Condition)
+	}
 
 	// Patch the object, ignoring conflicts on the conditions owned by this controller.
 	return patchHelper.Patch(
@@ -182,6 +186,10 @@ func patchDockerCluster(ctx context.Context, patchHelper *patch.Helper, dockerCl
 			clusterv1.ReadyCondition,
 			infrav1.LoadBalancerAvailableCondition,
 		}},
+		patch.WithOwnedV1Beta2Conditions{Conditions: []string{
+			infrav1.DevClusterReadyV1Beta2Condition,
+			infrav1.DevClusterDockerLoadBalancerAvailableV1Beta2Condition,
+		}},
 	)
 }
 
diff --git a/test/infrastructure/docker/internal/controllers/dockermachine_controller.go b/test/infrastructure/docker/internal/controllers/dockermachine_controller.go
index 56ceb3e370d8..7631cc3911f6 100644
--- a/test/infrastructure/docker/internal/controllers/dockermachine_controller.go
+++ b/test/infrastructure/docker/internal/controllers/dockermachine_controller.go
@@ -37,6 +37,7 @@ import (
 	dockerbackend "sigs.k8s.io/cluster-api/test/infrastructure/docker/internal/controllers/backends/docker"
 	"sigs.k8s.io/cluster-api/util"
 	"sigs.k8s.io/cluster-api/util/conditions"
+	v1beta2conditions "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
 	"sigs.k8s.io/cluster-api/util/finalizers"
 	clog "sigs.k8s.io/cluster-api/util/log"
 	"sigs.k8s.io/cluster-api/util/patch"
@@ -206,20 +207,6 @@ func (r *DockerMachineReconciler) SetupWithManager(ctx context.Context, mgr ctrl
 		Client:           r.Client,
 		ContainerRuntime: r.ContainerRuntime,
 		ClusterCache:     r.ClusterCache,
-		NewPatchHelperFunc: func(obj client.Object, crClient client.Client) (*patch.Helper, error) {
-			devMachine, ok := obj.(*infrav1.DevMachine)
-			if !ok {
-				panic(fmt.Sprintf("Expected obj to be *infrav1.DevMachine, got %T", obj))
-			}
-			dockerMachine := &infrav1.DockerMachine{}
-			devMachineToDockerMachine(devMachine, dockerMachine)
-			return patch.NewHelper(dockerMachine, crClient)
-		},
-		PatchDevMachineFunc: func(ctx context.Context, patchHelper *patch.Helper, devMachine *infrav1.DevMachine, _ bool) error {
-			dockerMachine := &infrav1.DockerMachine{}
-			devMachineToDockerMachine(devMachine, dockerMachine)
-			return patchDockerMachine(ctx, patchHelper, dockerMachine)
-		},
 	}
 
 	return nil
@@ -268,6 +255,25 @@ func patchDockerMachine(ctx context.Context, patchHelper *patch.Helper, dockerMa
 		),
 		conditions.WithStepCounterIf(dockerMachine.ObjectMeta.DeletionTimestamp.IsZero() && dockerMachine.Spec.ProviderID == nil),
 	)
+	if err := v1beta2conditions.SetSummaryCondition(dockerMachine, dockerMachine, infrav1.DevMachineReadyV1Beta2Condition,
+		v1beta2conditions.ForConditionTypes{
+			infrav1.DevMachineDockerContainerProvisionedV1Beta2Condition,
+			infrav1.DevMachineDockerContainerBootstrapExecSucceededV1Beta2Condition,
+		},
+		// Using a custom merge strategy to override reasons applied during merge.
+		v1beta2conditions.CustomMergeStrategy{
+			MergeStrategy: v1beta2conditions.DefaultMergeStrategy(
+				// Use custom reasons.
+				v1beta2conditions.ComputeReasonFunc(v1beta2conditions.GetDefaultComputeMergeReasonFunc(
+					infrav1.DevMachineNotReadyV1Beta2Reason,
+					infrav1.DevMachineReadyUnknownV1Beta2Reason,
+					infrav1.DevMachineReadyV1Beta2Reason,
+				)),
+			),
+		},
+	); err != nil {
+		return errors.Wrapf(err, "failed to set %s condition", infrav1.DevMachineReadyV1Beta2Condition)
+	}
 
 	// Patch the object, ignoring conflicts on the conditions owned by this controller.
 	return patchHelper.Patch(
@@ -278,6 +284,11 @@ func patchDockerMachine(ctx context.Context, patchHelper *patch.Helper, dockerMa
 			infrav1.ContainerProvisionedCondition,
 			infrav1.BootstrapExecSucceededCondition,
 		}},
+		patch.WithOwnedV1Beta2Conditions{Conditions: []string{
+			infrav1.DevMachineReadyV1Beta2Condition,
+			infrav1.DevMachineDockerContainerProvisionedV1Beta2Condition,
+			infrav1.DevMachineDockerContainerBootstrapExecSucceededV1Beta2Condition,
+		}},
 	)
 }
 


Reviews:
user: login: sbueringer
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T13:55:13Z
user: login: sbueringer
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T13:57:10Z
user: login: sbueringer
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T13:57:28Z
user: login: fabriziopandini
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T15:50:25Z
user: login: fabriziopandini
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T15:53:35Z
user: login: fabriziopandini
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T16:11:22Z
user: login: fabriziopandini
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T16:14:26Z
user: login: fabriziopandini
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T16:15:55Z
user: login: sbueringer
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T17:42:27Z
user: login: sbueringer
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T19:01:06Z
user: login: sbueringer
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T19:03:22Z
user: login: sbueringer
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T19:04:02Z
user: login: sbueringer
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T19:04:55Z

Review comments:
diff_hunk: @@ -28,6 +28,46 @@ const (
 	ListenerAnnotationName = "inmemorycluster.infrastructure.cluster.x-k8s.io/listener"
 )
 
+// DevCluster's v1Beta2 conditions that apply to all the supported backends.
+
+// DevCluster's Ready condition and corresponding reasons that will be used in v1Beta2 API version.
+const (
+	// DevClusterReadyV1Beta2Condition is true if the DevCluster's VMProvisioned, NodeProvisioned,
+	// EtcdProvisioned (if present) and APIServerProvisioned (if present) conditions are true.
+	DevClusterReadyV1Beta2Condition = clusterv1.ReadyV1Beta2Condition
+
+	// DevClusterReadyV1Beta2Reason surfaces when the DevCluster readiness criteria is met.
+	DevClusterReadyV1Beta2Reason = clusterv1.ReadyV1Beta2Reason
+
+	// DevClusterNotReadyV1Beta2Reason surfaces when the DevCluster readiness criteria is not met.
+	DevClusterNotReadyV1Beta2Reason = clusterv1.NotReadyV1Beta2Reason
+
+	// DevClusterReadyUnknownV1Beta2Reason surfaces when at least one DevCluster readiness criteria is unknown
+	// and no DevCluster readiness criteria is not met.
+	DevClusterReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
path: test/infrastructure/docker/api/v1beta1/devcluster_types.go
user: login: sbueringer
body: Is it intentional that none of these are used in this PR? Will we use them in a subsequent one?
created_at: 2025-03-04T13:25:40Z
author_association: MEMBER
diff_hunk: @@ -28,6 +28,46 @@ const (
 	ListenerAnnotationName = "inmemorycluster.infrastructure.cluster.x-k8s.io/listener"
 )
 
+// DevCluster's v1Beta2 conditions that apply to all the supported backends.
+
+// DevCluster's Ready condition and corresponding reasons that will be used in v1Beta2 API version.
+const (
+	// DevClusterReadyV1Beta2Condition is true if the DevCluster's VMProvisioned, NodeProvisioned,
+	// EtcdProvisioned (if present) and APIServerProvisioned (if present) conditions are true.
path: test/infrastructure/docker/api/v1beta1/devcluster_types.go
user: login: sbueringer
body: This doesn't seem true
created_at: 2025-03-04T13:26:32Z
author_association: MEMBER
diff_hunk: @@ -28,6 +28,46 @@ const (
 	ListenerAnnotationName = "inmemorycluster.infrastructure.cluster.x-k8s.io/listener"
 )
 
+// DevCluster's v1Beta2 conditions that apply to all the supported backends.
+
+// DevCluster's Ready condition and corresponding reasons that will be used in v1Beta2 API version.
+const (
+	// DevClusterReadyV1Beta2Condition is true if the DevCluster's VMProvisioned, NodeProvisioned,
+	// EtcdProvisioned (if present) and APIServerProvisioned (if present) conditions are true.
+	DevClusterReadyV1Beta2Condition = clusterv1.ReadyV1Beta2Condition
+
+	// DevClusterReadyV1Beta2Reason surfaces when the DevCluster readiness criteria is met.
+	DevClusterReadyV1Beta2Reason = clusterv1.ReadyV1Beta2Reason
+
+	// DevClusterNotReadyV1Beta2Reason surfaces when the DevCluster readiness criteria is not met.
+	DevClusterNotReadyV1Beta2Reason = clusterv1.NotReadyV1Beta2Reason
+
+	// DevClusterReadyUnknownV1Beta2Reason surfaces when at least one DevCluster readiness criteria is unknown
+	// and no DevCluster readiness criteria is not met.
+	DevClusterReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
+)
+
+// DevMachine's v1Beta2 conditions that apply to the docker backend.
path: test/infrastructure/docker/api/v1beta1/devcluster_types.go
user: login: sbueringer
body: ```suggestion
// DevCluster's v1Beta2 conditions that apply to the docker backend.
```
I think
created_at: 2025-03-04T13:28:11Z
author_association: MEMBER
diff_hunk: @@ -28,6 +28,46 @@ const (
 	ListenerAnnotationName = "inmemorycluster.infrastructure.cluster.x-k8s.io/listener"
 )
 
+// DevCluster's v1Beta2 conditions that apply to all the supported backends.
+
+// DevCluster's Ready condition and corresponding reasons that will be used in v1Beta2 API version.
+const (
+	// DevClusterReadyV1Beta2Condition is true if the DevCluster's VMProvisioned, NodeProvisioned,
+	// EtcdProvisioned (if present) and APIServerProvisioned (if present) conditions are true.
+	DevClusterReadyV1Beta2Condition = clusterv1.ReadyV1Beta2Condition
+
+	// DevClusterReadyV1Beta2Reason surfaces when the DevCluster readiness criteria is met.
+	DevClusterReadyV1Beta2Reason = clusterv1.ReadyV1Beta2Reason
+
+	// DevClusterNotReadyV1Beta2Reason surfaces when the DevCluster readiness criteria is not met.
+	DevClusterNotReadyV1Beta2Reason = clusterv1.NotReadyV1Beta2Reason
+
+	// DevClusterReadyUnknownV1Beta2Reason surfaces when at least one DevCluster readiness criteria is unknown
+	// and no DevCluster readiness criteria is not met.
+	DevClusterReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
+)
+
+// DevMachine's v1Beta2 conditions that apply to the docker backend.
+
+// LoadBalancerAvailable condition and corresponding reasons that will be used in v1Beta2 API version for a DevCluster's docker backend.
+const (
+	// DockerClusterDockerLoadBalancerAvailableV1Beta2Condition documents the availability of the container that implements
+	// the load balancer for a DevCluster's docker backend..
+	DockerClusterDockerLoadBalancerAvailableV1Beta2Condition string = "LoadBalancerAvailable"
path: test/infrastructure/docker/api/v1beta1/devcluster_types.go
user: login: sbueringer
body: DockerClusterDocker seems redundant (same below)

Should this be DevClusterDockerLoadBalancer... ?
created_at: 2025-03-04T13:28:49Z
author_association: MEMBER
diff_hunk: @@ -28,6 +28,46 @@ const (
 	ListenerAnnotationName = "inmemorycluster.infrastructure.cluster.x-k8s.io/listener"
 )
 
+// DevCluster's v1Beta2 conditions that apply to all the supported backends.
+
+// DevCluster's Ready condition and corresponding reasons that will be used in v1Beta2 API version.
+const (
+	// DevClusterReadyV1Beta2Condition is true if the DevCluster's VMProvisioned, NodeProvisioned,
+	// EtcdProvisioned (if present) and APIServerProvisioned (if present) conditions are true.
+	DevClusterReadyV1Beta2Condition = clusterv1.ReadyV1Beta2Condition
+
+	// DevClusterReadyV1Beta2Reason surfaces when the DevCluster readiness criteria is met.
+	DevClusterReadyV1Beta2Reason = clusterv1.ReadyV1Beta2Reason
+
+	// DevClusterNotReadyV1Beta2Reason surfaces when the DevCluster readiness criteria is not met.
+	DevClusterNotReadyV1Beta2Reason = clusterv1.NotReadyV1Beta2Reason
+
+	// DevClusterReadyUnknownV1Beta2Reason surfaces when at least one DevCluster readiness criteria is unknown
+	// and no DevCluster readiness criteria is not met.
+	DevClusterReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
+)
+
+// DevMachine's v1Beta2 conditions that apply to the docker backend.
+
+// LoadBalancerAvailable condition and corresponding reasons that will be used in v1Beta2 API version for a DevCluster's docker backend.
+const (
+	// DockerClusterDockerLoadBalancerAvailableV1Beta2Condition documents the availability of the container that implements
+	// the load balancer for a DevCluster's docker backend..
+	DockerClusterDockerLoadBalancerAvailableV1Beta2Condition string = "LoadBalancerAvailable"
+
+	// DockerClusterDockerLoadBalancerNotAvailableV1Beta2Reason surfaces when the container that implements
+	// the load balancer for a DevCluster's docker backend is not available.
+	DockerClusterDockerLoadBalancerNotAvailableV1Beta2Reason = clusterv1.NotAvailableV1Beta2Reason
+
+	// DockerClusterDockerLoadBalancerNotAvailableV1Beta2Reason surfaces when the container that implements
path: test/infrastructure/docker/api/v1beta1/devcluster_types.go
user: login: sbueringer
body: mismatch between this word and the name of the constant

(btw Intellij is reporting these for me)
created_at: 2025-03-04T13:29:49Z
author_association: MEMBER
diff_hunk: @@ -85,6 +85,61 @@ const (
 	DevMachineReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
 )
 
+// DevMachine's v1Beta2 conditions that apply to the docker backend.
+
+// ContainerProvisioned condition and corresponding reasons that will be used in v1Beta2 API version for a DevMachine's docker backend.
+const (
+	// DevMachineDockerContainerProvisionedV1Beta2Condition documents the status of the provisioning of the container
+	// generated by a DevMachine's docker backend.
+	//
+	// NOTE as a difference from other providers, container provisioning and bootstrap are directly managed
+	// by the DevMachine's docker backend controller (not by cloud-init).
+	DevMachineDockerContainerProvisionedV1Beta2Condition string = "ContainerProvisioned"
+
+	// DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason documents the container for a DevMachine's docker backend waiting for the cluster
+	// infrastructure to be ready.
+	DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason = clusterv1.WaitingForClusterInfrastructureReadyV1Beta2Reason
path: test/infrastructure/docker/api/v1beta1/devmachine_types.go
user: login: sbueringer
body: ```suggestion
	// DevMachineDockerContainerWaitingForClusterInfrastructureReadyV1Beta2Reason documents the container for a DevMachine's docker backend waiting for the cluster
	// infrastructure to be ready.
	DevMachineDockerContainerWaitingForClusterInfrastructureReadyV1Beta2Reason = clusterv1.WaitingForClusterInfrastructureReadyV1Beta2Reason
```
created_at: 2025-03-04T13:32:03Z
author_association: MEMBER
diff_hunk: @@ -85,6 +85,61 @@ const (
 	DevMachineReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
 )
 
+// DevMachine's v1Beta2 conditions that apply to the docker backend.
+
+// ContainerProvisioned condition and corresponding reasons that will be used in v1Beta2 API version for a DevMachine's docker backend.
+const (
+	// DevMachineDockerContainerProvisionedV1Beta2Condition documents the status of the provisioning of the container
+	// generated by a DevMachine's docker backend.
+	//
+	// NOTE as a difference from other providers, container provisioning and bootstrap are directly managed
+	// by the DevMachine's docker backend controller (not by cloud-init).
+	DevMachineDockerContainerProvisionedV1Beta2Condition string = "ContainerProvisioned"
+
+	// DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason documents the container for a DevMachine's docker backend waiting for the cluster
+	// infrastructure to be ready.
+	DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason = clusterv1.WaitingForClusterInfrastructureReadyV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForControlPlaneInitializedV1Beta2Reason documents a container for a DevMachine's docker backend waiting
+	// for the control plane to be initialized.
+	DevMachineDockerContainerWaitingForControlPlaneInitializedV1Beta2Reason = clusterv1.WaitingForControlPlaneInitializedV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForBootstrapDataV1Beta2Reason documents a container for a DevMachine's docker backend waiting for the bootstrap
+	// data to be ready.
+	DevMachineDockerContainerWaitingForBootstrapDataV1Beta2Reason = clusterv1.WaitingForBootstrapDataV1Beta2Reason
+
+	// DevMachineDockerContainerProvisionedV1Beta2Reason documents the container for a DevMachine's docker backend is provisioned.
+	DevMachineDockerContainerProvisionedV1Beta2Reason = clusterv1.ProvisionedV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason documents the container for a DevMachine's docker
path: test/infrastructure/docker/api/v1beta1/devmachine_types.go
user: login: sbueringer
body: ```suggestion
	// DevMachineDockerContainerNotProvisionedV1Beta2Reason documents the container for a DevMachine's docker
```
created_at: 2025-03-04T13:32:53Z
author_association: MEMBER
diff_hunk: @@ -85,6 +85,61 @@ const (
 	DevMachineReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
 )
 
+// DevMachine's v1Beta2 conditions that apply to the docker backend.
+
+// ContainerProvisioned condition and corresponding reasons that will be used in v1Beta2 API version for a DevMachine's docker backend.
+const (
+	// DevMachineDockerContainerProvisionedV1Beta2Condition documents the status of the provisioning of the container
+	// generated by a DevMachine's docker backend.
+	//
+	// NOTE as a difference from other providers, container provisioning and bootstrap are directly managed
+	// by the DevMachine's docker backend controller (not by cloud-init).
+	DevMachineDockerContainerProvisionedV1Beta2Condition string = "ContainerProvisioned"
+
+	// DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason documents the container for a DevMachine's docker backend waiting for the cluster
+	// infrastructure to be ready.
+	DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason = clusterv1.WaitingForClusterInfrastructureReadyV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForControlPlaneInitializedV1Beta2Reason documents a container for a DevMachine's docker backend waiting
+	// for the control plane to be initialized.
+	DevMachineDockerContainerWaitingForControlPlaneInitializedV1Beta2Reason = clusterv1.WaitingForControlPlaneInitializedV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForBootstrapDataV1Beta2Reason documents a container for a DevMachine's docker backend waiting for the bootstrap
+	// data to be ready.
+	DevMachineDockerContainerWaitingForBootstrapDataV1Beta2Reason = clusterv1.WaitingForBootstrapDataV1Beta2Reason
+
+	// DevMachineDockerContainerProvisionedV1Beta2Reason documents the container for a DevMachine's docker backend is provisioned.
+	DevMachineDockerContainerProvisionedV1Beta2Reason = clusterv1.ProvisionedV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason documents the container for a DevMachine's docker
+	// backend is not provisioned.
+	DevMachineDockerContainerNotProvisionedV1Beta2Reason = clusterv1.NotProvisionedV1Beta2Reason
+
+	// DevMachineDockerContainerDeletingV1Beta2Reason surfaces when the container for a DevMachine's docker
+	// backend is deleting.
+	DevMachineDockerContainerDeletingV1Beta2Reason = clusterv1.DeletingV1Beta2Reason
+)
+
+// BootstrapExecSucceeded condition and corresponding reasons that will be used in v1Beta2 API version for a DevMachine's docker backend.
+const (
+	// DockerMachineBootstrapExecSucceededV1Beta2Condition provides an observation of the DevMachine's docker backend bootstrap process.
+	// It is set based on successful execution of bootstrap commands and on the existence of
+	// the /run/cluster-api/bootstrap-success.complete file.
+	// The condition gets generated after ContainerProvisionedCondition is True.
path: test/infrastructure/docker/api/v1beta1/devmachine_types.go
user: login: sbueringer
body: ```suggestion
	// The condition gets generated after DevMachineDockerContainerProvisionedV1Beta2Condition is True.
```
I think
created_at: 2025-03-04T13:33:43Z
author_association: MEMBER
diff_hunk: @@ -85,6 +85,61 @@ const (
 	DevMachineReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
 )
 
+// DevMachine's v1Beta2 conditions that apply to the docker backend.
+
+// ContainerProvisioned condition and corresponding reasons that will be used in v1Beta2 API version for a DevMachine's docker backend.
+const (
+	// DevMachineDockerContainerProvisionedV1Beta2Condition documents the status of the provisioning of the container
+	// generated by a DevMachine's docker backend.
+	//
+	// NOTE as a difference from other providers, container provisioning and bootstrap are directly managed
+	// by the DevMachine's docker backend controller (not by cloud-init).
+	DevMachineDockerContainerProvisionedV1Beta2Condition string = "ContainerProvisioned"
+
+	// DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason documents the container for a DevMachine's docker backend waiting for the cluster
+	// infrastructure to be ready.
+	DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason = clusterv1.WaitingForClusterInfrastructureReadyV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForControlPlaneInitializedV1Beta2Reason documents a container for a DevMachine's docker backend waiting
+	// for the control plane to be initialized.
+	DevMachineDockerContainerWaitingForControlPlaneInitializedV1Beta2Reason = clusterv1.WaitingForControlPlaneInitializedV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForBootstrapDataV1Beta2Reason documents a container for a DevMachine's docker backend waiting for the bootstrap
+	// data to be ready.
+	DevMachineDockerContainerWaitingForBootstrapDataV1Beta2Reason = clusterv1.WaitingForBootstrapDataV1Beta2Reason
+
+	// DevMachineDockerContainerProvisionedV1Beta2Reason documents the container for a DevMachine's docker backend is provisioned.
+	DevMachineDockerContainerProvisionedV1Beta2Reason = clusterv1.ProvisionedV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason documents the container for a DevMachine's docker
+	// backend is not provisioned.
+	DevMachineDockerContainerNotProvisionedV1Beta2Reason = clusterv1.NotProvisionedV1Beta2Reason
+
+	// DevMachineDockerContainerDeletingV1Beta2Reason surfaces when the container for a DevMachine's docker
+	// backend is deleting.
+	DevMachineDockerContainerDeletingV1Beta2Reason = clusterv1.DeletingV1Beta2Reason
+)
+
+// BootstrapExecSucceeded condition and corresponding reasons that will be used in v1Beta2 API version for a DevMachine's docker backend.
+const (
+	// DockerMachineBootstrapExecSucceededV1Beta2Condition provides an observation of the DevMachine's docker backend bootstrap process.
+	// It is set based on successful execution of bootstrap commands and on the existence of
+	// the /run/cluster-api/bootstrap-success.complete file.
+	// The condition gets generated after ContainerProvisionedCondition is True.
+	//
+	// NOTE as a difference from other providers, container provisioning and bootstrap are directly managed
+	// by the DevMachine's docker backend controller (not by cloud-init).
+	DevMachineDockerBootstrapExecSucceededV1Beta2Condition string = "BootstrapExecSucceeded"
path: test/infrastructure/docker/api/v1beta1/devmachine_types.go
user: login: sbueringer
body: ```suggestion
	DevMachineDockerContainerBootstrapExecSucceededV1Beta2Condition string = "BootstrapExecSucceeded"
```
Maybe, all the ones above have that prefix. If yes, same for the two below
created_at: 2025-03-04T13:34:27Z
author_association: MEMBER
diff_hunk: @@ -85,6 +85,61 @@ const (
 	DevMachineReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
 )
 
+// DevMachine's v1Beta2 conditions that apply to the docker backend.
+
+// ContainerProvisioned condition and corresponding reasons that will be used in v1Beta2 API version for a DevMachine's docker backend.
+const (
+	// DevMachineDockerContainerProvisionedV1Beta2Condition documents the status of the provisioning of the container
+	// generated by a DevMachine's docker backend.
+	//
+	// NOTE as a difference from other providers, container provisioning and bootstrap are directly managed
+	// by the DevMachine's docker backend controller (not by cloud-init).
+	DevMachineDockerContainerProvisionedV1Beta2Condition string = "ContainerProvisioned"
+
+	// DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason documents the container for a DevMachine's docker backend waiting for the cluster
+	// infrastructure to be ready.
+	DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason = clusterv1.WaitingForClusterInfrastructureReadyV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForControlPlaneInitializedV1Beta2Reason documents a container for a DevMachine's docker backend waiting
+	// for the control plane to be initialized.
+	DevMachineDockerContainerWaitingForControlPlaneInitializedV1Beta2Reason = clusterv1.WaitingForControlPlaneInitializedV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForBootstrapDataV1Beta2Reason documents a container for a DevMachine's docker backend waiting for the bootstrap
+	// data to be ready.
+	DevMachineDockerContainerWaitingForBootstrapDataV1Beta2Reason = clusterv1.WaitingForBootstrapDataV1Beta2Reason
+
+	// DevMachineDockerContainerProvisionedV1Beta2Reason documents the container for a DevMachine's docker backend is provisioned.
+	DevMachineDockerContainerProvisionedV1Beta2Reason = clusterv1.ProvisionedV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason documents the container for a DevMachine's docker
+	// backend is not provisioned.
+	DevMachineDockerContainerNotProvisionedV1Beta2Reason = clusterv1.NotProvisionedV1Beta2Reason
+
+	// DevMachineDockerContainerDeletingV1Beta2Reason surfaces when the container for a DevMachine's docker
+	// backend is deleting.
+	DevMachineDockerContainerDeletingV1Beta2Reason = clusterv1.DeletingV1Beta2Reason
+)
+
+// BootstrapExecSucceeded condition and corresponding reasons that will be used in v1Beta2 API version for a DevMachine's docker backend.
+const (
+	// DockerMachineBootstrapExecSucceededV1Beta2Condition provides an observation of the DevMachine's docker backend bootstrap process.
path: test/infrastructure/docker/api/v1beta1/devmachine_types.go
user: login: sbueringer
body: mismatch with const name
created_at: 2025-03-04T13:34:46Z
author_association: MEMBER
diff_hunk: @@ -85,6 +85,61 @@ const (
 	DevMachineReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
 )
 
+// DevMachine's v1Beta2 conditions that apply to the docker backend.
+
+// ContainerProvisioned condition and corresponding reasons that will be used in v1Beta2 API version for a DevMachine's docker backend.
+const (
+	// DevMachineDockerContainerProvisionedV1Beta2Condition documents the status of the provisioning of the container
+	// generated by a DevMachine's docker backend.
+	//
+	// NOTE as a difference from other providers, container provisioning and bootstrap are directly managed
+	// by the DevMachine's docker backend controller (not by cloud-init).
+	DevMachineDockerContainerProvisionedV1Beta2Condition string = "ContainerProvisioned"
+
+	// DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason documents the container for a DevMachine's docker backend waiting for the cluster
+	// infrastructure to be ready.
+	DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason = clusterv1.WaitingForClusterInfrastructureReadyV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForControlPlaneInitializedV1Beta2Reason documents a container for a DevMachine's docker backend waiting
+	// for the control plane to be initialized.
+	DevMachineDockerContainerWaitingForControlPlaneInitializedV1Beta2Reason = clusterv1.WaitingForControlPlaneInitializedV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForBootstrapDataV1Beta2Reason documents a container for a DevMachine's docker backend waiting for the bootstrap
+	// data to be ready.
+	DevMachineDockerContainerWaitingForBootstrapDataV1Beta2Reason = clusterv1.WaitingForBootstrapDataV1Beta2Reason
+
+	// DevMachineDockerContainerProvisionedV1Beta2Reason documents the container for a DevMachine's docker backend is provisioned.
+	DevMachineDockerContainerProvisionedV1Beta2Reason = clusterv1.ProvisionedV1Beta2Reason
+
+	// DevMachineDockerContainerWaitingForClusterInfrastructureV1Beta2Reason documents the container for a DevMachine's docker
+	// backend is not provisioned.
+	DevMachineDockerContainerNotProvisionedV1Beta2Reason = clusterv1.NotProvisionedV1Beta2Reason
+
+	// DevMachineDockerContainerDeletingV1Beta2Reason surfaces when the container for a DevMachine's docker
+	// backend is deleting.
+	DevMachineDockerContainerDeletingV1Beta2Reason = clusterv1.DeletingV1Beta2Reason
+)
+
+// BootstrapExecSucceeded condition and corresponding reasons that will be used in v1Beta2 API version for a DevMachine's docker backend.
+const (
+	// DockerMachineBootstrapExecSucceededV1Beta2Condition provides an observation of the DevMachine's docker backend bootstrap process.
+	// It is set based on successful execution of bootstrap commands and on the existence of
+	// the /run/cluster-api/bootstrap-success.complete file.
+	// The condition gets generated after ContainerProvisionedCondition is True.
+	//
+	// NOTE as a difference from other providers, container provisioning and bootstrap are directly managed
+	// by the DevMachine's docker backend controller (not by cloud-init).
+	DevMachineDockerBootstrapExecSucceededV1Beta2Condition string = "BootstrapExecSucceeded"
+
+	// DevMachineDockerBootstrapExecSucceededV1Beta2Reason documents the container for a DevMachine's docker backend having
+	// completed bootstrap exec commands.
+	DevMachineDockerBootstrapExecSucceededV1Beta2Reason string = "Succeeded"
+
+	// DevMachineDockerBootstrapExecSucceededV1Beta2Reason documents the container for a DevMachine's docker backend not having
path: test/infrastructure/docker/api/v1beta1/devmachine_types.go
user: login: sbueringer
body: ```suggestion
	// DevMachineDockerBootstrapExecNotSucceededV1Beta2Reason documents the container for a DevMachine's docker backend not having
```
created_at: 2025-03-04T13:35:21Z
author_association: MEMBER
diff_hunk: @@ -28,6 +28,46 @@ const (
 	ListenerAnnotationName = "inmemorycluster.infrastructure.cluster.x-k8s.io/listener"
 )
 
+// DevCluster's v1Beta2 conditions that apply to all the supported backends.
+
+// DevCluster's Ready condition and corresponding reasons that will be used in v1Beta2 API version.
+const (
+	// DevClusterReadyV1Beta2Condition is true if the DevCluster's VMProvisioned, NodeProvisioned,
+	// EtcdProvisioned (if present) and APIServerProvisioned (if present) conditions are true.
+	DevClusterReadyV1Beta2Condition = clusterv1.ReadyV1Beta2Condition
+
+	// DevClusterReadyV1Beta2Reason surfaces when the DevCluster readiness criteria is met.
+	DevClusterReadyV1Beta2Reason = clusterv1.ReadyV1Beta2Reason
+
+	// DevClusterNotReadyV1Beta2Reason surfaces when the DevCluster readiness criteria is not met.
+	DevClusterNotReadyV1Beta2Reason = clusterv1.NotReadyV1Beta2Reason
+
+	// DevClusterReadyUnknownV1Beta2Reason surfaces when at least one DevCluster readiness criteria is unknown
+	// and no DevCluster readiness criteria is not met.
+	DevClusterReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
path: test/infrastructure/docker/api/v1beta1/devcluster_types.go
user: login: sbueringer
body: I think the idea was to use these 3 in dockercluster_backend.go l.177+
created_at: 2025-03-04T13:40:33Z
author_association: MEMBER
diff_hunk: @@ -118,12 +121,24 @@ func (r *MachineBackendReconciler) ReconcileNormal(ctx context.Context, cluster
 
 		if externalMachine.Exists() {
 			conditions.MarkTrue(dockerMachine, infrav1.ContainerProvisionedCondition)
+			v1beta2conditions.Set(dockerMachine, metav1.Condition{
+				Type:   infrav1.DevMachineDockerContainerProvisionedV1Beta2Condition,
+				Status: metav1.ConditionTrue,
+				Reason: infrav1.DevMachineDockerContainerProvisionedV1Beta2Reason,
+			})
+
 			// Setting machine address is required after move, because status.Address field is not retained during move.
 			if err := setMachineAddress(ctx, dockerMachine, externalMachine); err != nil {
 				return ctrl.Result{}, errors.Wrap(err, "failed to set the machine address")
 			}
 		} else {
 			conditions.MarkFalse(dockerMachine, infrav1.ContainerProvisionedCondition, infrav1.ContainerDeletedReason, clusterv1.ConditionSeverityError, fmt.Sprintf("Container %s does not exists anymore", externalMachine.Name()))
+			v1beta2conditions.Set(dockerMachine, metav1.Condition{
+				Type:    infrav1.DevMachineDockerContainerProvisionedV1Beta2Condition,
+				Status:  metav1.ConditionFalse,
+				Reason:  infrav1.DevMachineDockerContainerNotProvisionedV1Beta2Reason,
+				Message: fmt.Sprintf("Container %s does not exists anymore", externalMachine.Name()),
path: test/infrastructure/docker/internal/controllers/backends/docker/dockermachine_backend.go
user: login: sbueringer
body: ```suggestion
				Message: fmt.Sprintf("Container %s does not exist anymore", externalMachine.Name()),
```
Same in l.135
created_at: 2025-03-04T13:43:55Z
author_association: MEMBER
diff_hunk: @@ -254,12 +275,24 @@ func (r *MachineBackendReconciler) ReconcileNormal(ctx context.Context, cluster
 			// Run the bootstrap script. Simulates cloud-init/Ignition.
 			if err := externalMachine.ExecBootstrap(timeoutCtx, bootstrapData, format, version, dockerMachine.Spec.Backend.Docker.CustomImage); err != nil {
 				conditions.MarkFalse(dockerMachine, infrav1.BootstrapExecSucceededCondition, infrav1.BootstrapFailedReason, clusterv1.ConditionSeverityWarning, "Repeating bootstrap")
+				v1beta2conditions.Set(dockerMachine, metav1.Condition{
+					Type:    infrav1.DevMachineDockerBootstrapExecSucceededV1Beta2Condition,
+					Status:  metav1.ConditionFalse,
+					Reason:  infrav1.DevMachineDockerBootstrapExecNotSucceededV1Beta2Reason,
+					Message: "Failed to exec DockerMachine bootstrap",
path: test/infrastructure/docker/internal/controllers/backends/docker/dockermachine_backend.go
user: login: sbueringer
body: Do we want to include the error here and below? (like we did in some other cases in this PR)
created_at: 2025-03-04T13:48:41Z
author_association: MEMBER
diff_hunk: @@ -362,22 +399,13 @@ func (r *MachineBackendReconciler) ReconcileDelete(ctx context.Context, cluster
 	// this visible to the users.
 	// NB. The operation in docker is fast, so there is the chance the user will not notice the status change;
 	// nevertheless we are issuing a patch so we can test a pattern that will be used by other providers as well
-	newPatchHelperFunc := r.NewPatchHelperFunc
-	if newPatchHelperFunc == nil {
-		newPatchHelperFunc = patch.NewHelper
-	}
-	patchHelper, err := newPatchHelperFunc(dockerMachine, r.Client)
-	if err != nil {
-		return ctrl.Result{}, err
-	}
-	conditions.MarkFalse(dockerMachine, infrav1.ContainerProvisionedCondition, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
-
-	patchDevMachineFunc := r.PatchDevMachineFunc
-	if patchDevMachineFunc == nil {
-		patchDevMachineFunc = r.PatchDevMachine
-	}
-	if err := patchDevMachineFunc(ctx, patchHelper, dockerMachine, util.IsControlPlaneMachine(machine)); err != nil {
-		return ctrl.Result{}, errors.Wrap(err, "failed to patch DockerMachine")
+	if conditions.GetReason(dockerMachine, infrav1.ContainerProvisionedCondition) != clusterv1.DeletingReason {
path: test/infrastructure/docker/internal/controllers/backends/docker/dockermachine_backend.go
user: login: sbueringer
body: I wonder if it would be better to check for status false here instead of reason != ""
created_at: 2025-03-04T13:50:20Z
author_association: MEMBER
diff_hunk: @@ -173,6 +159,24 @@ func patchDockerCluster(ctx context.Context, patchHelper *patch.Helper, dockerCl
 		),
 		conditions.WithStepCounterIf(dockerCluster.ObjectMeta.DeletionTimestamp.IsZero()),
 	)
+	if err := v1beta2conditions.SetSummaryCondition(dockerCluster, dockerCluster, infrav1.DevClusterReadyV1Beta2Condition,
+		v1beta2conditions.ForConditionTypes{
+			infrav1.DockerClusterDockerLoadBalancerAvailableV1Beta2Condition,
+		},
+		// Using a custom merge strategy to override reasons applied during merge.
+		v1beta2conditions.CustomMergeStrategy{
+			MergeStrategy: v1beta2conditions.DefaultMergeStrategy(
+				// Use custom reasons.
+				v1beta2conditions.ComputeReasonFunc(v1beta2conditions.GetDefaultComputeMergeReasonFunc(
+					infrav1.DevMachineNotReadyV1Beta2Reason,
+					infrav1.DevMachineReadyUnknownV1Beta2Reason,
+					infrav1.DevMachineReadyV1Beta2Reason,
path: test/infrastructure/docker/internal/controllers/dockercluster_controller.go
user: login: sbueringer
body: Should be DevCluster*
created_at: 2025-03-04T13:53:01Z
author_association: MEMBER
diff_hunk: @@ -152,6 +174,24 @@ func (r *ClusterBackEndReconciler) PatchDevCluster(ctx context.Context, patchHel
 		),
 		conditions.WithStepCounterIf(dockerCluster.ObjectMeta.DeletionTimestamp.IsZero()),
 	)
+	if err := v1beta2conditions.SetSummaryCondition(dockerCluster, dockerCluster, infrav1.DevClusterReadyV1Beta2Condition,
+		v1beta2conditions.ForConditionTypes{
+			infrav1.DockerClusterDockerLoadBalancerAvailableV1Beta2Condition,
+		},
+		// Using a custom merge strategy to override reasons applied during merge.
+		v1beta2conditions.CustomMergeStrategy{
+			MergeStrategy: v1beta2conditions.DefaultMergeStrategy(
+				// Use custom reasons.
+				v1beta2conditions.ComputeReasonFunc(v1beta2conditions.GetDefaultComputeMergeReasonFunc(
+					infrav1.DevMachineNotReadyV1Beta2Reason,
path: test/infrastructure/docker/internal/controllers/backends/docker/dockercluster_backend.go
user: login: sbueringer
body: Should be DevCluster*
created_at: 2025-03-04T13:53:58Z
author_association: MEMBER
diff_hunk: @@ -19,9 +19,12 @@ package docker
 
 import (
path: test/infrastructure/docker/internal/controllers/backends/docker/dockercluster_backend.go
user: login: sbueringer
body: Just to double check

We neither have nor need any condition for the in-memory backend of DevCluster, right?
created_at: 2025-03-04T13:57:09Z
author_association: MEMBER
diff_hunk: @@ -209,4 +209,7 @@ const (
 
path: api/v1beta1/v1beta2_condition_consts.go
user: login: sbueringer
body: I think the PR title doesn't cover the entire PR (DevCluster conditions)
created_at: 2025-03-04T13:57:28Z
author_association: MEMBER
diff_hunk: @@ -28,6 +28,46 @@ const (
 	ListenerAnnotationName = "inmemorycluster.infrastructure.cluster.x-k8s.io/listener"
 )
 
+// DevCluster's v1Beta2 conditions that apply to all the supported backends.
+
+// DevCluster's Ready condition and corresponding reasons that will be used in v1Beta2 API version.
+const (
+	// DevClusterReadyV1Beta2Condition is true if the DevCluster's VMProvisioned, NodeProvisioned,
+	// EtcdProvisioned (if present) and APIServerProvisioned (if present) conditions are true.
+	DevClusterReadyV1Beta2Condition = clusterv1.ReadyV1Beta2Condition
+
+	// DevClusterReadyV1Beta2Reason surfaces when the DevCluster readiness criteria is met.
+	DevClusterReadyV1Beta2Reason = clusterv1.ReadyV1Beta2Reason
+
+	// DevClusterNotReadyV1Beta2Reason surfaces when the DevCluster readiness criteria is not met.
+	DevClusterNotReadyV1Beta2Reason = clusterv1.NotReadyV1Beta2Reason
+
+	// DevClusterReadyUnknownV1Beta2Reason surfaces when at least one DevCluster readiness criteria is unknown
+	// and no DevCluster readiness criteria is not met.
+	DevClusterReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
path: test/infrastructure/docker/api/v1beta1/devcluster_types.go
user: login: fabriziopandini
body: Fixed
created_at: 2025-03-04T15:50:25Z
author_association: MEMBER
diff_hunk: @@ -28,6 +28,46 @@ const (
 	ListenerAnnotationName = "inmemorycluster.infrastructure.cluster.x-k8s.io/listener"
 )
 
+// DevCluster's v1Beta2 conditions that apply to all the supported backends.
+
+// DevCluster's Ready condition and corresponding reasons that will be used in v1Beta2 API version.
+const (
+	// DevClusterReadyV1Beta2Condition is true if the DevCluster's VMProvisioned, NodeProvisioned,
+	// EtcdProvisioned (if present) and APIServerProvisioned (if present) conditions are true.
path: test/infrastructure/docker/api/v1beta1/devcluster_types.go
user: login: fabriziopandini
body: Fixed also for DevMachine
created_at: 2025-03-04T15:53:35Z
author_association: MEMBER
diff_hunk: @@ -254,12 +275,24 @@ func (r *MachineBackendReconciler) ReconcileNormal(ctx context.Context, cluster
 			// Run the bootstrap script. Simulates cloud-init/Ignition.
 			if err := externalMachine.ExecBootstrap(timeoutCtx, bootstrapData, format, version, dockerMachine.Spec.Backend.Docker.CustomImage); err != nil {
 				conditions.MarkFalse(dockerMachine, infrav1.BootstrapExecSucceededCondition, infrav1.BootstrapFailedReason, clusterv1.ConditionSeverityWarning, "Repeating bootstrap")
+				v1beta2conditions.Set(dockerMachine, metav1.Condition{
+					Type:    infrav1.DevMachineDockerBootstrapExecSucceededV1Beta2Condition,
+					Status:  metav1.ConditionFalse,
+					Reason:  infrav1.DevMachineDockerBootstrapExecNotSucceededV1Beta2Reason,
+					Message: "Failed to exec DockerMachine bootstrap",
path: test/infrastructure/docker/internal/controllers/backends/docker/dockermachine_backend.go
user: login: fabriziopandini
body: I mirrored what was implemented for existing conditions.
We can eventually follow up 
created_at: 2025-03-04T16:11:22Z
author_association: MEMBER
diff_hunk: @@ -362,22 +399,13 @@ func (r *MachineBackendReconciler) ReconcileDelete(ctx context.Context, cluster
 	// this visible to the users.
 	// NB. The operation in docker is fast, so there is the chance the user will not notice the status change;
 	// nevertheless we are issuing a patch so we can test a pattern that will be used by other providers as well
-	newPatchHelperFunc := r.NewPatchHelperFunc
-	if newPatchHelperFunc == nil {
-		newPatchHelperFunc = patch.NewHelper
-	}
-	patchHelper, err := newPatchHelperFunc(dockerMachine, r.Client)
-	if err != nil {
-		return ctrl.Result{}, err
-	}
-	conditions.MarkFalse(dockerMachine, infrav1.ContainerProvisionedCondition, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
-
-	patchDevMachineFunc := r.PatchDevMachineFunc
-	if patchDevMachineFunc == nil {
-		patchDevMachineFunc = r.PatchDevMachine
-	}
-	if err := patchDevMachineFunc(ctx, patchHelper, dockerMachine, util.IsControlPlaneMachine(machine)); err != nil {
-		return ctrl.Result{}, errors.Wrap(err, "failed to patch DockerMachine")
+	if conditions.GetReason(dockerMachine, infrav1.ContainerProvisionedCondition) != clusterv1.DeletingReason {
path: test/infrastructure/docker/internal/controllers/backends/docker/dockermachine_backend.go
user: login: fabriziopandini
body: The goal of this if is to surface deleting is started, and we should do this if the condition is reporting something different than deleting
created_at: 2025-03-04T16:14:26Z
author_association: MEMBER
diff_hunk: @@ -19,9 +19,12 @@ package docker
 
 import (
path: test/infrastructure/docker/internal/controllers/backends/docker/dockercluster_backend.go
user: login: fabriziopandini
body: Yes, current state is that we don't have any (we never invested effort in adding some condition)
created_at: 2025-03-04T16:15:55Z
author_association: MEMBER
diff_hunk: @@ -254,12 +275,24 @@ func (r *MachineBackendReconciler) ReconcileNormal(ctx context.Context, cluster
 			// Run the bootstrap script. Simulates cloud-init/Ignition.
 			if err := externalMachine.ExecBootstrap(timeoutCtx, bootstrapData, format, version, dockerMachine.Spec.Backend.Docker.CustomImage); err != nil {
 				conditions.MarkFalse(dockerMachine, infrav1.BootstrapExecSucceededCondition, infrav1.BootstrapFailedReason, clusterv1.ConditionSeverityWarning, "Repeating bootstrap")
+				v1beta2conditions.Set(dockerMachine, metav1.Condition{
+					Type:    infrav1.DevMachineDockerBootstrapExecSucceededV1Beta2Condition,
+					Status:  metav1.ConditionFalse,
+					Reason:  infrav1.DevMachineDockerBootstrapExecNotSucceededV1Beta2Reason,
+					Message: "Failed to exec DockerMachine bootstrap",
path: test/infrastructure/docker/internal/controllers/backends/docker/dockermachine_backend.go
user: login: sbueringer
body: Yeah was just wondering. I think in the other cases it wasn't mirrored. But I don't mind. Resolving
created_at: 2025-03-04T17:42:26Z
author_association: MEMBER
diff_hunk: @@ -362,22 +399,13 @@ func (r *MachineBackendReconciler) ReconcileDelete(ctx context.Context, cluster
 	// this visible to the users.
 	// NB. The operation in docker is fast, so there is the chance the user will not notice the status change;
 	// nevertheless we are issuing a patch so we can test a pattern that will be used by other providers as well
-	newPatchHelperFunc := r.NewPatchHelperFunc
-	if newPatchHelperFunc == nil {
-		newPatchHelperFunc = patch.NewHelper
-	}
-	patchHelper, err := newPatchHelperFunc(dockerMachine, r.Client)
-	if err != nil {
-		return ctrl.Result{}, err
-	}
-	conditions.MarkFalse(dockerMachine, infrav1.ContainerProvisionedCondition, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
-
-	patchDevMachineFunc := r.PatchDevMachineFunc
-	if patchDevMachineFunc == nil {
-		patchDevMachineFunc = r.PatchDevMachine
-	}
-	if err := patchDevMachineFunc(ctx, patchHelper, dockerMachine, util.IsControlPlaneMachine(machine)); err != nil {
-		return ctrl.Result{}, errors.Wrap(err, "failed to patch DockerMachine")
+	if conditions.GetReason(dockerMachine, infrav1.ContainerProvisionedCondition) != clusterv1.DeletingReason {
path: test/infrastructure/docker/internal/controllers/backends/docker/dockermachine_backend.go
user: login: sbueringer
body: I think I misplaced this comment
created_at: 2025-03-04T19:01:06Z
author_association: MEMBER
diff_hunk: @@ -362,22 +399,13 @@ func (r *MachineBackendReconciler) ReconcileDelete(ctx context.Context, cluster
 	// this visible to the users.
 	// NB. The operation in docker is fast, so there is the chance the user will not notice the status change;
 	// nevertheless we are issuing a patch so we can test a pattern that will be used by other providers as well
-	newPatchHelperFunc := r.NewPatchHelperFunc
-	if newPatchHelperFunc == nil {
-		newPatchHelperFunc = patch.NewHelper
-	}
-	patchHelper, err := newPatchHelperFunc(dockerMachine, r.Client)
-	if err != nil {
-		return ctrl.Result{}, err
-	}
-	conditions.MarkFalse(dockerMachine, infrav1.ContainerProvisionedCondition, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
-
-	patchDevMachineFunc := r.PatchDevMachineFunc
-	if patchDevMachineFunc == nil {
-		patchDevMachineFunc = r.PatchDevMachine
-	}
-	if err := patchDevMachineFunc(ctx, patchHelper, dockerMachine, util.IsControlPlaneMachine(machine)); err != nil {
-		return ctrl.Result{}, errors.Wrap(err, "failed to patch DockerMachine")
+	if conditions.GetReason(dockerMachine, infrav1.ContainerProvisionedCondition) != clusterv1.DeletingReason {
path: test/infrastructure/docker/internal/controllers/backends/docker/dockermachine_backend.go
user: login: sbueringer
body: I'll reopen at the correct location
created_at: 2025-03-04T19:03:22Z
author_association: MEMBER
diff_hunk: @@ -179,32 +204,28 @@ func (r *MachineBackendReconciler) ReconcileNormal(ctx context.Context, cluster
 		dockerMachine.Status.Backend.Docker.LoadBalancerConfigured = true
 	}
 
-	// Update the ContainerProvisionedCondition condition
-	// NOTE: it is required to create the patch helper before this change otherwise it wont surface if
-	// we issue a patch down in the code (because if we create patch helper after this point the ContainerProvisionedCondition=True exists both on before and after).
-	newPatchHelperFunc := r.NewPatchHelperFunc
-	if newPatchHelperFunc == nil {
-		newPatchHelperFunc = patch.NewHelper
+	// Update the ContainerProvisioned and BootstrapExecSucceeded condition if not already in the correct state.
+	requeue := false
+	if conditions.GetReason(dockerMachine, infrav1.ContainerProvisionedCondition) != "" {
path: test/infrastructure/docker/internal/controllers/backends/docker/dockermachine_backend.go
user: login: sbueringer
body: I wonder if it would be better to check for status false here instead of reason != ""

(that's where I actually wanted to put this comment :))
created_at: 2025-03-04T19:04:02Z
author_association: MEMBER
diff_hunk: @@ -179,32 +204,28 @@ func (r *MachineBackendReconciler) ReconcileNormal(ctx context.Context, cluster
 		dockerMachine.Status.Backend.Docker.LoadBalancerConfigured = true
 	}
 
-	// Update the ContainerProvisionedCondition condition
-	// NOTE: it is required to create the patch helper before this change otherwise it wont surface if
-	// we issue a patch down in the code (because if we create patch helper after this point the ContainerProvisionedCondition=True exists both on before and after).
-	newPatchHelperFunc := r.NewPatchHelperFunc
-	if newPatchHelperFunc == nil {
-		newPatchHelperFunc = patch.NewHelper
+	// Update the ContainerProvisioned and BootstrapExecSucceeded condition if not already in the correct state.
+	requeue := false
+	if conditions.GetReason(dockerMachine, infrav1.ContainerProvisionedCondition) != "" {
path: test/infrastructure/docker/internal/controllers/backends/docker/dockermachine_backend.go
user: login: sbueringer
body: Just feels less brittle to say. If this conditions is false, let's set it to true.

Compared to: Let's assume if the reason is not empty, then the condition is false, so let's set it to true

(If I'm not missing anything)
created_at: 2025-03-04T19:04:55Z
author_association: MEMBER

PR comments:
user: login: sbueringer
created_at: 2025-03-04T19:14:20Z
body: Thank you very much!!

/lgtm
/approve

/test pull-cluster-api-e2e-main
author_association: MEMBER
user: login: k8s-ci-robot
created_at: 2025-03-04T19:14:26Z
body: LGTM label has been added.  <details>Git tree hash: dfc8b4d9cdf40a12ac48c23664c45892a9cb83b3</details>
author_association: CONTRIBUTOR
user: login: k8s-ci-robot
created_at: 2025-03-04T19:14:28Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/cluster-api/pull/11923#issuecomment-2698663645" title="Approved">sbueringer</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/cluster-api/blob/main/OWNERS)~~ [sbueringer]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR