From 9c3baeddd9bfd1699e8c906e026a4a53cf348b99 Mon Sep 17 00:00:00 2001 From: vijayaraghavanr31 Date: Thu, 1 Oct 2026 23:13:52 +0530 Subject: [PATCH] fix: keep bootstrapRef across the VM creation request-id patch --- controllers/nutanixmachine_controller.go | 13 +++++ controllers/nutanixmachine_controller_test.go | 54 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/controllers/nutanixmachine_controller.go b/controllers/nutanixmachine_controller.go index 25aa33b8f3..00e40a2a08 100644 --- a/controllers/nutanixmachine_controller.go +++ b/controllers/nutanixmachine_controller.go @@ -1891,6 +1891,13 @@ func (r *NutanixMachineReconciler) getOrMintVMCreationRequestID(rctx *nctx.Machi // silently defeating the "persist before anything else" guarantee this function exists // to provide. before := rctx.NutanixMachine.DeepCopy() + // ensureBootstrapRef sets spec.bootstrapRef in memory before this patch, and that + // value is not on the API object yet. client.Patch zeros the local object before + // decoding the response, so a field left out of the patch comes back nil. + // addGuestCustomizationToVM then panics on bootstrapRef.Kind and the VM is not + // created. Drop it from the baseline so this patch stores it and the decoded + // object still has it. + before.Spec.BootstrapRef = nil requestID := uuid.NewString() if rctx.NutanixMachine.Annotations == nil { @@ -2446,6 +2453,9 @@ func (r *NutanixMachineReconciler) logProfileNicMapping( func (r *NutanixMachineReconciler) addGuestCustomizationToDeployParams(rctx *nctx.MachineContext, params *vmmconfig.DeployVmFromVmProfileParams) error { // Get the bootstrapData bootstrapRef := rctx.NutanixMachine.Spec.BootstrapRef + if bootstrapRef == nil { + return errors.New("NutanixMachine spec.BootstrapRef is nil.") + } if bootstrapRef.Kind == infrav1.NutanixMachineBootstrapRefKindSecret { bootstrapData, err := r.getBootstrapData(rctx) if err != nil { @@ -2536,6 +2546,9 @@ func (r *NutanixMachineReconciler) powerOnVM(rctx *nctx.MachineContext, vmUUID, func (r *NutanixMachineReconciler) addGuestCustomizationToVM(rctx *nctx.MachineContext, vm *vmmconfig.Vm) error { // Get the bootstrapData bootstrapRef := rctx.NutanixMachine.Spec.BootstrapRef + if bootstrapRef == nil { + return errors.New("NutanixMachine spec.BootstrapRef is nil.") + } if bootstrapRef.Kind == infrav1.NutanixMachineBootstrapRefKindSecret { bootstrapData, err := r.getBootstrapData(rctx) if err != nil { diff --git a/controllers/nutanixmachine_controller_test.go b/controllers/nutanixmachine_controller_test.go index e48e57c836..48724d3928 100644 --- a/controllers/nutanixmachine_controller_test.go +++ b/controllers/nutanixmachine_controller_test.go @@ -3169,6 +3169,60 @@ func TestNutanixMachineReconciler_getOrMintVMCreationRequestID(t *testing.T) { require.NoError(t, err) assert.Equal(t, existingRequestID, requestID) }) + + t.Run("keeps in-memory bootstrapRef after the request-id patch", func(t *testing.T) { + ctx := context.Background() + // API object does not have bootstrapRef yet. ensureBootstrapRef sets it + // in memory before this patch runs. + stored := &infrav1.NutanixMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-machine", + Namespace: "default", + }, + } + scheme := runtime.NewScheme() + require.NoError(t, infrav1.AddToScheme(scheme)) + fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(stored).Build() + + live := stored.DeepCopy() + live.Spec.BootstrapRef = &corev1.ObjectReference{ + APIVersion: "v1", + Kind: infrav1.NutanixMachineBootstrapRefKindSecret, + Name: "bootstrap-secret", + Namespace: "default", + } + + reconciler := &NutanixMachineReconciler{Client: fakeClient} + rctx := &nctx.MachineContext{Context: ctx, NutanixMachine: live} + + requestID, err := reconciler.getOrMintVMCreationRequestID(rctx) + require.NoError(t, err) + require.NotNil(t, live.Spec.BootstrapRef, "in-memory bootstrapRef must survive the patch response") + assert.Equal(t, "bootstrap-secret", live.Spec.BootstrapRef.Name) + assert.Equal(t, infrav1.NutanixMachineBootstrapRefKindSecret, live.Spec.BootstrapRef.Kind) + assert.Equal(t, requestID, live.Annotations[VMCreationRequestIDAnnotation]) + + persisted := &infrav1.NutanixMachine{} + require.NoError(t, fakeClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "test-machine"}, persisted)) + require.NotNil(t, persisted.Spec.BootstrapRef) + assert.Equal(t, "bootstrap-secret", persisted.Spec.BootstrapRef.Name) + assert.Equal(t, requestID, persisted.Annotations[VMCreationRequestIDAnnotation]) + }) +} + +func TestAddGuestCustomizationNilBootstrapRef(t *testing.T) { + reconciler := &NutanixMachineReconciler{} + rctx := &nctx.MachineContext{ + Context: context.Background(), + NutanixMachine: &infrav1.NutanixMachine{}, + Machine: &capiv1beta2.Machine{ObjectMeta: metav1.ObjectMeta{Name: "test-machine"}}, + } + + err := reconciler.addGuestCustomizationToVM(rctx, vmmModels.NewVm()) + require.Error(t, err) + + err = reconciler.addGuestCustomizationToDeployParams(rctx, vmmModels.NewDeployVmFromVmProfileParams()) + require.Error(t, err) } func TestNutanixMachineReconciler_getOrCreateVM(t *testing.T) {