Skip to content

Commit c53db05

Browse files
authored
Merge pull request #12038 from sbueringer/pr-machine-controller-contract
✨ Implement v1beta2 contract in Machine controller
2 parents 1e26d40 + 25c5922 commit c53db05

File tree

16 files changed

+517
-117
lines changed

16 files changed

+517
-117
lines changed

controlplane/kubeadm/internal/controllers/helpers.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"sigs.k8s.io/cluster-api/controllers/external"
3838
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
3939
"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal"
40+
"sigs.k8s.io/cluster-api/internal/contract"
4041
topologynames "sigs.k8s.io/cluster-api/internal/topology/names"
4142
"sigs.k8s.io/cluster-api/internal/util/ssa"
4243
"sigs.k8s.io/cluster-api/util"
@@ -151,7 +152,7 @@ func (r *KubeadmControlPlaneReconciler) reconcileExternalReference(ctx context.C
151152
return nil
152153
}
153154

154-
if err := utilconversion.UpdateReferenceAPIContract(ctx, r.Client, ref); err != nil {
155+
if err := utilconversion.UpdateReferenceAPIContract(ctx, r.Client, ref, contract.Version); err != nil {
155156
return err
156157
}
157158

exp/internal/controllers/machinepool_controller_phases.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
capierrors "sigs.k8s.io/cluster-api/errors"
4242
expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
4343
utilexp "sigs.k8s.io/cluster-api/exp/util"
44+
"sigs.k8s.io/cluster-api/internal/contract"
4445
"sigs.k8s.io/cluster-api/internal/util/ssa"
4546
"sigs.k8s.io/cluster-api/util"
4647
"sigs.k8s.io/cluster-api/util/annotations"
@@ -110,7 +111,7 @@ func (r *MachinePoolReconciler) reconcilePhase(mp *expv1.MachinePool) {
110111
func (r *MachinePoolReconciler) reconcileExternal(ctx context.Context, m *expv1.MachinePool, ref *corev1.ObjectReference) (external.ReconcileOutput, error) {
111112
log := ctrl.LoggerFrom(ctx)
112113

113-
if err := utilconversion.UpdateReferenceAPIContract(ctx, r.Client, ref); err != nil {
114+
if err := utilconversion.UpdateReferenceAPIContract(ctx, r.Client, ref, contract.Version); err != nil {
114115
return external.ReconcileOutput{}, err
115116
}
116117

internal/contract/bootstrap.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ func (b *BootstrapContract) Ready() *Bool {
3939
}
4040
}
4141

42+
// DataSecretCreated returns if the data secret has been created.
43+
func (b *BootstrapContract) DataSecretCreated(contractVersion string) *Bool {
44+
if contractVersion == "v1beta1" {
45+
return &Bool{
46+
path: []string{"status", "ready"},
47+
}
48+
}
49+
50+
return &Bool{
51+
path: []string{"status", "initialization", "dataSecretCreated"},
52+
}
53+
}
54+
4255
// ReadyConditionType returns the type of the ready condition.
4356
func (b *BootstrapContract) ReadyConditionType() string {
4457
return "Ready" //nolint:goconst // Not making this a constant for now

internal/contract/infrastructure_machine.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ func (m *InfrastructureMachineContract) Ready() *Bool {
4949
}
5050
}
5151

52+
// Provisioned returns if the InfrastructureMachine is provisioned.
53+
func (m *InfrastructureMachineContract) Provisioned(contractVersion string) *Bool {
54+
if contractVersion == "v1beta1" {
55+
return &Bool{
56+
path: []string{"status", "ready"},
57+
}
58+
}
59+
60+
return &Bool{
61+
path: []string{"status", "initialization", "provisioned"},
62+
}
63+
}
64+
5265
// ReadyConditionType returns the type of the ready condition.
5366
func (m *InfrastructureMachineContract) ReadyConditionType() string {
5467
return "Ready"

internal/controllers/cluster/cluster_controller_phases.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
3737
"sigs.k8s.io/cluster-api/controllers/external"
3838
capierrors "sigs.k8s.io/cluster-api/errors"
39+
"sigs.k8s.io/cluster-api/internal/contract"
3940
"sigs.k8s.io/cluster-api/util"
4041
"sigs.k8s.io/cluster-api/util/conditions"
4142
utilconversion "sigs.k8s.io/cluster-api/util/conversion"
@@ -85,7 +86,7 @@ func (r *Reconciler) reconcilePhase(_ context.Context, cluster *clusterv1.Cluste
8586
func (r *Reconciler) reconcileExternal(ctx context.Context, cluster *clusterv1.Cluster, ref *corev1.ObjectReference) (*unstructured.Unstructured, error) {
8687
log := ctrl.LoggerFrom(ctx)
8788

88-
if err := utilconversion.UpdateReferenceAPIContract(ctx, r.Client, ref); err != nil {
89+
if err := utilconversion.UpdateReferenceAPIContract(ctx, r.Client, ref, contract.Version); err != nil {
8990
if apierrors.IsNotFound(err) {
9091
// We want to surface the NotFound error only for the referenced object, so we use a generic error in case CRD is not found.
9192
return nil, errors.New(err.Error())

internal/controllers/clusterclass/clusterclass_controller.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import (
4747
runtimeclient "sigs.k8s.io/cluster-api/exp/runtime/client"
4848
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
4949
"sigs.k8s.io/cluster-api/feature"
50+
"sigs.k8s.io/cluster-api/internal/contract"
5051
internalruntimeclient "sigs.k8s.io/cluster-api/internal/runtime/client"
5152
"sigs.k8s.io/cluster-api/internal/topology/variables"
5253
"sigs.k8s.io/cluster-api/util"
@@ -273,7 +274,7 @@ func (r *Reconciler) reconcileExternalReferences(ctx context.Context, s *scope)
273274
// Check if the template reference is outdated, i.e. it is not using the latest apiVersion
274275
// for the current CAPI contract.
275276
updatedRef := ref.DeepCopy()
276-
if err := conversion.UpdateReferenceAPIContract(ctx, r.Client, updatedRef); err != nil {
277+
if err := conversion.UpdateReferenceAPIContract(ctx, r.Client, updatedRef, contract.Version); err != nil {
277278
errs = append(errs, err)
278279
}
279280
if ref.GroupVersionKind().Version != updatedRef.GroupVersionKind().Version {

internal/controllers/machine/machine_controller.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import (
5353
"sigs.k8s.io/cluster-api/controllers/external"
5454
"sigs.k8s.io/cluster-api/controllers/noderefutil"
5555
"sigs.k8s.io/cluster-api/feature"
56+
"sigs.k8s.io/cluster-api/internal/contract"
5657
"sigs.k8s.io/cluster-api/internal/controllers/machine/drain"
5758
"sigs.k8s.io/cluster-api/util"
5859
"sigs.k8s.io/cluster-api/util/annotations"
@@ -117,6 +118,10 @@ type Reconciler struct {
117118
reconcileDeleteCache cache.Cache[cache.ReconcileEntry]
118119

119120
predicateLog *logr.Logger
121+
122+
// currentContractVersion should only be used in unit tests to test behavior with other
123+
// than the current contract versions. Defaults to contract.Version.
124+
currentContractVersion string
120125
}
121126

122127
func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
@@ -185,6 +190,7 @@ func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opt
185190
PredicateLogger: r.predicateLog,
186191
}
187192
r.reconcileDeleteCache = cache.New[cache.ReconcileEntry](cache.DefaultTTL)
193+
r.currentContractVersion = contract.Version
188194
return nil
189195
}
190196

internal/controllers/machine/machine_controller_phases.go

Lines changed: 84 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
3737
"sigs.k8s.io/cluster-api/controllers/external"
3838
capierrors "sigs.k8s.io/cluster-api/errors"
39+
"sigs.k8s.io/cluster-api/internal/contract"
3940
"sigs.k8s.io/cluster-api/util"
4041
"sigs.k8s.io/cluster-api/util/conditions"
4142
utilconversion "sigs.k8s.io/cluster-api/util/conversion"
@@ -47,7 +48,7 @@ var externalReadyWait = 30 * time.Second
4748

4849
// reconcileExternal handles generic unstructured objects referenced by a Machine.
4950
func (r *Reconciler) reconcileExternal(ctx context.Context, cluster *clusterv1.Cluster, m *clusterv1.Machine, ref *corev1.ObjectReference) (*unstructured.Unstructured, error) {
50-
if err := utilconversion.UpdateReferenceAPIContract(ctx, r.Client, ref); err != nil {
51+
if err := utilconversion.UpdateReferenceAPIContract(ctx, r.Client, ref, r.currentContractVersion); err != nil {
5152
if apierrors.IsNotFound(err) {
5253
// We want to surface the NotFound error only for the referenced object, so we use a generic error in case CRD is not found.
5354
return nil, errors.New(err.Error())
@@ -143,7 +144,7 @@ func (r *Reconciler) ensureExternalOwnershipAndWatch(ctx context.Context, cluste
143144
return obj, nil
144145
}
145146

146-
// reconcileBootstrap reconciles the Spec.Bootstrap.ConfigRef object on a Machine.
147+
// reconcileBootstrap reconciles the BootstrapConfig of a Machine.
147148
func (r *Reconciler) reconcileBootstrap(ctx context.Context, s *scope) (ctrl.Result, error) {
148149
log := ctrl.LoggerFrom(ctx)
149150
cluster := s.cluster
@@ -180,48 +181,59 @@ func (r *Reconciler) reconcileBootstrap(ctx context.Context, s *scope) (ctrl.Res
180181
return ctrl.Result{}, nil
181182
}
182183

183-
// Determine if the bootstrap provider is ready.
184-
ready, err := external.IsReady(s.bootstrapConfig)
184+
// Determine contract version used by the BootstrapConfig.
185+
contractVersion, err := utilconversion.GetContractVersion(ctx, r.Client, s.bootstrapConfig.GroupVersionKind(), r.currentContractVersion)
186+
if err != nil {
187+
return ctrl.Result{}, err
188+
}
189+
190+
// Determine if the data secret was created.
191+
dataSecretCreated, err := contract.Bootstrap().DataSecretCreated(contractVersion).Get(s.bootstrapConfig)
185192
if err != nil {
186193
return ctrl.Result{}, err
187194
}
188195

189196
// Report a summary of current status of the bootstrap object defined for this machine.
190-
fallBack := conditions.WithFallbackValue(ready, clusterv1.WaitingForDataSecretFallbackReason, clusterv1.ConditionSeverityInfo, "")
197+
fallBack := conditions.WithFallbackValue(*dataSecretCreated, clusterv1.WaitingForDataSecretFallbackReason, clusterv1.ConditionSeverityInfo, "")
191198
if !s.machine.DeletionTimestamp.IsZero() {
192-
fallBack = conditions.WithFallbackValue(ready, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
199+
fallBack = conditions.WithFallbackValue(*dataSecretCreated, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
193200
}
194-
conditions.SetMirror(m, clusterv1.BootstrapReadyCondition,
195-
conditions.UnstructuredGetter(s.bootstrapConfig),
196-
fallBack,
197-
)
201+
conditions.SetMirror(m, clusterv1.BootstrapReadyCondition, conditions.UnstructuredGetter(s.bootstrapConfig), fallBack)
198202

199203
if !s.bootstrapConfig.GetDeletionTimestamp().IsZero() {
200204
return ctrl.Result{}, nil
201205
}
202206

203-
// If the bootstrap provider is not ready, return.
204-
if !ready {
205-
log.Info("Waiting for bootstrap provider to generate data secret and report status.ready", s.bootstrapConfig.GetKind(), klog.KObj(s.bootstrapConfig))
207+
// If the data secret was not created yet, return.
208+
if !*dataSecretCreated {
209+
log.Info(fmt.Sprintf("Waiting for bootstrap provider to generate data secret and set %s",
210+
contract.Bootstrap().DataSecretCreated(contractVersion).Path().String()),
211+
s.bootstrapConfig.GetKind(), klog.KObj(s.bootstrapConfig))
206212
return ctrl.Result{}, nil
207213
}
208214

209-
// Get and set the name of the secret containing the bootstrap data.
210-
secretName, _, err := unstructured.NestedString(s.bootstrapConfig.Object, "status", "dataSecretName")
211-
if err != nil {
212-
return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve dataSecretName from bootstrap provider for Machine %q in namespace %q", m.Name, m.Namespace)
213-
} else if secretName == "" {
214-
return ctrl.Result{}, errors.Errorf("retrieved empty dataSecretName from bootstrap provider for Machine %q in namespace %q", m.Name, m.Namespace)
215+
// Get and set the dataSecretName containing the bootstrap data.
216+
secretName, err := contract.Bootstrap().DataSecretName().Get(s.bootstrapConfig)
217+
switch {
218+
case err != nil:
219+
return ctrl.Result{}, errors.Wrapf(err, "failed to read dataSecretName from %s %s",
220+
s.bootstrapConfig.GetKind(), klog.KObj(s.bootstrapConfig))
221+
case *secretName == "":
222+
return ctrl.Result{}, errors.Errorf("got empty %s field from %s %s",
223+
contract.Bootstrap().DataSecretName().Path().String(),
224+
s.bootstrapConfig.GetKind(), klog.KObj(s.bootstrapConfig))
225+
default:
226+
m.Spec.Bootstrap.DataSecretName = secretName
215227
}
216-
m.Spec.Bootstrap.DataSecretName = ptr.To(secretName)
228+
217229
if !m.Status.BootstrapReady {
218-
log.Info("Bootstrap provider generated data secret and reports status.ready", s.bootstrapConfig.GetKind(), klog.KObj(s.bootstrapConfig), "Secret", klog.KRef(m.Namespace, secretName))
230+
log.Info("Bootstrap provider generated data secret", s.bootstrapConfig.GetKind(), klog.KObj(s.bootstrapConfig), "Secret", klog.KRef(m.Namespace, *secretName))
219231
}
220232
m.Status.BootstrapReady = true
221233
return ctrl.Result{}, nil
222234
}
223235

224-
// reconcileInfrastructure reconciles the Spec.InfrastructureRef object on a Machine.
236+
// reconcileInfrastructure reconciles the InfrastructureMachine of a Machine.
225237
func (r *Reconciler) reconcileInfrastructure(ctx context.Context, s *scope) (ctrl.Result, error) {
226238
log := ctrl.LoggerFrom(ctx)
227239
cluster := s.cluster
@@ -240,77 +252,90 @@ func (r *Reconciler) reconcileInfrastructure(ctx context.Context, s *scope) (ctr
240252

241253
if m.Status.InfrastructureReady {
242254
// Infra object went missing after the machine was up and running
243-
log.Error(err, "Machine infrastructure reference has been deleted after being ready, setting failure state")
255+
log.Error(err, "Machine infrastructure reference has been deleted after provisioning was completed, setting failure state")
244256
m.Status.FailureReason = ptr.To(capierrors.InvalidConfigurationMachineError)
245-
m.Status.FailureMessage = ptr.To(fmt.Sprintf("Machine infrastructure resource %v with name %q has been deleted after being ready",
257+
m.Status.FailureMessage = ptr.To(fmt.Sprintf("Machine infrastructure resource %v with name %q has been deleted after provisioning was completed",
246258
m.Spec.InfrastructureRef.GroupVersionKind(), m.Spec.InfrastructureRef.Name))
247259
return ctrl.Result{}, errors.Errorf("could not find %v %q for Machine %q in namespace %q", m.Spec.InfrastructureRef.GroupVersionKind().String(), m.Spec.InfrastructureRef.Name, m.Name, m.Namespace)
248260
}
249-
log.Info("Could not find infrastructure machine, requeuing", m.Spec.InfrastructureRef.Kind, klog.KRef(m.Spec.InfrastructureRef.Namespace, m.Spec.InfrastructureRef.Name))
261+
log.Info("Could not find InfrastructureMachine, requeuing", m.Spec.InfrastructureRef.Kind, klog.KRef(m.Spec.InfrastructureRef.Namespace, m.Spec.InfrastructureRef.Name))
250262
return ctrl.Result{RequeueAfter: externalReadyWait}, nil
251263
}
252264
return ctrl.Result{}, err
253265
}
254266
s.infraMachine = obj
255267

256-
// Determine if the infrastructure provider is ready.
257-
ready, err := external.IsReady(s.infraMachine)
268+
// Determine contract version used by the InfraMachine.
269+
contractVersion, err := utilconversion.GetContractVersion(ctx, r.Client, s.infraMachine.GroupVersionKind(), r.currentContractVersion)
270+
if err != nil {
271+
return ctrl.Result{}, err
272+
}
273+
274+
// Determine if the InfrastructureMachine is provisioned.
275+
provisioned, err := contract.InfrastructureMachine().Provisioned(contractVersion).Get(s.infraMachine)
258276
if err != nil {
259277
return ctrl.Result{}, err
260278
}
261-
if ready && !m.Status.InfrastructureReady {
262-
log.Info("Infrastructure provider has completed machine infrastructure provisioning and reports status.ready", s.infraMachine.GetKind(), klog.KObj(s.infraMachine))
279+
if *provisioned && !m.Status.InfrastructureReady {
280+
log.Info("Infrastructure provider has completed provisioning", s.infraMachine.GetKind(), klog.KObj(s.infraMachine))
263281
}
264282

265-
// Report a summary of current status of the infrastructure object defined for this machine.
266-
fallBack := conditions.WithFallbackValue(ready, clusterv1.WaitingForInfrastructureFallbackReason, clusterv1.ConditionSeverityInfo, "")
283+
// Report a summary of current status of the InfrastructureMachine for this Machine.
284+
fallBack := conditions.WithFallbackValue(*provisioned, clusterv1.WaitingForInfrastructureFallbackReason, clusterv1.ConditionSeverityInfo, "")
267285
if !s.machine.DeletionTimestamp.IsZero() {
268-
fallBack = conditions.WithFallbackValue(ready, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
286+
fallBack = conditions.WithFallbackValue(*provisioned, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
269287
}
270-
conditions.SetMirror(m, clusterv1.InfrastructureReadyCondition,
271-
conditions.UnstructuredGetter(s.infraMachine),
272-
fallBack,
273-
)
288+
conditions.SetMirror(m, clusterv1.InfrastructureReadyCondition, conditions.UnstructuredGetter(s.infraMachine), fallBack)
274289

275290
if !s.infraMachine.GetDeletionTimestamp().IsZero() {
276291
return ctrl.Result{}, nil
277292
}
278293

279-
// If the infrastructure provider is not ready (and it wasn't ready before), return early.
280-
if !ready && !m.Status.InfrastructureReady {
281-
log.Info("Waiting for infrastructure provider to create machine infrastructure and report status.ready", s.infraMachine.GetKind(), klog.KObj(s.infraMachine))
294+
// If the InfrastructureMachine is not provisioned (and it wasn't already provisioned before), return.
295+
if !*provisioned && !m.Status.InfrastructureReady {
296+
log.Info(fmt.Sprintf("Waiting for infrastructure provider to create machine infrastructure and set %s",
297+
contract.InfrastructureMachine().Provisioned(contractVersion).Path().String()),
298+
s.infraMachine.GetKind(), klog.KObj(s.infraMachine))
282299
return ctrl.Result{}, nil
283300
}
284301

285-
// Get Spec.ProviderID from the infrastructure provider.
286-
var providerID string
287-
if err := util.UnstructuredUnmarshalField(s.infraMachine, &providerID, "spec", "providerID"); err != nil {
288-
return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve Spec.ProviderID from infrastructure provider for Machine %q in namespace %q", m.Name, m.Namespace)
289-
} else if providerID == "" {
290-
return ctrl.Result{}, errors.Errorf("retrieved empty Spec.ProviderID from infrastructure provider for Machine %q in namespace %q", m.Name, m.Namespace)
302+
// Get providerID from the InfrastructureMachine (intentionally not setting it on the Machine yet).
303+
var providerID *string
304+
if providerID, err = contract.InfrastructureMachine().ProviderID().Get(s.infraMachine); err != nil {
305+
return ctrl.Result{}, errors.Wrapf(err, "failed to read providerID from %s %s",
306+
s.infraMachine.GetKind(), klog.KObj(s.infraMachine))
307+
} else if *providerID == "" {
308+
return ctrl.Result{}, errors.Errorf("got empty %s field from %s %s",
309+
contract.InfrastructureMachine().ProviderID().Path().String(),
310+
s.infraMachine.GetKind(), klog.KObj(s.infraMachine))
291311
}
292312

293-
// Get and set Status.Addresses from the infrastructure provider.
294-
err = util.UnstructuredUnmarshalField(s.infraMachine, &m.Status.Addresses, "status", "addresses")
295-
if err != nil && err != util.ErrUnstructuredFieldNotFound {
296-
return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve addresses from infrastructure provider for Machine %q in namespace %q", m.Name, m.Namespace)
313+
// Get and set addresses from the InfrastructureMachine.
314+
addresses, err := contract.InfrastructureMachine().Addresses().Get(s.infraMachine)
315+
switch {
316+
case errors.Is(err, contract.ErrFieldNotFound): // no-op
317+
case err != nil:
318+
return ctrl.Result{}, errors.Wrapf(err, "failed to read addresses from %s %s",
319+
s.infraMachine.GetKind(), klog.KObj(s.infraMachine))
320+
default:
321+
m.Status.Addresses = *addresses
297322
}
298323

299-
// Get and set the failure domain from the infrastructure provider.
300-
var failureDomain string
301-
err = util.UnstructuredUnmarshalField(s.infraMachine, &failureDomain, "spec", "failureDomain")
324+
// Get and set failureDomain from the InfrastructureMachine.
325+
failureDomain, err := contract.InfrastructureMachine().FailureDomain().Get(s.infraMachine)
302326
switch {
303-
case err == util.ErrUnstructuredFieldNotFound: // no-op
327+
case errors.Is(err, contract.ErrFieldNotFound): // no-op
304328
case err != nil:
305-
return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve failure domain from infrastructure provider for Machine %q in namespace %q", m.Name, m.Namespace)
329+
return ctrl.Result{}, errors.Wrapf(err, "failed to read failureDomain from %s %s",
330+
s.infraMachine.GetKind(), klog.KObj(s.infraMachine))
306331
default:
307-
m.Spec.FailureDomain = ptr.To(failureDomain)
332+
m.Spec.FailureDomain = failureDomain
308333
}
309334

310-
// When we hit this point provider id is set, and either:
311-
// - the infra machine is reporting ready for the first time
312-
// - the infra machine already reported ready (and thus m.Status.InfrastructureReady is already true and it should not flip back)
313-
m.Spec.ProviderID = ptr.To(providerID)
335+
// When we hit this point providerID is set, and either:
336+
// - the infra machine is reporting provisioned for the first time
337+
// - the infra machine already reported provisioned (and thus m.Status.InfrastructureReady is already true and it should not flip back)
338+
m.Spec.ProviderID = providerID
314339
m.Status.InfrastructureReady = true
315340
return ctrl.Result{}, nil
316341
}

0 commit comments

Comments
 (0)