Repository: syself/cluster-api-provider-hetzner
Pull request #1412 (2024-08-01 06:55:08)

:seedling: re-enable bm server  bm-2 (1716772)
**What this PR does / why we need it**:

> Dear Client,
> We have replaced the defective SATA SSDs and started the server afterwards into the Linux rescue system

hardware was replaced. Rescue system works (tested by hand),



Patch:
diff --git a/api/v1beta1/hetznerbaremetalhost_types.go b/api/v1beta1/hetznerbaremetalhost_types.go
index 4b87f9d8..47be9682 100644
--- a/api/v1beta1/hetznerbaremetalhost_types.go
+++ b/api/v1beta1/hetznerbaremetalhost_types.go
@@ -152,9 +152,6 @@ const (
 	// StateImageInstalling means we install a new image.
 	StateImageInstalling ProvisioningState = "image-installing"
 
-	// StateProvisioning means we are sending userData to the host and booting the machine.
-	StateProvisioning ProvisioningState = "provisioning"
-
 	// StateEnsureProvisioned means we are ensuring the reboot worked and cloud-init was executed successfully.
 	StateEnsureProvisioned ProvisioningState = "ensure-provisioned"
 
diff --git a/api/v1beta1/hetznerbaremetalmachine_types.go b/api/v1beta1/hetznerbaremetalmachine_types.go
index 2952a96f..42b40760 100644
--- a/api/v1beta1/hetznerbaremetalmachine_types.go
+++ b/api/v1beta1/hetznerbaremetalmachine_types.go
@@ -149,7 +149,7 @@ type InstallImage struct {
 	// Image is the image to be provisioned. It defines the image for baremetal machine.
 	Image Image `json:"image"`
 
-	// PostInstallScript is used for configuring commands that should be executed after installimage.
+	// PostInstallScript (Bash) is used for configuring commands that should be executed after installimage.
 	// It is passed along with the installimage command.
 	PostInstallScript string `json:"postInstallScript,omitempty"`
 
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_hetznerbaremetalhosts.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_hetznerbaremetalhosts.yaml
index 45a71431..5317afa2 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_hetznerbaremetalhosts.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_hetznerbaremetalhosts.yaml
@@ -441,7 +441,7 @@ spec:
                         type: array
                       postInstallScript:
                         description: |-
-                          PostInstallScript is used for configuring commands that should be executed after installimage.
+                          PostInstallScript (Bash) is used for configuring commands that should be executed after installimage.
                           It is passed along with the installimage command.
                         type: string
                       swraid:
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_hetznerbaremetalmachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_hetznerbaremetalmachines.yaml
index 83402f08..c29e06e4 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_hetznerbaremetalmachines.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_hetznerbaremetalmachines.yaml
@@ -220,7 +220,7 @@ spec:
                     type: array
                   postInstallScript:
                     description: |-
-                      PostInstallScript is used for configuring commands that should be executed after installimage.
+                      PostInstallScript (Bash) is used for configuring commands that should be executed after installimage.
                       It is passed along with the installimage command.
                     type: string
                   swraid:
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_hetznerbaremetalmachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_hetznerbaremetalmachinetemplates.yaml
index c45afa75..9625e4c7 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_hetznerbaremetalmachinetemplates.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_hetznerbaremetalmachinetemplates.yaml
@@ -207,7 +207,7 @@ spec:
                             type: array
                           postInstallScript:
                             description: |-
-                              PostInstallScript is used for configuring commands that should be executed after installimage.
+                              PostInstallScript (Bash) is used for configuring commands that should be executed after installimage.
                               It is passed along with the installimage command.
                             type: string
                           swraid:
diff --git a/controllers/hetznerbaremetalhost_controller.go b/controllers/hetznerbaremetalhost_controller.go
index 92579bc7..0965bc4e 100644
--- a/controllers/hetznerbaremetalhost_controller.go
+++ b/controllers/hetznerbaremetalhost_controller.go
@@ -66,7 +66,6 @@ type HetznerBareMetalHostReconciler struct {
 // Reconcile implements the reconcilement of HetznerBareMetalHost objects.
 func (r *HetznerBareMetalHostReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, reterr error) {
 	log := ctrl.LoggerFrom(ctx)
-
 	// Fetch the Hetzner bare metal host instance.
 	bmHost := &infrav1.HetznerBareMetalHost{}
 	err := r.Get(ctx, req.NamespacedName, bmHost)
@@ -77,8 +76,6 @@ func (r *HetznerBareMetalHostReconciler) Reconcile(ctx context.Context, req ctrl
 		return reconcile.Result{}, err
 	}
 
-	log = log.WithValues("HetznerBareMetalHost", klog.KObj(bmHost))
-
 	// Add a finalizer to newly created objects.
 	if bmHost.DeletionTimestamp.IsZero() && !hostHasFinalizer(bmHost) {
 		bmHost.Finalizers = append(bmHost.Finalizers,
diff --git a/hack/filter-caph-controller-manager-logs.py b/hack/filter-caph-controller-manager-logs.py
index dfa936fa..4e618092 100755
--- a/hack/filter-caph-controller-manager-logs.py
+++ b/hack/filter-caph-controller-manager-logs.py
@@ -29,14 +29,24 @@ rows_to_skip = [
     'Registering a mutating webhook', 'Starting EventSource',
     'Starting Controller',
     '"Starting workers" controller/controller',
+    '"starting manager"',
+    '"Starting metrics server"',
+    '"starting server"',
     '"Reconciling finished"',
     '"Creating cluster scope"',
     '"Starting reconciling cluster"',
     '"Completed function"',
     '"Adding request."',
+    '"Serving metrics server"',
+    '"Starting webhook server"',
+    '"Serving webhook server"',
+    'attempting to acquire leader lease caph-system/hetzner.cluster.x-k8s.io.',
     'Update to resource only changes insignificant fields',
     '"approved csr"',
     '"Registering webhook"',
+    '"Stopping and waiting for',
+    '"All workers finished"',
+    '"Shutdown signal received, waiting for all workers to finish"',
     "'statusCode': 200, 'method': 'GET', 'url': 'https://robot-ws.your-server.de",
 ]
 
diff --git a/pkg/services/baremetal/baremetal/baremetal.go b/pkg/services/baremetal/baremetal/baremetal.go
index 8b835f8a..ba619fa5 100644
--- a/pkg/services/baremetal/baremetal/baremetal.go
+++ b/pkg/services/baremetal/baremetal/baremetal.go
@@ -504,7 +504,7 @@ func (s *Service) skipHost(labelSelector labels.Selector, host infrav1.HetznerBa
 		// Machine should have RAID. Skip machines which have less than two WWNs
 		lenOfWwnSlice := len(host.Spec.RootDeviceHints.Raid.WWN)
 		if lenOfWwnSlice < 2 {
-			mapOfSkipReasons[fmt.Sprintf("machine-should-use-swraid-but-only-%d-RAID-WWN-in-hbmh", lenOfWwnSlice)]++
+			mapOfSkipReasons["machine-should-use-swraid-but-not-enough-RAID-WWNs-in-hbmh"]++
 			return true
 		}
 		return false
diff --git a/pkg/services/baremetal/client/mocks/ssh/Client.go b/pkg/services/baremetal/client/mocks/ssh/Client.go
index db669999..f1590312 100644
--- a/pkg/services/baremetal/client/mocks/ssh/Client.go
+++ b/pkg/services/baremetal/client/mocks/ssh/Client.go
@@ -246,97 +246,6 @@ func (_c *Client_CreateAutoSetup_Call) RunAndReturn(run func(string) sshclient.O
 	return _c
 }
 
-// CreateMetaData provides a mock function with given fields: hostName
-func (_m *Client) CreateMetaData(hostName string) sshclient.Output {
-	ret := _m.Called(hostName)
-
-	if len(ret) == 0 {
-		panic("no return value specified for CreateMetaData")
-	}
-
-	var r0 sshclient.Output
-	if rf, ok := ret.Get(0).(func(string) sshclient.Output); ok {
-		r0 = rf(hostName)
-	} else {
-		r0 = ret.Get(0).(sshclient.Output)
-	}
-
-	return r0
-}
-
-// Client_CreateMetaData_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateMetaData'
-type Client_CreateMetaData_Call struct {
-	*mock.Call
-}
-
-// CreateMetaData is a helper method to define mock.On call
-//   - hostName string
-func (_e *Client_Expecter) CreateMetaData(hostName interface{}) *Client_CreateMetaData_Call {
-	return &Client_CreateMetaData_Call{Call: _e.mock.On("CreateMetaData", hostName)}
-}
-
-func (_c *Client_CreateMetaData_Call) Run(run func(hostName string)) *Client_CreateMetaData_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(string))
-	})
-	return _c
-}
-
-func (_c *Client_CreateMetaData_Call) Return(_a0 sshclient.Output) *Client_CreateMetaData_Call {
-	_c.Call.Return(_a0)
-	return _c
-}
-
-func (_c *Client_CreateMetaData_Call) RunAndReturn(run func(string) sshclient.Output) *Client_CreateMetaData_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// CreateNoCloudDirectory provides a mock function with given fields:
-func (_m *Client) CreateNoCloudDirectory() sshclient.Output {
-	ret := _m.Called()
-
-	if len(ret) == 0 {
-		panic("no return value specified for CreateNoCloudDirectory")
-	}
-
-	var r0 sshclient.Output
-	if rf, ok := ret.Get(0).(func() sshclient.Output); ok {
-		r0 = rf()
-	} else {
-		r0 = ret.Get(0).(sshclient.Output)
-	}
-
-	return r0
-}
-
-// Client_CreateNoCloudDirectory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateNoCloudDirectory'
-type Client_CreateNoCloudDirectory_Call struct {
-	*mock.Call
-}
-
-// CreateNoCloudDirectory is a helper method to define mock.On call
-func (_e *Client_Expecter) CreateNoCloudDirectory() *Client_CreateNoCloudDirectory_Call {
-	return &Client_CreateNoCloudDirectory_Call{Call: _e.mock.On("CreateNoCloudDirectory")}
-}
-
-func (_c *Client_CreateNoCloudDirectory_Call) Run(run func()) *Client_CreateNoCloudDirectory_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		run()
-	})
-	return _c
-}
-
-func (_c *Client_CreateNoCloudDirectory_Call) Return(_a0 sshclient.Output) *Client_CreateNoCloudDirectory_Call {
-	_c.Call.Return(_a0)
-	return _c
-}
-
-func (_c *Client_CreateNoCloudDirectory_Call) RunAndReturn(run func() sshclient.Output) *Client_CreateNoCloudDirectory_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
 // CreatePostInstallScript provides a mock function with given fields: data
 func (_m *Client) CreatePostInstallScript(data string) sshclient.Output {
 	ret := _m.Called(data)
@@ -383,52 +292,6 @@ func (_c *Client_CreatePostInstallScript_Call) RunAndReturn(run func(string) ssh
 	return _c
 }
 
-// CreateUserData provides a mock function with given fields: userData
-func (_m *Client) CreateUserData(userData string) sshclient.Output {
-	ret := _m.Called(userData)
-
-	if len(ret) == 0 {
-		panic("no return value specified for CreateUserData")
-	}
-
-	var r0 sshclient.Output
-	if rf, ok := ret.Get(0).(func(string) sshclient.Output); ok {
-		r0 = rf(userData)
-	} else {
-		r0 = ret.Get(0).(sshclient.Output)
-	}
-
-	return r0
-}
-
-// Client_CreateUserData_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateUserData'
-type Client_CreateUserData_Call struct {
-	*mock.Call
-}
-
-// CreateUserData is a helper method to define mock.On call
-//   - userData string
-func (_e *Client_Expecter) CreateUserData(userData interface{}) *Client_CreateUserData_Call {
-	return &Client_CreateUserData_Call{Call: _e.mock.On("CreateUserData", userData)}
-}
-
-func (_c *Client_CreateUserData_Call) Run(run func(userData string)) *Client_CreateUserData_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(string))
-	})
-	return _c
-}
-
-func (_c *Client_CreateUserData_Call) Return(_a0 sshclient.Output) *Client_CreateUserData_Call {
-	_c.Call.Return(_a0)
-	return _c
-}
-
-func (_c *Client_CreateUserData_Call) RunAndReturn(run func(string) sshclient.Output) *Client_CreateUserData_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
 // DetectLinuxOnAnotherDisk provides a mock function with given fields: sliceOfWwns
 func (_m *Client) DetectLinuxOnAnotherDisk(sliceOfWwns []string) sshclient.Output {
 	ret := _m.Called(sliceOfWwns)
@@ -522,51 +385,6 @@ func (_c *Client_DownloadImage_Call) RunAndReturn(run func(string, string) sshcl
 	return _c
 }
 
-// EnsureCloudInit provides a mock function with given fields:
-func (_m *Client) EnsureCloudInit() sshclient.Output {
-	ret := _m.Called()
-
-	if len(ret) == 0 {
-		panic("no return value specified for EnsureCloudInit")
-	}
-
-	var r0 sshclient.Output
-	if rf, ok := ret.Get(0).(func() sshclient.Output); ok {
-		r0 = rf()
-	} else {
-		r0 = ret.Get(0).(sshclient.Output)
-	}
-
-	return r0
-}
-
-// Client_EnsureCloudInit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EnsureCloudInit'
-type Client_EnsureCloudInit_Call struct {
-	*mock.Call
-}
-
-// EnsureCloudInit is a helper method to define mock.On call
-func (_e *Client_Expecter) EnsureCloudInit() *Client_EnsureCloudInit_Call {
-	return &Client_EnsureCloudInit_Call{Call: _e.mock.On("EnsureCloudInit")}
-}
-
-func (_c *Client_EnsureCloudInit_Call) Run(run func()) *Client_EnsureCloudInit_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		run()
-	})
-	return _c
-}
-
-func (_c *Client_EnsureCloudInit_Call) Return(_a0 sshclient.Output) *Client_EnsureCloudInit_Call {
-	_c.Call.Return(_a0)
-	return _c
-}
-
-func (_c *Client_EnsureCloudInit_Call) RunAndReturn(run func() sshclient.Output) *Client_EnsureCloudInit_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
 // ExecuteInstallImage provides a mock function with given fields: hasPostInstallScript
 func (_m *Client) ExecuteInstallImage(hasPostInstallScript bool) sshclient.Output {
 	ret := _m.Called(hasPostInstallScript)
diff --git a/pkg/services/baremetal/client/ssh/ssh_client.go b/pkg/services/baremetal/client/ssh/ssh_client.go
index c9345e0a..6e76134c 100644
--- a/pkg/services/baremetal/client/ssh/ssh_client.go
+++ b/pkg/services/baremetal/client/ssh/ssh_client.go
@@ -176,6 +176,30 @@ type Output struct {
 	Err    error
 }
 
+func (o Output) String() string {
+	s := make([]string, 0, 3)
+	stdout := strings.TrimSpace(o.StdOut)
+	if stdout != "" {
+		s = append(s, stdout)
+	}
+	stderr := strings.TrimSpace(o.StdErr)
+	if stderr != "" {
+		if len(s) > 0 {
+			stderr = "Stderr: " + stderr
+		}
+		s = append(s, stderr)
+	}
+	if o.Err != nil {
+		e := o.Err.Error()
+		e = strings.TrimSpace(e)
+		if len(s) > 0 {
+			e = "Err: " + e
+		}
+		s = append(s, e)
+	}
+	return strings.Join(s, ". ")
+}
+
 // Client is the interface defining all functions necessary to talk to a bare metal server via SSH.
 type Client interface {
 	GetHostName() Output
@@ -196,10 +220,6 @@ type Client interface {
 	CreatePostInstallScript(data string) Output
 	ExecuteInstallImage(hasPostInstallScript bool) Output
 	Reboot() Output
-	EnsureCloudInit() Output
-	CreateNoCloudDirectory() Output
-	CreateMetaData(hostName string) Output
-	CreateUserData(userData string) Output
 	CloudInitStatus() Output
 	CheckCloudInitLogsForSigTerm() Output
 	CleanCloudInitLogs() Output
@@ -326,7 +346,7 @@ func (c *sshClient) GetHardwareDetailsCPUCores() Output {
 
 // GetHardwareDetailsDebug implements the GetHardwareDetailsDebug method of the SSHClient interface.
 func (c *sshClient) GetHardwareDetailsDebug() Output {
-	return c.runSSH(`ip a; echo ==========----------==========; 
+	return c.runSSH(`ip a; echo ==========----------==========;
 	ethtool "*"; echo ==========----------==========;
 	lspci; echo ==========----------==========;
 	`)
@@ -362,7 +382,7 @@ EOF_VIA_SSH`, data))
 	if out.Err != nil || out.StdErr != "" {
 		return out
 	}
-	return c.runSSH(`chmod +x /root/post-install.sh . `)
+	return c.runSSH(`chmod +x /root/post-install.sh`)
 }
 
 // GetRunningInstallImageProcesses returns the running installimage processes. Output.StdOut is empty if no processes are running.
@@ -432,30 +452,6 @@ func (c *sshClient) Reboot() Output {
 	return out
 }
 
-// EnsureCloudInit implements the EnsureCloudInit method of the SSHClient interface.
-func (c *sshClient) EnsureCloudInit() Output {
-	return c.runSSH(`command -v cloud-init`)
-}
-
-// CreateNoCloudDirectory implements the CreateNoCloudDirectory method of the SSHClient interface.
-func (c *sshClient) CreateNoCloudDirectory() Output {
-	return c.runSSH(`mkdir -p /var/lib/cloud/seed/nocloud-net`)
-}
-
-// CreateMetaData implements the CreateMetaData method of the SSHClient interface.
-func (c *sshClient) CreateMetaData(hostName string) Output {
-	return c.runSSH(fmt.Sprintf(`cat << 'EOF_VIA_SSH' > /var/lib/cloud/seed/nocloud-net/meta-data
-local-hostname: %s
-EOF_VIA_SSH`, hostName))
-}
-
-// CreateUserData implements the CreateUserData method of the SSHClient interface.
-func (c *sshClient) CreateUserData(userData string) Output {
-	return c.runSSH(fmt.Sprintf(`cat << 'EOF_VIA_SSH' > /var/lib/cloud/seed/nocloud-net/user-data
-%s
-EOF_VIA_SSH`, userData))
-}
-
 // CloudInitStatus implements the CloudInitStatus method of the SSHClient interface.
 func (c *sshClient) CloudInitStatus() Output {
 	out := c.runSSH("cloud-init status")
diff --git a/pkg/services/baremetal/host/host.go b/pkg/services/baremetal/host/host.go
index d7e9c9cc..9f096fd0 100644
--- a/pkg/services/baremetal/host/host.go
+++ b/pkg/services/baremetal/host/host.go
@@ -64,6 +64,10 @@ const (
 	errMsgInvalidSSHStdOut             = "invalid output in stdOut: %w"
 	errMsgFailedHandlingIncompleteBoot = "failed to handle incomplete boot: %w"
 	rebootServerStr                    = "RebootBMServer"
+
+	// PostInstallScriptFinished is a marker in the output of installimage. If it is not present,
+	// then install-image failed.
+	PostInstallScriptFinished = "POST_INSTALL_SCRIPT_FINISHED"
 )
 
 var (
@@ -130,7 +134,7 @@ func (s *Service) Reconcile(ctx context.Context) (result reconcile.Result, err e
 	}()
 
 	// reconcile state
-	actResult := hostStateMachine.ReconcileState()
+	actResult := hostStateMachine.ReconcileState(ctx)
 
 	result, err = actResult.Result()
 	if err != nil {
@@ -162,7 +166,9 @@ func SaveHostAndReturn(ctx context.Context, cl client.Client, host *infrav1.Hetz
 	return res, nil
 }
 
-func (s *Service) actionPreparing() actionResult {
+// previous: None
+// next: Registering
+func (s *Service) actionPreparing(_ context.Context) actionResult {
 	markProvisionPending(s.scope.HetznerBareMetalHost, infrav1.StatePreparing)
 
 	server, err := s.scope.RobotClient.GetBMServer(s.scope.HetznerBareMetalHost.Spec.ServerID)
@@ -238,14 +244,14 @@ func (s *Service) actionPreparing() actionResult {
 	if trimLineBreak(out.StdOut) != "" {
 		// we managed access with ssh - we can do an ssh reboot
 		if err := handleSSHError(sshClient.Reboot()); err != nil {
-			return actionError{err: fmt.Errorf("failed to reboot server via ssh: %w", err)}
+			return actionError{err: fmt.Errorf("failed to reboot server via ssh (actionPreparing): %w", err)}
 		}
 		msg := "Rebooting into rescue mode."
 		createSSHRebootEvent(s.scope.HetznerBareMetalHost, msg)
 		// we immediately set an error message in the host status to track the reboot we just performed
 		s.scope.HetznerBareMetalHost.SetError(infrav1.ErrorTypeSSHRebootTriggered, fmt.Sprintf("Phase %s, reboot via ssh: %s",
 			s.scope.HetznerBareMetalHost.Spec.Status.ProvisioningState, msg))
-		return actionComplete{}
+		return actionComplete{} // next: Registering
 	}
 
 	// Check if software reboot is available. If it is not, choose hardware reboot.
@@ -259,7 +265,7 @@ func (s *Service) actionPreparing() actionResult {
 	msg := createRebootEvent(s.scope.HetznerBareMetalHost, rebootType, "Reboot into rescue system.")
 	// we immediately set an error message in the host status to track the reboot we just performed
 	s.scope.HetznerBareMetalHost.SetError(errorType, msg)
-	return actionComplete{}
+	return actionComplete{} // next: Registering
 }
 
 func (s *Service) enforceRescueMode() error {
@@ -564,7 +570,9 @@ func (s *Service) ensureRescueMode() error {
 	return nil
 }
 
-func (s *Service) actionRegistering() actionResult {
+// previous: Preparing
+// next: ImageInstalling
+func (s *Service) actionRegistering(_ context.Context) actionResult {
 	markProvisionPending(s.scope.HetznerBareMetalHost, infrav1.StateRegistering)
 
 	creds := sshclient.CredentialsFromSecret(s.scope.RescueSSHSecret, s.scope.HetznerCluster.Spec.SSHKeys.RobotRescueSecretRef)
@@ -687,7 +695,7 @@ func (s *Service) actionRegistering() actionResult {
 
 	conditions.MarkTrue(s.scope.HetznerBareMetalHost, infrav1.RootDeviceHintsValidatedCondition)
 	s.scope.HetznerBareMetalHost.ClearError()
-	return actionComplete{}
+	return actionComplete{} // next: ImageInstalling
 }
 
 func validateRootDeviceWwnsAreSubsetOfExistingWwns(rootDeviceHints *infrav1.RootDeviceHints, storageDevices []infrav1.Storage) error {
@@ -722,8 +730,8 @@ func getHardwareDetails(sshClient sshclient.Client) (infrav1.HardwareDetails, er
 	}
 
 	// remove names of storage devices because they might change
-	for _, device := range storage {
-		device.Name = ""
+	for i := range storage {
+		storage[i].Name = ""
 	}
 
 	cpu, err := obtainHardwareDetailsCPU(sshClient)
@@ -1046,7 +1054,9 @@ func validateStdOut(stdOut string) (string, error) {
 	return stdOut, nil
 }
 
-func (s *Service) actionImageInstalling() actionResult {
+// previous: Registering
+// next: EnsureProvisioned
+func (s *Service) actionImageInstalling(ctx context.Context) actionResult {
 	markProvisionPending(s.scope.HetznerBareMetalHost, infrav1.StateImageInstalling)
 
 	creds := sshclient.CredentialsFromSecret(s.scope.RescueSSHSecret, s.scope.HetznerCluster.Spec.SSHKeys.RobotRescueSecretRef)
@@ -1112,11 +1122,10 @@ func (s *Service) actionImageInstalling() actionResult {
 		return actionError{err: fmt.Errorf("failed to get running installimage processes: %q %q %w", out.StdOut, out.StdErr, out.Err)}
 	}
 	if out.StdOut != "" {
-		record.Warnf(s.scope.HetznerBareMetalHost, "InstallImageAlreadyRunning",
-			"installimage is already running:\n%s", out.StdOut)
-		return actionContinue{
-			delay: 10 * time.Second,
-		}
+		// This can happen if the controller was restarted.
+		record.Warn(s.scope.HetznerBareMetalHost, "InstallImageAlreadyRunning",
+			"installimage is already running.")
+		return actionError{err: fmt.Errorf("installimage is already running")}
 	}
 
 	record.Event(s.scope.HetznerBareMetalHost, "InstallImagePreflightCheckSuccessful", "Rescue system reachable, disks look good.")
@@ -1140,10 +1149,38 @@ func (s *Service) actionImageInstalling() actionResult {
 	// create post install script
 	postInstallScript := s.scope.HetznerBareMetalHost.Spec.Status.InstallImage.PostInstallScript
 
-	if postInstallScript != "" {
-		if err := handleSSHError(sshClient.CreatePostInstallScript(postInstallScript)); err != nil {
-			return actionError{err: fmt.Errorf("failed to create post install script %s: %w", postInstallScript, err)}
-		}
+	if !strings.HasPrefix(postInstallScript, "#!/bin/bash") {
+		postInstallScript = fmt.Sprintf("#!/bin/bash\n%s", postInstallScript)
+	}
+
+	cloudInitData, err := s.scope.GetRawBootstrapData(ctx)
+	if err != nil {
+		return actionError{err: fmt.Errorf("failed to get user data: %w", err)}
+	}
+
+	postInstallScript = fmt.Sprintf(`%s
+
+# install cloud-init data
+
+trap 'echo "ERROR: A command has failed. Exiting the script. Line was ($0:$LINENO): $(sed -n "${LINENO}p" "$0")"; exit 3' ERR
+set -Eeuo pipefail
+
+mkdir -p /var/lib/cloud/seed/nocloud-net
+
+cat << 'EOF_POST_INSTALL_SCRIPT' > /var/lib/cloud/seed/nocloud-net/meta-data
+local-hostname: %s
+EOF_POST_INSTALL_SCRIPT
+
+cat << 'EOF_POST_INSTALL_SCRIPT' > /var/lib/cloud/seed/nocloud-net/user-data
+%s
+EOF_POST_INSTALL_SCRIPT
+
+echo %q
+# end of install cloud-init data
+`, postInstallScript, s.scope.Hostname(), cloudInitData, PostInstallScriptFinished)
+
+	if err := handleSSHError(sshClient.CreatePostInstallScript(postInstallScript)); err != nil {
+		return actionError{err: fmt.Errorf("failed to create post install script %s: %w", postInstallScript, err)}
 	}
 
 	record.Event(s.scope.HetznerBareMetalHost, "InstallingMachineImageStarted",
@@ -1160,10 +1197,14 @@ func (s *Service) actionImageInstalling() actionResult {
 	// Execute install image
 	out = sshClient.ExecuteInstallImage(postInstallScript != "")
 	if out.Err != nil {
-		record.Warnf(s.scope.HetznerBareMetalHost, "ExecuteInstallImageFailed", out.StdOut)
+		record.Warnf(s.scope.HetznerBareMetalHost, "ExecuteInstallImageFailed", out.String())
 		return actionError{err: fmt.Errorf("failed to execute installimage: %w", out.Err)}
 	}
-
+	if !strings.Contains(out.StdOut, PostInstallScriptFinished) {
+		record.Warnf(s.scope.HetznerBareMetalHost, "ExecuteInstallImageDidNotFinish", out.StdOut)
+		return actionError{err: fmt.Errorf("did not find marker %q in stdout. Failed to execute installimage",
+			PostInstallScriptFinished)}
+	}
 	record.Eventf(s.scope.HetznerBareMetalHost, "ExecuteInstallImageSucceeded", out.StdOut)
 	s.scope.Logger.Info("ExecuteInstallImageSucceeded", "stdout", out.StdOut, "stderr", out.StdErr)
 
@@ -1174,17 +1215,19 @@ func (s *Service) actionImageInstalling() actionResult {
 		return actionError{err: fmt.Errorf("failed to update name of host in robot API: %w", err)}
 	}
 
-	if err := handleSSHError(sshClient.Reboot()); err != nil {
+	out = sshClient.Reboot()
+	if err := handleSSHError(out); err != nil {
+		err = fmt.Errorf("failed to reboot server (after install-image): %w", err)
 		record.Warn(s.scope.HetznerBareMetalHost, "RebootFailed", err.Error())
-		return actionError{err: fmt.Errorf("failed to reboot server: %w", err)}
+		return actionError{err: err}
 	}
-	createSSHRebootEvent(s.scope.HetznerBareMetalHost, "machine image was installed")
+	createSSHRebootEvent(s.scope.HetznerBareMetalHost, "machine image and cloud-init data got installed")
 
 	s.scope.Logger.Info("RebootAfterInstallimageSucceeded", "stdout", out.StdOut, "stderr", out.StdErr)
 
 	// clear potential errors - all done
 	s.scope.HetznerBareMetalHost.ClearError()
-	return actionComplete{}
+	return actionComplete{} // next: ensure-provisioned
 }
 
 func (s *Service) createAutoSetupInput(sshClient sshclient.Client) (autoSetupInput, actionResult) {
@@ -1255,129 +1298,6 @@ func getDeviceNames(wwn []string, storageDevices []infrav1.Storage) []string {
 	return deviceNames
 }
 
-func (s *Service) actionProvisioning() actionResult {
-	markProvisionPending(s.scope.HetznerBareMetalHost, infrav1.StateProvisioning)
-
-	host := s.scope.HetznerBareMetalHost
-
-	portAfterInstallImage := host.Spec.Status.SSHSpec.PortAfterInstallImage
-	privateKey := sshclient.CredentialsFromSecret(s.scope.OSSSHSecret, host.Spec.Status.SSHSpec.SecretRef).PrivateKey
-	sshClient := s.scope.SSHClientFactory.NewClient(sshclient.Input{
-		PrivateKey: privateKey,
-		Port:       portAfterInstallImage,
-		IP:         host.Spec.Status.GetIPAddress(),
-	})
-
-	// check hostname with sshClient
-	wantHostName := s.scope.Hostname()
-
-	out := sshClient.GetHostName()
-
-	if out.Err != nil {
-		msg := fmt.Sprintf("ssh to port %d failed: %s (%s): %v", portAfterInstallImage, out.StdOut, out.StdErr, out.Err)
-		conditions.MarkFalse(host,
-			infrav1.SSHAfterInstallImageSucceededCondition,
-			infrav1.SSHAfterInstallImageFailedReason,
-			clusterv1.ConditionSeverityWarning, msg)
-
-		if time.Now().After(conditions.GetLastTransitionTime(
-			s.scope.HetznerBareMetalHost,
-			infrav1.SSHAfterInstallImageSucceededCondition).Add(7 * time.Minute)) {
-			// We waited some minutes. Still no connection. There seems to something wrong.
-			record.Warn(host, infrav1.SSHAfterInstallImageFailedReason, msg)
-		}
-	} else {
-		conditions.MarkTrue(s.scope.HetznerBareMetalHost,
-			infrav1.SSHAfterInstallImageSucceededCondition)
-	}
-
-	if trimLineBreak(out.StdOut) != wantHostName {
-		// Give the reboot some time until it takes effect. Otherwise the ssh connection gets done too fast,
-		// and it will connect to the machine before it gets rebooted.
-		if s.hasJustRebooted() {
-			s.scope.Logger.Info("hasJustRebooted is true", "LastUpdated", host.Spec.Status.LastUpdated)
-			return actionContinue{delay: 2 * time.Second}
-		}
-
-		privateKeyRescue := sshclient.CredentialsFromSecret(s.scope.RescueSSHSecret, s.scope.HetznerCluster.Spec.SSHKeys.RobotRescueSecretRef).PrivateKey
-		rescueSSHClient := s.scope.SSHClientFactory.NewClient(sshclient.Input{
-			PrivateKey: privateKeyRescue,
-			Port:       rescuePort,
-			IP:         host.Spec.Status.GetIPAddress(),
-		})
-
-		isSSHTimeoutError, isSSHConnectionRefusedError, err := analyzeSSHOutputInstallImage(out, rescueSSHClient, portAfterInstallImage)
-		if err != nil {
-			if errors.Is(err, errUnexpectedHostName) {
-				// One possible reason: The machine gets used by a second wl-cluster
-				record.Warn(host, "UnexpectedHostName",
-					fmt.Sprintf("Provisioning: wanted %q. %s", wantHostName, err.Error()))
-			}
-			return actionError{err: fmt.Errorf("failed to handle incomplete boot - installImage: %w", err)}
-		}
-		failed, err := s.handleIncompleteBoot(false, isSSHTimeoutError, isSSHConnectionRefusedError)
-		if failed {
-			return s.recordActionFailure(infrav1.ProvisioningError, err.Error())
-		}
-		if err != nil {
-			return actionError{err: fmt.Errorf(errMsgFailedHandlingIncompleteBoot, err)}
-		}
-		return actionContinue{delay: 10 * time.Second}
-	}
-
-	// we are in correct boot and can start provisioning
-	if failedAction := s.provision(sshClient); failedAction != nil {
-		return failedAction
-	}
-
-	host.ClearError()
-	return actionComplete{}
-}
-
-func (s *Service) provision(sshClient sshclient.Client) actionResult {
-	{
-		out := sshClient.EnsureCloudInit()
-		if err := handleSSHError(out); err != nil {
-			return actionError{err: fmt.Errorf("failed to ensure cloud init: %w", err)}
-		}
-
-		if trimLineBreak(out.StdOut) == "" {
-			msg := "cloud init not installed"
-			conditions.MarkFalse(
-				s.scope.HetznerBareMetalHost,
-				infrav1.ProvisionSucceededCondition,
-				infrav1.CloudInitNotInstalledReason,
-				clusterv1.ConditionSeverityError,
-				msg,
-			)
-			return s.recordActionFailure(infrav1.ProvisioningError, msg)
-		}
-	}
-
-	if err := handleSSHError(sshClient.CreateNoCloudDirectory()); err != nil {
-		return actionError{err: fmt.Errorf("failed to create no cloud directory: %w", err)}
-	}
-
-	if err := handleSSHError(sshClient.CreateMetaData(s.scope.Hostname())); err != nil {
-		return actionError{err: fmt.Errorf("failed to create meta data: %w", err)}
-	}
-
-	userData, err := s.scope.GetRawBootstrapData(context.TODO())
-	if err != nil {
-		return actionError{err: fmt.Errorf("failed to get user data: %w", err)}
-	}
-
-	if err := handleSSHError(sshClient.CreateUserData(string(userData))); err != nil {
-		return actionError{err: fmt.Errorf("failed to create user data: %w", err)}
-	}
-
-	if err := handleSSHError(sshClient.Reboot()); err != nil {
-		return actionError{err: fmt.Errorf("failed to reboot: %w", err)}
-	}
-	createSSHRebootEvent(s.scope.HetznerBareMetalHost, "UserData of cloud-init was created")
-	return nil
-}
-
 func analyzeSSHOutputInstallImage(out sshclient.Output, sshClient sshclient.Client, port int) (isTimeout, isConnectionRefused bool, reterr error) {
 	// check err
 	if out.Err != nil {
@@ -1441,7 +1361,7 @@ func verifyConnectionRefused(sshClient sshclient.Client, port int) bool {
 	return true
 }
 
-func (s *Service) actionEnsureProvisioned() (ar actionResult) {
+func (s *Service) actionEnsureProvisioned(_ context.Context) (ar actionResult) {
 	markProvisionPending(s.scope.HetznerBareMetalHost, infrav1.StateEnsureProvisioned)
 	sshClient := s.scope.SSHClientFactory.NewClient(sshclient.Input{
 		PrivateKey: sshclient.CredentialsFromSecret(s.scope.OSSSHSecret, s.scope.HetznerBareMetalHost.Spec.Status.SSHSpec.SecretRef).PrivateKey,
@@ -1482,9 +1402,11 @@ func (s *Service) actionEnsureProvisioned() (ar actionResult) {
 	wantHostName := s.scope.Hostname()
 
 	out := sshClient.GetHostName()
-	if trimLineBreak(out.StdOut) != wantHostName {
+	hostname := trimLineBreak(out.StdOut)
+	if hostname != wantHostName {
 		// give the reboot some time until it takes effect
 		if s.hasJustRebooted() {
+			s.scope.Logger.Info("ensureProvisioned: hasJustRebooted. Retrying...", "hostname", hostname)
 			return actionContinue{delay: 2 * time.Second}
 		}
 
@@ -1500,6 +1422,7 @@ func (s *Service) actionEnsureProvisioned() (ar actionResult) {
 		// A connection failed error could mean that cloud init is still running (if cloudInit introduces a new port)
 		if isSSHConnectionRefusedError {
 			if actionRes := s.handleConnectionRefused(); actionRes != nil {
+				s.scope.Logger.Info("ensureProvisioned: ConnectionRefused", "actionResult", actionRes)
 				return actionRes
 			}
 		}
@@ -1515,8 +1438,9 @@ func (s *Service) actionEnsureProvisioned() (ar actionResult) {
 	}
 
 	// Check the status of cloud init
-	actResult, _ := s.checkCloudInitStatus(sshClient)
+	actResult, msg, _ := s.checkCloudInitStatus(sshClient)
 	if _, complete := actResult.(actionComplete); !complete {
+		record.Event(s.scope.HetznerBareMetalHost, "CloudInitStillRunning", msg)
 		return actResult
 	}
 
@@ -1526,6 +1450,7 @@ func (s *Service) actionEnsureProvisioned() (ar actionResult) {
 	if s.scope.HetznerBareMetalHost.Spec.Status.SSHSpec.PortAfterInstallImage == s.scope.HetznerBareMetalHost.Spec.Status.SSHSpec.PortAfterCloudInit {
 		actResult = s.handleCloudInitNotStarted()
 		if _, complete := actResult.(actionComplete); !complete {
+			s.scope.Logger.Info("ensureProvisioned: handleCloudInitNotStarted", "actResult", actResult)
 			return actResult
 		}
 	}
@@ -1533,7 +1458,7 @@ func (s *Service) actionEnsureProvisioned() (ar actionResult) {
 	record.Event(s.scope.HetznerBareMetalHost, "ServerProvisioned", "server successfully provisioned")
 	conditions.MarkTrue(s.scope.HetznerBareMetalHost, infrav1.ProvisionSucceededCondition)
 	s.scope.HetznerBareMetalHost.ClearError()
-	return actionComplete{}
+	return actionComplete{} // next: EnsureProvisioned
 }
 
 // handleConnectionRefused checks cloud init status via ssh to the old ssh port if the new ssh port
@@ -1548,7 +1473,7 @@ func (s *Service) handleConnectionRefused() actionResult {
 		Port:       s.scope.HetznerBareMetalHost.Spec.Status.SSHSpec.PortAfterInstallImage,
 		IP:         s.scope.HetznerBareMetalHost.Spec.Status.GetIPAddress(),
 	})
-	actResult, err := s.checkCloudInitStatus(oldSSHClient)
+	actResult, _, err := s.checkCloudInitStatus(oldSSHClient)
 	// If this ssh client also gives an error, then we go back to analyzing the error of the first ssh call
 	// This happens in the statement below this one.
 	if err == nil {
@@ -1569,40 +1494,42 @@ func (s *Service) handleConnectionRefused() actionResult {
 	return nil
 }
 
-func (s *Service) checkCloudInitStatus(sshClient sshclient.Client) (actionResult, error) {
+func (s *Service) checkCloudInitStatus(sshClient sshclient.Client) (actionResult, string, error) {
 	out := sshClient.CloudInitStatus()
 	// This error is interesting for further logic and might happen because of the fact that the sshClient has the wrong port
 	if out.Err != nil {
-		return actionError{err: fmt.Errorf("failed to get cloud init status: %w", out.Err)}, out.Err
+		return actionError{err: fmt.Errorf("failed to get cloud init status: %w", out.Err)}, "", out.Err
 	}
 
 	stdOut := trimLineBreak(out.StdOut)
 	switch {
 	case strings.Contains(stdOut, "status: running"):
 		// Cloud init is still running
-		return actionContinue{delay: 5 * time.Second}, nil
+		return actionContinue{delay: 5 * time.Second}, "cloud-init still running", nil
 	case strings.Contains(stdOut, "status: disabled"):
 		// Reboot needs to be triggered again - did not start yet
 		out = sshClient.Reboot()
+		msg := "cloud-init-status was 'disabled'"
 		if err := handleSSHError(out); err != nil {
-			return actionError{err: fmt.Errorf("failed to reboot: %w", err)}, nil
+			return actionError{err: fmt.Errorf("failed to reboot (%s): %w", msg, err)}, "", nil
 		}
+		createSSHRebootEvent(s.scope.HetznerBareMetalHost, msg)
 		s.scope.HetznerBareMetalHost.SetError(infrav1.ErrorTypeSSHRebootTriggered, "ssh reboot just triggered")
-		record.Event(s.scope.HetznerBareMetalHost, "SSHRebootAfterCloudInitStatusDisabled", "cloud-init status was 'disabled'")
-		return actionContinue{delay: 5 * time.Second}, nil
+		record.Warn(s.scope.HetznerBareMetalHost, "SSHRebootAfterCloudInitStatusDisabled", msg)
+		return actionContinue{delay: 5 * time.Second}, "cloud-init was disabled. Triggered a reboot again", nil
 	case strings.Contains(stdOut, "status: done"):
-
 		s.scope.HetznerBareMetalHost.ClearError()
+		return actionComplete{}, "cloud-init is done", nil
 	case strings.Contains(stdOut, "status: error"):
 		record.Warn(s.scope.HetznerBareMetalHost, "CloudInitFailed", "cloud init returned status error")
-		return s.recordActionFailure(infrav1.FatalError, "cloud init returned status error"), nil
+		return s.recordActionFailure(infrav1.FatalError, "cloud init returned status error"), "", nil
 	default:
 		// Errors are handled after stdOut in this case, as status: error returns an exited with status 1 error
 		if err := handleSSHError(out); err != nil {
-			return actionError{err: fmt.Errorf("failed to get cloud init status: %w", err)}, nil
+			return actionError{err: fmt.Errorf("failed to get cloud init status: %w", err)}, "", nil
 		}
+		return actionContinue{delay: 5 * time.Second}, fmt.Sprintf("cloud-init unknown output: %s. %s", out.StdOut, out.StdErr), nil
 	}
-	return actionComplete{}, nil
 }
 
 func (s *Service) handleCloudInitNotStarted() actionResult {
@@ -1629,8 +1556,9 @@ func (s *Service) handleCloudInitNotStarted() actionResult {
 		}
 		out = oldSSHClient.Reboot()
 		if err := handleSSHError(out); err != nil {
-			return actionError{err: fmt.Errorf("failed to reboot: %w", err)}
+			return actionError{err: fmt.Errorf("failed to reboot (handleCloudInitNotStarted): %w", err)}
 		}
+		createSSHRebootEvent(s.scope.HetznerBareMetalHost, "machine image and cloud-init data got installed")
 		record.Eventf(s.scope.HetznerBareMetalHost,
 			"SSHRebootAfterCloudInitSigTermFound", "rebooted via ssh after cloud init logs contained sigterm: %s", trimLineBreak(out.StdOut))
 		return actionContinue{delay: 10 * time.Second}
@@ -1675,7 +1603,9 @@ func analyzeSSHOutputProvisioned(out sshclient.Output) (isTimeout, isConnectionR
 	return false, false, fmt.Errorf("%w: %s", errUnexpectedHostName, trimLineBreak(out.StdOut))
 }
 
-func (s *Service) actionProvisioned() actionResult {
+// previous: EnsureProvisioned
+// next: Stays in Provisioned (final state)
+func (s *Service) actionProvisioned(_ context.Context) actionResult {
 	// set host to provisioned
 	conditions.MarkTrue(s.scope.HetznerBareMetalHost, infrav1.ProvisionSucceededCondition)
 
@@ -1730,15 +1660,16 @@ func (s *Service) actionProvisioned() actionResult {
 			return actionError{err: err}
 		}
 
-		createSSHRebootEvent(s.scope.HetznerBareMetalHost, "reboot annotation was set")
+		createSSHRebootEvent(s.scope.HetznerBareMetalHost, "Rebooting because annotation was set")
 		s.scope.HetznerBareMetalHost.Spec.Status.Rebooted = true
 		return actionContinue{delay: 10 * time.Second}
 	}
 
-	return actionComplete{}
+	return actionComplete{} // Stays in Provisioned (final state)
 }
 
-func (s *Service) actionDeprovisioning() actionResult {
+// next: None
+func (s *Service) actionDeprovisioning(_ context.Context) actionResult {
 	// Update name in robot API
 	if _, err := s.scope.RobotClient.SetBMServerName(
 		s.scope.HetznerBareMetalHost.Spec.ServerID,
@@ -1772,11 +1703,10 @@ func (s *Service) actionDeprovisioning() actionResult {
 		s.scope.HetznerBareMetalHost.ClearError()
 		conditions.Delete(s.scope.HetznerBareMetalHost, infrav1.ProvisionSucceededCondition)
 	}
-
-	return actionComplete{}
+	return actionComplete{} // next: None
 }
 
-func (s *Service) actionDeleting() actionResult {
+func (s *Service) actionDeleting(_ context.Context) actionResult {
 	s.scope.HetznerBareMetalHost.Finalizers = utils.FilterStringFromList(s.scope.HetznerBareMetalHost.Finalizers, infrav1.BareMetalHostFinalizer)
 	return deleteComplete{}
 }
diff --git a/pkg/services/baremetal/host/state_machine.go b/pkg/services/baremetal/host/state_machine.go
index a6086af0..cc336a16 100644
--- a/pkg/services/baremetal/host/state_machine.go
+++ b/pkg/services/baremetal/host/state_machine.go
@@ -17,6 +17,7 @@ limitations under the License.
 package host
 
 import (
+	"context"
 	"fmt"
 
 	"github.com/go-logr/logr"
@@ -51,14 +52,13 @@ func newHostStateMachine(host *infrav1.HetznerBareMetalHost, reconciler *Service
 	return &r
 }
 
-type stateHandler func() actionResult
+type stateHandler func(ctx context.Context) actionResult
 
 func (hsm *hostStateMachine) handlers() map[infrav1.ProvisioningState]stateHandler {
 	return map[infrav1.ProvisioningState]stateHandler{
 		infrav1.StatePreparing:         hsm.handlePreparing,
 		infrav1.StateRegistering:       hsm.handleRegistering,
 		infrav1.StateImageInstalling:   hsm.handleImageInstalling,
-		infrav1.StateProvisioning:      hsm.handleProvisioning,
 		infrav1.StateEnsureProvisioned: hsm.handleEnsureProvisioned,
 		infrav1.StateProvisioned:       hsm.handleProvisioned,
 		infrav1.StateDeprovisioning:    hsm.handleDeprovisioning,
@@ -66,7 +66,7 @@ func (hsm *hostStateMachine) handlers() map[infrav1.ProvisioningState]stateHandl
 	}
 }
 
-func (hsm *hostStateMachine) ReconcileState() (actionRes actionResult) {
+func (hsm *hostStateMachine) ReconcileState(ctx context.Context) (actionRes actionResult) {
 	initialState := hsm.host.Spec.Status.ProvisioningState
 	defer func() {
 		if hsm.nextState != initialState {
@@ -87,8 +87,17 @@ func (hsm *hostStateMachine) ReconcileState() (actionRes actionResult) {
 	// Assume credentials are ready for now. This can be changed while the state is handled.
 	conditions.MarkTrue(hsm.host, infrav1.CredentialsAvailableCondition)
 
+	// This state was removed. We have to handle the edge-case where
+	// the controller got updated and a machine
+	// is in this state. Installing the image again should solve that.
+	if initialState == "provisioning" {
+		hsm.log.Info("edge-case was hit: New code meets machine in removed state 'provisioning'. Re-setting",
+			"new-state", infrav1.StateImageInstalling)
+		initialState = infrav1.StateImageInstalling
+	}
+
 	if stateHandler, found := hsm.handlers()[initialState]; found {
-		return stateHandler()
+		return stateHandler(ctx)
 	}
 
 	return actionError{fmt.Errorf("%w: state %q", errNoHandlerFound, initialState)}
@@ -103,7 +112,7 @@ func (hsm *hostStateMachine) checkInitiateDelete() bool {
 	switch hsm.nextState {
 	default:
 		hsm.nextState = infrav1.StateDeleting
-	case infrav1.StateRegistering, infrav1.StateImageInstalling, infrav1.StateProvisioning,
+	case infrav1.StateRegistering, infrav1.StateImageInstalling,
 		infrav1.StateEnsureProvisioned, infrav1.StateProvisioned:
 		hsm.nextState = infrav1.StateDeprovisioning
 	case infrav1.StateDeprovisioning:
@@ -149,7 +158,7 @@ func (hsm *hostStateMachine) updateOSSSHStatusAndValidateKey(osSSHSecret *corev1
 	if !hsm.host.Spec.Status.SSHStatus.CurrentOS.Match(*osSSHSecret) {
 		// Take action depending on state
 		switch hsm.nextState {
-		case infrav1.StateProvisioning, infrav1.StateEnsureProvisioned:
+		case infrav1.StateEnsureProvisioned:
 			// Go back to StateImageInstalling as we need to provision again
 			hsm.nextState = infrav1.StateImageInstalling
 		case infrav1.StateProvisioned:
@@ -214,7 +223,7 @@ func validateSSHKey(sshSecret *corev1.Secret, secretRef infrav1.SSHSecretRef) er
 	return nil
 }
 
-func (hsm *hostStateMachine) handlePreparing() actionResult {
+func (hsm *hostStateMachine) handlePreparing(ctx context.Context) actionResult {
 	if hsm.provisioningCancelled() {
 		hsm.nextState = infrav1.StateDeprovisioning
 		return actionComplete{}
@@ -222,75 +231,72 @@ func (hsm *hostStateMachine) handlePreparing() actionResult {
 
 	record.Eventf(hsm.host, "PreparingForProvisioning", "ServerID %d %s", hsm.host.Spec.ServerID, hsm.host.Spec.Description)
 
-	actResult := hsm.reconciler.actionPreparing()
+	actResult := hsm.reconciler.actionPreparing(ctx)
 	if _, ok := actResult.(actionComplete); ok {
 		hsm.nextState = infrav1.StateRegistering
 	}
 	return actResult
 }
 
-func (hsm *hostStateMachine) handleRegistering() actionResult {
+func (hsm *hostStateMachine) handleRegistering(ctx context.Context) actionResult {
 	if hsm.provisioningCancelled() {
 		hsm.nextState = infrav1.StateDeprovisioning
 		return actionComplete{}
 	}
 
-	actResult := hsm.reconciler.actionRegistering()
+	actResult := hsm.reconciler.actionRegistering(ctx)
 	if _, ok := actResult.(actionComplete); ok {
 		hsm.nextState = infrav1.StateImageInstalling
 	}
 	return actResult
 }
 
-func (hsm *hostStateMachine) handleImageInstalling() actionResult {
-	if hsm.provisioningCancelled() {
-		hsm.nextState = infrav1.StateDeprovisioning
-		return actionComplete{}
-	}
-
-	actResult := hsm.reconciler.actionImageInstalling()
-	if _, ok := actResult.(actionComplete); ok {
-		hsm.nextState = infrav1.StateProvisioning
-	}
-	return actResult
-}
-
-func (hsm *hostStateMachine) handleProvisioning() actionResult {
+func (hsm *hostStateMachine) handleImageInstalling(ctx context.Context) actionResult {
 	if hsm.provisioningCancelled() {
 		hsm.nextState = infrav1.StateDeprovisioning
 		return actionComplete{}
 	}
 
-	actResult := hsm.reconciler.actionProvisioning()
-	if _, ok := actResult.(actionComplete); ok {
+	actResult := hsm.reconciler.actionImageInstalling(ctx)
+	switch actResult.(type) {
+	case actionComplete:
 		hsm.nextState = infrav1.StateEnsureProvisioned
+	case actionError:
+		// re-enable rescue system. If installimage failed, then it is likely, that
+		// the next run (without reboot) fails with this error:
+		// ERROR unmounting device(s):
+		// umount: /: target is busy.
+		// Cannot continue, device(s) seem to be in use.
+		// Please unmount used devices manually or reboot the rescuesystem and retry.
+		hsm.nextState = infrav1.StatePreparing
 	}
+
 	return actResult
 }
 
-func (hsm *hostStateMachine) handleEnsureProvisioned() actionResult {
+func (hsm *hostStateMachine) handleEnsureProvisioned(ctx context.Context) actionResult {
 	if hsm.provisioningCancelled() {
 		hsm.nextState = infrav1.StateDeprovisioning
 		return actionComplete{}
 	}
 
-	actResult := hsm.reconciler.actionEnsureProvisioned()
+	actResult := hsm.reconciler.actionEnsureProvisioned(ctx)
 	if _, ok := actResult.(actionComplete); ok {
 		hsm.nextState = infrav1.StateProvisioned
 	}
 	return actResult
 }
 
-func (hsm *hostStateMachine) handleProvisioned() actionResult {
+func (hsm *hostStateMachine) handleProvisioned(ctx context.Context) actionResult {
 	if hsm.provisioningCancelled() {
 		hsm.nextState = infrav1.StateDeprovisioning
 		return actionComplete{}
 	}
-	return hsm.reconciler.actionProvisioned()
+	return hsm.reconciler.actionProvisioned(ctx)
 }
 
-func (hsm *hostStateMachine) handleDeprovisioning() actionResult {
-	actResult := hsm.reconciler.actionDeprovisioning()
+func (hsm *hostStateMachine) handleDeprovisioning(ctx context.Context) actionResult {
+	actResult := hsm.reconciler.actionDeprovisioning(ctx)
 	if _, ok := actResult.(actionComplete); ok {
 		hsm.nextState = infrav1.StateNone
 		return actionComplete{}
@@ -298,8 +304,8 @@ func (hsm *hostStateMachine) handleDeprovisioning() actionResult {
 	return actResult
 }
 
-func (hsm *hostStateMachine) handleDeleting() actionResult {
-	return hsm.reconciler.actionDeleting()
+func (hsm *hostStateMachine) handleDeleting(ctx context.Context) actionResult {
+	return hsm.reconciler.actionDeleting(ctx)
 }
 
 func (hsm *hostStateMachine) provisioningCancelled() bool {


Test patch:
diff --git a/controllers/hcloudmachine_controller_test.go b/controllers/hcloudmachine_controller_test.go
index 6eed7543..37bef3a8 100644
--- a/controllers/hcloudmachine_controller_test.go
+++ b/controllers/hcloudmachine_controller_test.go
@@ -127,9 +127,11 @@ func TestIgnoreInsignificantHCloudMachineStatusUpdates(t *testing.T) {
 		})
 	}
 }
+
 func stPtr(h hcloud.ServerStatus) *hcloud.ServerStatus {
 	return &h
 }
+
 func TestIgnoreInsignificantMachineStatusUpdates(t *testing.T) {
 	logger := klog.Background()
 	predicate := IgnoreInsignificantMachineStatusUpdates(logger)
@@ -361,7 +363,7 @@ var _ = Describe("HCloudMachineReconciler", func() {
 				}, timeout).Should(BeTrue())
 			})
 
-			It("creates the HCloud machine in Hetzner", func() {
+			It("creates the HCloud machine in Hetzner 1", func() {
 				By("checking that no servers exist")
 
 				Eventually(func() bool {
@@ -518,7 +520,7 @@ var _ = Describe("HCloudMachineReconciler", func() {
 				Expect(testEnv.Cleanup(ctx, hetznerCluster, hcloudMachine)).To(Succeed())
 			})
 
-			It("creates the HCloud machine in Hetzner", func() {
+			It("creates the HCloud machine in Hetzner 2", func() {
 				Eventually(func() int {
 					servers, err := hcloudClient.ListServers(ctx, hcloud.ServerListOpts{
 						ListOpts: hcloud.ListOpts{
@@ -546,7 +548,7 @@ var _ = Describe("HCloudMachineReconciler", func() {
 				Expect(testEnv.Cleanup(ctx, hetznerCluster, hcloudMachine)).To(Succeed())
 			})
 
-			It("creates the HCloud machine in Hetzner", func() {
+			It("creates the HCloud machine in Hetzner 3", func() {
 				Eventually(func() int {
 					servers, err := hcloudClient.ListServers(ctx, hcloud.ServerListOpts{
 						ListOpts: hcloud.ListOpts{
@@ -594,7 +596,7 @@ var _ = Describe("HCloudMachineReconciler", func() {
 				Expect(testEnv.Cleanup(ctx, hetznerCluster, hcloudMachine)).To(Succeed())
 			})
 
-			It("creates the HCloud machine in Hetzner", func() {
+			It("creates the HCloud machine in Hetzner 4", func() {
 				Eventually(func() int {
 					servers, err := hcloudClient.ListServers(ctx, hcloud.ServerListOpts{
 						ListOpts: hcloud.ListOpts{
diff --git a/controllers/hetznerbaremetalhost_controller_test.go b/controllers/hetznerbaremetalhost_controller_test.go
index fc244c75..d8d83230 100644
--- a/controllers/hetznerbaremetalhost_controller_test.go
+++ b/controllers/hetznerbaremetalhost_controller_test.go
@@ -36,6 +36,7 @@ import (
 	robotmock "github.com/syself/cluster-api-provider-hetzner/pkg/services/baremetal/client/mocks/robot"
 	sshmock "github.com/syself/cluster-api-provider-hetzner/pkg/services/baremetal/client/mocks/ssh"
 	sshclient "github.com/syself/cluster-api-provider-hetzner/pkg/services/baremetal/client/ssh"
+	hostpkg "github.com/syself/cluster-api-provider-hetzner/pkg/services/baremetal/host"
 	"github.com/syself/cluster-api-provider-hetzner/pkg/utils"
 	"github.com/syself/cluster-api-provider-hetzner/test/helpers"
 )
@@ -162,10 +163,6 @@ var _ = Describe("HetznerBareMetalHostReconciler", func() {
 		configureRescueSSHClient(rescueSSHClient)
 
 		osSSHClientAfterInstallImage.On("Reboot").Return(sshclient.Output{})
-		osSSHClientAfterInstallImage.On("CreateNoCloudDirectory").Return(sshclient.Output{})
-		osSSHClientAfterInstallImage.On("CreateMetaData", mock.Anything).Return(sshclient.Output{})
-		osSSHClientAfterInstallImage.On("CreateUserData", mock.Anything).Return(sshclient.Output{})
-		osSSHClientAfterInstallImage.On("EnsureCloudInit").Return(sshclient.Output{StdOut: "cloud-init"})
 		osSSHClientAfterInstallImage.On("CloudInitStatus").Return(sshclient.Output{StdOut: "status: done"})
 		osSSHClientAfterInstallImage.On("CheckCloudInitLogsForSigTerm").Return(sshclient.Output{})
 		osSSHClientAfterInstallImage.On("ResetKubeadm").Return(sshclient.Output{})
@@ -895,7 +892,7 @@ name="eth0" model="Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express
 	sshClient.On("CreateAutoSetup", mock.Anything).Return(sshclient.Output{})
 	sshClient.On("UntarTGZ").Return(sshclient.Output{})
 	sshClient.On("CreatePostInstallScript", mock.Anything).Return(sshclient.Output{})
-	sshClient.On("ExecuteInstallImage", mock.Anything).Return(sshclient.Output{})
+	sshClient.On("ExecuteInstallImage", mock.Anything).Return(sshclient.Output{StdOut: hostpkg.PostInstallScriptFinished})
 	sshClient.On("Reboot").Return(sshclient.Output{})
 	sshClient.On("GetCloudInitOutput").Return(sshclient.Output{StdOut: "dummy content of /var/log/cloud-init-output.log"})
 	sshClient.On("DetectLinuxOnAnotherDisk", mock.Anything).Return(sshclient.Output{})
diff --git a/controllers/hetznerbaremetalmachine_controller_test.go b/controllers/hetznerbaremetalmachine_controller_test.go
index 00af20fc..b3769b6a 100644
--- a/controllers/hetznerbaremetalmachine_controller_test.go
+++ b/controllers/hetznerbaremetalmachine_controller_test.go
@@ -148,10 +148,6 @@ var _ = Describe("HetznerBareMetalMachineReconciler", func() {
 		configureRescueSSHClient(rescueSSHClient)
 
 		osSSHClient.On("Reboot").Return(sshclient.Output{})
-		osSSHClient.On("CreateNoCloudDirectory").Return(sshclient.Output{})
-		osSSHClient.On("CreateMetaData", mock.Anything).Return(sshclient.Output{})
-		osSSHClient.On("CreateUserData", mock.Anything).Return(sshclient.Output{})
-		osSSHClient.On("EnsureCloudInit").Return(sshclient.Output{StdOut: "cloud-init"})
 		osSSHClient.On("CloudInitStatus").Return(sshclient.Output{StdOut: "status: done"})
 		osSSHClient.On("CheckCloudInitLogsForSigTerm").Return(sshclient.Output{})
 		osSSHClient.On("ResetKubeadm").Return(sshclient.Output{})
diff --git a/controllers/hetznerbaremetalremediation_controller_test.go b/controllers/hetznerbaremetalremediation_controller_test.go
index e77bf665..d7599c0f 100644
--- a/controllers/hetznerbaremetalremediation_controller_test.go
+++ b/controllers/hetznerbaremetalremediation_controller_test.go
@@ -207,10 +207,6 @@ var _ = Describe("HetznerBareMetalRemediationReconciler", func() {
 		configureRescueSSHClient(rescueSSHClient)
 
 		osSSHClientAfterInstallImage.On("Reboot").Return(sshclient.Output{})
-		osSSHClientAfterInstallImage.On("CreateNoCloudDirectory").Return(sshclient.Output{})
-		osSSHClientAfterInstallImage.On("CreateMetaData", mock.Anything).Return(sshclient.Output{})
-		osSSHClientAfterInstallImage.On("CreateUserData", mock.Anything).Return(sshclient.Output{})
-		osSSHClientAfterInstallImage.On("EnsureCloudInit").Return(sshclient.Output{StdOut: "cloud-init"})
 		osSSHClientAfterInstallImage.On("CloudInitStatus").Return(sshclient.Output{StdOut: "status: done"})
 		osSSHClientAfterInstallImage.On("CheckCloudInitLogsForSigTerm").Return(sshclient.Output{})
 		osSSHClientAfterInstallImage.On("ResetKubeadm").Return(sshclient.Output{})
diff --git a/pkg/services/baremetal/baremetal/baremetal_test.go b/pkg/services/baremetal/baremetal/baremetal_test.go
index 5d652f63..6773a5a4 100644
--- a/pkg/services/baremetal/baremetal/baremetal_test.go
+++ b/pkg/services/baremetal/baremetal/baremetal_test.go
@@ -337,7 +337,7 @@ var _ = Describe("chooseHost", func() {
 			testCaseChooseHostWithReason{
 				hosts:            []client.Object{&hostWithNonRaidWwnConfig},
 				expectedHostName: "",
-				expectedReason:   "No available host of 1 found: machine-should-use-swraid-but-only-0-RAID-WWN-in-hbmh: 1",
+				expectedReason:   "No available host of 1 found: machine-should-use-swraid-but-not-enough-RAID-WWNs-in-hbmh: 1",
 				swraid:           1,
 			}),
 		Entry("No host, because invalid RAID config (want no RAID)",
diff --git a/pkg/services/baremetal/client/ssh/ssh_client_test.go b/pkg/services/baremetal/client/ssh/ssh_client_test.go
index 55e7da57..6281269f 100644
--- a/pkg/services/baremetal/client/ssh/ssh_client_test.go
+++ b/pkg/services/baremetal/client/ssh/ssh_client_test.go
@@ -17,7 +17,13 @@ limitations under the License.
 // Package sshclient contains the interface to speak to bare metal servers with ssh.
 package sshclient
 
-import "testing"
+import (
+	_ "embed"
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
 
 func Test_removeUselessLinesFromCloudInitOutput(t *testing.T) {
 	tests := []struct {
@@ -49,3 +55,50 @@ func Test_removeUselessLinesFromCloudInitOutput(t *testing.T) {
 		})
 	}
 }
+
+func TestOutput_String(t *testing.T) {
+	f := func(o Output, wantString string) {
+		require.Equal(t, wantString, o.String())
+	}
+	f(Output{
+		StdOut: "",
+		StdErr: "",
+		Err:    nil,
+	}, "")
+
+	f(Output{
+		StdOut: " mystdout ",
+		StdErr: "",
+		Err:    nil,
+	}, "mystdout")
+
+	f(Output{
+		StdOut: "",
+		StdErr: " mystderr ",
+		Err:    nil,
+	}, "mystderr")
+
+	f(Output{
+		StdOut: "",
+		StdErr: "",
+		Err:    fmt.Errorf(" some err "),
+	}, "some err")
+
+	f(Output{
+		StdOut: "mystdout",
+		StdErr: "",
+		Err:    fmt.Errorf("some err"),
+	}, "mystdout. Err: some err")
+
+	f(Output{
+		StdOut: "",
+		StdErr: "mystderr",
+		Err:    fmt.Errorf("some err"),
+	}, "mystderr. Err: some err")
+
+	f(Output{
+		StdOut: "mystdout",
+		StdErr: "mystderr",
+		Err:    fmt.Errorf("some err"),
+	}, "mystdout. Stderr: mystderr. Err: some err")
+}
diff --git a/pkg/services/baremetal/host/host_test.go b/pkg/services/baremetal/host/host_test.go
index aaf7ffd9..4fb8021c 100644
--- a/pkg/services/baremetal/host/host_test.go
+++ b/pkg/services/baremetal/host/host_test.go
@@ -17,6 +17,7 @@ limitations under the License.
 package host
 
 import (
+	"context"
 	"fmt"
 	"time"
 
@@ -1120,7 +1121,7 @@ var _ = Describe("actionRegistering", func() {
 		expectedErrorMessage      *string
 		swRaid                    bool
 	}
-
+	ctx := context.Background()
 	DescribeTable("actionRegistering",
 		func(tc testCaseActionRegistering) {
 			var host *infrav1.HetznerBareMetalHost
@@ -1155,7 +1156,7 @@ var _ = Describe("actionRegistering", func() {
 			sshMock := registeringSSHMock(tc.storageStdOut)
 			service := newTestService(host, nil, bmmock.NewSSHFactory(sshMock, sshMock, sshMock), nil, helpers.GetDefaultSSHSecret(rescueSSHKeyName, "default"))
 
-			actResult := service.actionRegistering()
+			actResult := service.actionRegistering(ctx)
 			Expect(host.Spec.Status.HardwareDetails).ToNot(BeNil())
 			if tc.expectedErrorMessage != nil {
 				Expect(host.Spec.Status.ErrorMessage).To(Equal(*tc.expectedErrorMessage))
@@ -1231,7 +1232,7 @@ var _ = Describe("actionRegistering", func() {
 
 			service := newTestService(host, &robotMock, bmmock.NewSSHFactory(sshMock, sshMock, sshMock), nil, helpers.GetDefaultSSHSecret(rescueSSHKeyName, "default"))
 
-			actResult := service.actionRegistering()
+			actResult := service.actionRegistering(ctx)
 			Expect(actResult).Should(BeAssignableToTypeOf(actionContinue{}))
 			if tc.expectedErrorType != infrav1.ErrorType("") {
 				Expect(host.Spec.Status.ErrorType).To(Equal(tc.expectedErrorType))
@@ -1270,6 +1271,7 @@ name="eth0" model="Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express
 }
 
 var _ = Describe("actionRegistering check RAID", func() {
+	ctx := context.Background()
 	It("check RAID", func() {
 		sshMock := registeringSSHMock(`NAME="nvme2n1" TYPE="disk" MODEL="mymode." VENDOR="" SIZE="3068773888" WWN="wwn1" ROTA="0"
 		NAME="nvme2n2" TYPE="disk" MODEL="mymodel" VENDOR="" SIZE="3068773888" WWN="wwn2" ROTA="0"`)
@@ -1285,7 +1287,7 @@ var _ = Describe("actionRegistering check RAID", func() {
 		}
 		service := newTestService(host, nil, bmmock.NewSSHFactory(
 			sshMock, sshMock, sshMock), nil, helpers.GetDefaultSSHSecret(rescueSSHKeyName, "default"))
-		actResult := service.actionRegistering()
+		actResult := service.actionRegistering(ctx)
 
 		_, err := actResult.Result()
 		Expect(err).Should(BeNil())
@@ -1294,7 +1296,7 @@ var _ = Describe("actionRegistering check RAID", func() {
 		host.Spec.Status.InstallImage.Swraid = 0
 		host.Spec.RootDeviceHints.WWN = ""
 		host.Spec.RootDeviceHints.Raid.WWN = []string{"wwn1", "wwn2"}
-		actResult = service.actionRegistering()
+		actResult = service.actionRegistering(ctx)
 
 		_, err = actResult.Result()
 		Expect(err).Should(BeNil())
@@ -1390,6 +1392,7 @@ var _ = Describe("actionEnsureProvisioned", func() {
 
 	DescribeTable("actionEnsureProvisioned",
 		func(in testCaseActionEnsureProvisioned) {
+			ctx := context.Background()
 			var (
 				portAfterCloudInit    = 24
 				portAfterInstallImage = 23
@@ -1427,7 +1430,7 @@ var _ = Describe("actionEnsureProvisioned", func() {
 
 			service := newTestService(host, &robotMock, bmmock.NewSSHFactory(sshMock, oldSSHMock, sshMock), helpers.GetDefaultSSHSecret(osSSHKeyName, "default"), nil)
 
-			actResult := service.actionEnsureProvisioned()
+			actResult := service.actionEnsureProvisioned(ctx)
 			Expect(actResult).Should(BeAssignableToTypeOf(in.expectedActionResult))
 			if in.expectedErrorType != infrav1.ErrorType("") {
 				Expect(host.Spec.Status.ErrorType).To(Equal(in.expectedErrorType))
@@ -1748,6 +1751,7 @@ var _ = Describe("actionProvisioned", func() {
 
 	DescribeTable("actionProvisioned",
 		func(tc testCaseActionProvisioned) {
+			ctx := context.Background()
 			host := helpers.BareMetalHost(
 				"test-host",
 				"default",
@@ -1774,7 +1778,7 @@ var _ = Describe("actionProvisioned", func() {
 
 			service := newTestService(host, nil, bmmock.NewSSHFactory(sshMock, sshMock, sshMock), helpers.GetDefaultSSHSecret(osSSHKeyName, "default"), helpers.GetDefaultSSHSecret(rescueSSHKeyName, "default"))
 
-			actResult := service.actionProvisioned()
+			actResult := service.actionProvisioned(ctx)
 			Expect(actResult).Should(BeAssignableToTypeOf(tc.expectedActionResult))
 			Expect(host.Spec.Status.Rebooted).To(Equal(tc.expectRebootInStatus))
 			Expect(host.HasRebootAnnotation()).To(Equal(tc.expectRebootAnnotation))
diff --git a/pkg/services/baremetal/host/state_machine_test.go b/pkg/services/baremetal/host/state_machine_test.go
index a2b5819b..50453ecb 100644
--- a/pkg/services/baremetal/host/state_machine_test.go
+++ b/pkg/services/baremetal/host/state_machine_test.go
@@ -189,7 +189,7 @@ var _ = Describe("updateSSHKey", func() {
 				"sshkey-name": []byte("my-name"),
 				"public-key":  []byte("my-public-key"),
 			},
-			currentState:         infrav1.StateProvisioning,
+			currentState:         infrav1.StateImageInstalling,
 			expectedActionResult: actionComplete{},
 			expectedNextState:    infrav1.StateImageInstalling,
 			expectedOSSecretData: map[string][]byte{
@@ -203,31 +203,6 @@ var _ = Describe("updateSSHKey", func() {
 				"public-key":  []byte("my-public-key"),
 			},
 		}),
-		Entry("rescue secret changed - state provisioning", testCaseUpdateSSHKey{
-			osSecretData: map[string][]byte{
-				"private-key": []byte(fmt.Sprintf("%s-private-key", osSSHKeyName)),
-				"sshkey-name": []byte("my-name"),
-				"public-key":  []byte("my-public-key"),
-			},
-			rescueSecretData: map[string][]byte{
-				"private-key": []byte(fmt.Sprintf("%s-private-key", rescueSSHKeyName)),
-				"sshkey-name": []byte("my-old-name"),
-				"public-key":  []byte("my-old-public-key"),
-			},
-			currentState:         infrav1.StateProvisioning,
-			expectedActionResult: actionComplete{},
-			expectedNextState:    infrav1.StateProvisioning,
-			expectedOSSecretData: map[string][]byte{
-				"private-key": []byte(fmt.Sprintf("%s-private-key", osSSHKeyName)),
-				"sshkey-name": []byte("my-name"),
-				"public-key":  []byte("my-public-key"),
-			},
-			expectedRescueSecretData: map[string][]byte{
-				"private-key": []byte(fmt.Sprintf("%s-private-key", rescueSSHKeyName)),
-				"sshkey-name": []byte("my-name"),
-				"public-key":  []byte("my-public-key"),
-			},
-		}),
 		Entry("rescue secret changed - state available", testCaseUpdateSSHKey{
 			osSecretData: map[string][]byte{
 				"private-key": []byte(fmt.Sprintf("%s-private-key", osSSHKeyName)),
diff --git a/test/e2e/data/infrastructure-hetzner/v1beta1/bases/hetznerbaremetalhosts.yaml b/test/e2e/data/infrastructure-hetzner/v1beta1/bases/hetznerbaremetalhosts.yaml
index ef0e35c1..9689ca78 100644
--- a/test/e2e/data/infrastructure-hetzner/v1beta1/bases/hetznerbaremetalhosts.yaml
+++ b/test/e2e/data/infrastructure-hetzner/v1beta1/bases/hetznerbaremetalhosts.yaml
@@ -8,20 +8,17 @@ spec:
     wwn: "0x5002538c0004413f"
   maintenanceMode: false
   description: Test Machine 1670788
-
-# ---
-# Broken 2024-07-31
-# apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-# kind: HetznerBareMetalHost
-# metadata:
-#   name: "bm-e2e-1716772"
-# spec:
-#   serverID: 1716772
-#   rootDeviceHints:
-#     wwn: "0x500a07510fe0ce34"
-#   maintenanceMode: false
-#   description: Test Machine 1716772
-
+---
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+kind: HetznerBareMetalHost
+metadata:
+  name: "bm-e2e-1716772"
+spec:
+  serverID: 1716772
+  rootDeviceHints:
+    wwn: "0x500a07510fe0ce34"
+  maintenanceMode: false
+  description: Test Machine 1716772
 ---
 apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
 kind: HetznerBareMetalHost