@@ -6,6 +6,7 @@ package machinedetails
66import (
77 "context"
88 "errors"
9+ "fmt"
910 "slices"
1011
1112 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -88,15 +89,29 @@ func (h *nutanixMachineDetailsPatchHandler) Mutate(
8889 & holderRef ,
8990 h .patchSelector ,
9091 log ,
91- func (obj * capxv1.NutanixMachineTemplate ) error {
92+ func (machineTemplate * capxv1.NutanixMachineTemplate ) error {
93+ // First, ensure the required fields exist in the original unstructured object
94+ // if they will be set by the mutation
95+ if nutanixMachineDetailsVar .Cluster != nil {
96+ if err := ensureClusterFieldExists (obj ); err != nil {
97+ return fmt .Errorf ("failed to ensure cluster field exists: %w" , err )
98+ }
99+ }
100+ if len (nutanixMachineDetailsVar .Subnets ) > 0 {
101+ if err := ensureSubnetFieldExists (obj ); err != nil {
102+ return fmt .Errorf ("failed to ensure subnet field exists: %w" , err )
103+ }
104+ }
105+
92106 log .WithValues (
93- "patchedObjectKind" , obj .GetObjectKind ().GroupVersionKind ().String (),
94- "patchedObjectName" , client .ObjectKeyFromObject (obj ),
95- ).Info ("setting Nutanix machine details in worker NutanixMachineTemplate spec" )
107+ "patchedObjectKind" , machineTemplate .GetObjectKind ().GroupVersionKind ().String (),
108+ "patchedObjectName" , client .ObjectKeyFromObject (machineTemplate ),
109+ ).Info ("setting Nutanix machine details in NutanixMachineTemplate spec" )
96110
97- spec := obj .Spec .Template .Spec
111+ spec := machineTemplate .Spec .Template .Spec
98112
99113 spec .BootType = nutanixMachineDetailsVar .BootType
114+
100115 if nutanixMachineDetailsVar .Cluster != nil {
101116 spec .Cluster = * nutanixMachineDetailsVar .Cluster
102117 }
@@ -125,8 +140,55 @@ func (h *nutanixMachineDetailsPatchHandler) Mutate(
125140 spec .GPUs = slices .Clone (nutanixMachineDetailsVar .GPUs )
126141 spec .Project = nutanixMachineDetailsVar .Project .DeepCopy ()
127142
128- obj .Spec .Template .Spec = spec
143+ machineTemplate .Spec .Template .Spec = spec
144+ log .WithValues (
145+ "patchedObjectKind" , machineTemplate .GetObjectKind ().GroupVersionKind ().String (),
146+ "patchedObjectName" , client .ObjectKeyFromObject (machineTemplate ),
147+ "patchedObject" , machineTemplate ,
148+ ).V (5 ).Info ("Patched Nutanix machine details in NutanixMachineTemplate spec" )
129149 return nil
130150 },
131151 )
132152}
153+
154+ // ensureClusterFieldExists ensures the cluster field exists in the unstructured object
155+ func ensureClusterFieldExists (obj * unstructured.Unstructured ) error {
156+ clusterPath := []string {"spec" , "template" , "spec" , "cluster" }
157+ if ! hasNestedField (obj .Object , clusterPath ) {
158+ // Add empty cluster field structure
159+ return unstructured .SetNestedMap (obj .Object , map [string ]any {
160+ "type" : "" ,
161+ }, clusterPath ... )
162+ }
163+ return nil
164+ }
165+
166+ // ensureSubnetFieldExists ensures the subnet field exists in the unstructured object
167+ func ensureSubnetFieldExists (obj * unstructured.Unstructured ) error {
168+ subnetPath := []string {"spec" , "template" , "spec" , "subnet" }
169+ if ! hasNestedField (obj .Object , subnetPath ) {
170+ // Add empty subnet field
171+ return unstructured .SetNestedSlice (obj .Object , []any {}, subnetPath ... )
172+ }
173+ return nil
174+ }
175+
176+ // hasNestedField checks if a nested field exists in an unstructured object
177+ func hasNestedField (obj map [string ]interface {}, path []string ) bool {
178+ current := obj
179+ for _ , key := range path {
180+ if current == nil {
181+ return false
182+ }
183+ val , exists := current [key ]
184+ if ! exists {
185+ return false
186+ }
187+ if next , ok := val .(map [string ]interface {}); ok {
188+ current = next
189+ } else {
190+ return len (path ) == 1
191+ }
192+ }
193+ return true
194+ }
0 commit comments