diff --git a/internal/controllers/topology/cluster/reconcile_state.go b/internal/controllers/topology/cluster/reconcile_state.go index db47382ba07c..790fff77db19 100644 --- a/internal/controllers/topology/cluster/reconcile_state.go +++ b/internal/controllers/topology/cluster/reconcile_state.go @@ -230,6 +230,20 @@ func (r *Reconciler) reconcileControlPlane(ctx context.Context, s *scope.Scope) return err } + // If the controlPlane has infrastructureMachines and the InfrastructureMachineTemplate has changed on this reconcile + // delete the old template. + // This is a best effort deletion only and may leak templates if an error occurs during reconciliation. + if s.Blueprint.HasControlPlaneInfrastructureMachine() && s.Current.ControlPlane.InfrastructureMachineTemplate != nil { + if s.Current.ControlPlane.InfrastructureMachineTemplate.GetName() != s.Desired.ControlPlane.InfrastructureMachineTemplate.GetName() { + if err := r.Client.Delete(ctx, s.Current.ControlPlane.InfrastructureMachineTemplate); err != nil { + return errors.Wrapf(err, "failed to delete oldinfrastructure machine template %s of control plane %s", + tlog.KObj{Obj: s.Current.ControlPlane.InfrastructureMachineTemplate}, + tlog.KObj{Obj: s.Current.ControlPlane.Object}, + ) + } + } + } + // If the ControlPlane has defined a current or desired MachineHealthCheck attempt to reconcile it. if s.Desired.ControlPlane.MachineHealthCheck != nil || s.Current.ControlPlane.MachineHealthCheck != nil { // Set the ControlPlane Object and the Cluster as owners for the MachineHealthCheck to ensure object garbage collection diff --git a/internal/controllers/topology/cluster/reconcile_state_test.go b/internal/controllers/topology/cluster/reconcile_state_test.go index b2f518fd6cc9..8695c1a17c39 100644 --- a/internal/controllers/topology/cluster/reconcile_state_test.go +++ b/internal/controllers/topology/cluster/reconcile_state_test.go @@ -734,6 +734,18 @@ func TestReconcileControlPlaneInfrastructureMachineTemplate(t *testing.T) { for k, v := range tt.want.InfrastructureMachineTemplate.GetLabels() { g.Expect(gotInfrastructureMachineTemplate.GetLabels()).To(HaveKeyWithValue(k, v)) } + + // If the template was rotated during the reconcile we want to make sure the old template was deleted. + if tt.current.InfrastructureMachineTemplate != nil && tt.current.InfrastructureMachineTemplate.GetName() != tt.desired.InfrastructureMachineTemplate.GetName() { + obj := &unstructured.Unstructured{} + obj.SetAPIVersion(builder.InfrastructureGroupVersion.String()) + obj.SetKind(builder.GenericInfrastructureMachineTemplateKind) + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: tt.current.InfrastructureMachineTemplate.GetNamespace(), + Name: tt.current.InfrastructureMachineTemplate.GetName(), + }, obj) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) + } }) } }