@@ -17,19 +17,14 @@ limitations under the License.
1717package controllers
1818
1919import (
20- "bytes"
2120 "context"
22- "encoding/json"
2321 "fmt"
24- "runtime/debug"
2522 "strings"
2623
27- jsonpatch "github.com/evanphx/json-patch/v5"
2824 "github.com/pkg/errors"
2925 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3026 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3127 "k8s.io/apimachinery/pkg/runtime"
32- kerrors "k8s.io/apimachinery/pkg/util/errors"
3328 "k8s.io/klog/v2"
3429 ctrl "sigs.k8s.io/controller-runtime"
3530 "sigs.k8s.io/controller-runtime/pkg/client"
@@ -40,7 +35,8 @@ import (
4035 "sigs.k8s.io/cluster-api/controlplane/kubeadm/internal"
4136 "sigs.k8s.io/cluster-api/feature"
4237 "sigs.k8s.io/cluster-api/internal/util/compare"
43- patchutil "sigs.k8s.io/cluster-api/internal/util/patch"
38+ "sigs.k8s.io/cluster-api/internal/util/inplace"
39+ "sigs.k8s.io/cluster-api/internal/util/patch"
4440 "sigs.k8s.io/cluster-api/internal/util/ssa"
4541)
4642
@@ -75,7 +71,7 @@ func (r *KubeadmControlPlaneReconciler) canUpdateMachine(ctx context.Context, ma
7571 return false , nil
7672 }
7773 if len (extensionHandlers ) > 1 {
78- return false , errors .Errorf ("found multiple CanUpdateMachine hooks (%s) (more than one is not supported yet) " , strings .Join (extensionHandlers , "," ))
74+ return false , errors .Errorf ("found multiple CanUpdateMachine hooks (%s): only one hook is supported" , strings .Join (extensionHandlers , "," ))
7975 }
8076
8177 canUpdateMachine , reasons , err := r .canExtensionsUpdateMachine (ctx , machine , machineUpToDateResult , extensionHandlers )
@@ -186,19 +182,19 @@ func createRequest(ctx context.Context, c client.Client, currentMachine *cluster
186182 },
187183 }
188184 var err error
189- req .Current .BootstrapConfig , err = convertToRawExtension (cleanupKubeadmConfig (currentKubeadmConfigForDiff ))
185+ req .Current .BootstrapConfig , err = patch . ConvertToRawExtension (cleanupKubeadmConfig (currentKubeadmConfigForDiff ))
190186 if err != nil {
191187 return nil , err
192188 }
193- req .Desired .BootstrapConfig , err = convertToRawExtension (cleanupKubeadmConfig (desiredKubeadmConfigForDiff ))
189+ req .Desired .BootstrapConfig , err = patch . ConvertToRawExtension (cleanupKubeadmConfig (desiredKubeadmConfigForDiff ))
194190 if err != nil {
195191 return nil , err
196192 }
197- req .Current .InfrastructureMachine , err = convertToRawExtension (cleanupUnstructured (currentInfraMachineForDiff ))
193+ req .Current .InfrastructureMachine , err = patch . ConvertToRawExtension (cleanupUnstructured (currentInfraMachineForDiff ))
198194 if err != nil {
199195 return nil , err
200196 }
201- req .Desired .InfrastructureMachine , err = convertToRawExtension (cleanupUnstructured (desiredInfraMachineForDiff ))
197+ req .Desired .InfrastructureMachine , err = patch . ConvertToRawExtension (cleanupUnstructured (desiredInfraMachineForDiff ))
202198 if err != nil {
203199 return nil , err
204200 }
@@ -255,143 +251,28 @@ func cleanupUnstructured(u *unstructured.Unstructured) *unstructured.Unstructure
255251 return cleanedUpU
256252}
257253
258- func convertToRawExtension (object runtime.Object ) (runtime.RawExtension , error ) {
259- objectBytes , err := json .Marshal (object )
260- if err != nil {
261- return runtime.RawExtension {}, errors .Wrap (err , "failed to marshal object to JSON" )
262- }
263-
264- objectUnstructured , ok := object .(* unstructured.Unstructured )
265- if ! ok {
266- objectUnstructured = & unstructured.Unstructured {}
267- // Note: This only succeeds if object has apiVersion & kind set (which is always the case).
268- if err := json .Unmarshal (objectBytes , objectUnstructured ); err != nil {
269- return runtime.RawExtension {}, errors .Wrap (err , "failed to Unmarshal object into Unstructured" )
270- }
271- }
272-
273- // Note: Raw and Object are always both set and Object is always set as an Unstructured
274- // to simplify subsequent code in matchesUnstructuredSpec.
275- return runtime.RawExtension {
276- Raw : objectBytes ,
277- Object : objectUnstructured ,
278- }, nil
279- }
280-
281254func applyPatchesToRequest (ctx context.Context , req * runtimehooksv1.CanUpdateMachineRequest , resp * runtimehooksv1.CanUpdateMachineResponse ) error {
282255 if resp .MachinePatch .IsDefined () {
283- if err := applyPatchToMachine (ctx , & req .Current .Machine , resp .MachinePatch ); err != nil {
256+ if err := patch . ApplyPatchToTypedObject (ctx , & req .Current .Machine , resp .MachinePatch , "spec" ); err != nil {
284257 return err
285258 }
286259 }
287260
288261 if resp .BootstrapConfigPatch .IsDefined () {
289- if _ , err := applyPatchToObject (ctx , & req .Current .BootstrapConfig , resp .BootstrapConfigPatch ); err != nil {
262+ if _ , err := patch . ApplyPatchToObject (ctx , & req .Current .BootstrapConfig , resp .BootstrapConfigPatch , "spec" ); err != nil {
290263 return err
291264 }
292265 }
293266
294267 if resp .InfrastructureMachinePatch .IsDefined () {
295- if _ , err := applyPatchToObject (ctx , & req .Current .InfrastructureMachine , resp .InfrastructureMachinePatch ); err != nil {
268+ if _ , err := patch . ApplyPatchToObject (ctx , & req .Current .InfrastructureMachine , resp .InfrastructureMachinePatch , "spec" ); err != nil {
296269 return err
297270 }
298271 }
299272
300273 return nil
301274}
302275
303- func applyPatchToMachine (ctx context.Context , currentMachine * clusterv1.Machine , machinePath runtimehooksv1.Patch ) error {
304- // Note: Machine needs special handling because it is not a runtime.RawExtension. Simply converting it here to
305- // a runtime.RawExtension so we can avoid making the code in applyPatchToObject more complex.
306- currentMachineRaw , err := convertToRawExtension (currentMachine )
307- if err != nil {
308- return err
309- }
310-
311- machineChanged , err := applyPatchToObject (ctx , & currentMachineRaw , machinePath )
312- if err != nil {
313- return err
314- }
315-
316- if ! machineChanged {
317- return nil
318- }
319-
320- // Note: json.Unmarshal can't be used directly on *currentMachine as json.Unmarshal does not unset fields.
321- patchedCurrentMachine := & clusterv1.Machine {}
322- if err := json .Unmarshal (currentMachineRaw .Raw , patchedCurrentMachine ); err != nil {
323- return err
324- }
325- * currentMachine = * patchedCurrentMachine
326- return nil
327- }
328-
329- // applyPatchToObject applies the patch to the obj.
330- // Note: This is following the same general structure that is used in the applyPatchToRequest func in
331- // internal/controllers/topology/cluster/patches/engine.go.
332- func applyPatchToObject (ctx context.Context , obj * runtime.RawExtension , patch runtimehooksv1.Patch ) (objChanged bool , reterr error ) {
333- log := ctrl .LoggerFrom (ctx )
334-
335- if patch .PatchType == "" {
336- return false , errors .Errorf ("failed to apply patch: patchType is not set" )
337- }
338-
339- defer func () {
340- if r := recover (); r != nil {
341- log .Info (fmt .Sprintf ("Observed a panic when applying patch: %v\n %s" , r , string (debug .Stack ())))
342- reterr = kerrors .NewAggregate ([]error {reterr , fmt .Errorf ("observed a panic when applying patch: %v" , r )})
343- }
344- }()
345-
346- // Create a copy of obj.Raw.
347- // The patches will be applied to the copy and then only spec changes will be copied back to the obj.
348- patchedObject := bytes .Clone (obj .Raw )
349- var err error
350-
351- switch patch .PatchType {
352- case runtimehooksv1 .JSONPatchType :
353- log .V (5 ).Info ("Accumulating JSON patch" , "patch" , string (patch .Patch ))
354- jsonPatch , err := jsonpatch .DecodePatch (patch .Patch )
355- if err != nil {
356- log .Error (err , "Failed to apply patch: error decoding json patch (RFC6902)" , "patch" , string (patch .Patch ))
357- return false , errors .Wrap (err , "failed to apply patch: error decoding json patch (RFC6902)" )
358- }
359-
360- if len (jsonPatch ) == 0 {
361- // Return if there are no patches, nothing to do.
362- return false , nil
363- }
364-
365- patchedObject , err = jsonPatch .Apply (patchedObject )
366- if err != nil {
367- log .Error (err , "Failed to apply patch: error applying json patch (RFC6902)" , "patch" , string (patch .Patch ))
368- return false , errors .Wrap (err , "failed to apply patch: error applying json patch (RFC6902)" )
369- }
370- case runtimehooksv1 .JSONMergePatchType :
371- if len (patch .Patch ) == 0 || bytes .Equal (patch .Patch , []byte ("{}" )) {
372- // Return if there are no patches, nothing to do.
373- return false , nil
374- }
375-
376- log .V (5 ).Info ("Accumulating JSON merge patch" , "patch" , string (patch .Patch ))
377- patchedObject , err = jsonpatch .MergePatch (patchedObject , patch .Patch )
378- if err != nil {
379- log .Error (err , "Failed to apply patch: error applying json merge patch (RFC7386)" , "patch" , string (patch .Patch ))
380- return false , errors .Wrap (err , "failed to apply patch: error applying json merge patch (RFC7386)" )
381- }
382- default :
383- return false , errors .Errorf ("failed to apply patch: unknown patchType %s" , patch .PatchType )
384- }
385-
386- // Overwrite the spec of obj with the spec of the patchedObject,
387- // to ensure that we only pick up changes to the spec.
388- if err := patchutil .PatchSpec (obj , patchedObject ); err != nil {
389- return false , errors .Wrap (err , "failed to apply patch to object" )
390- }
391-
392- return true , nil
393- }
394-
395276func matchesMachine (req * runtimehooksv1.CanUpdateMachineRequest ) (bool , []string , error ) {
396277 var reasons []string
397278 match , diff , err := matchesMachineSpec (& req .Current .Machine , & req .Desired .Machine )
@@ -427,10 +308,10 @@ func matchesMachineSpec(patched, desired *clusterv1.Machine) (equal bool, diff s
427308 // Note: Wrapping Machine specs in a Machine for proper formatting of the diff.
428309 return compare .Diff (
429310 & clusterv1.Machine {
430- Spec : patched .Spec ,
311+ Spec : * inplace . CleanupMachineSpecForDiff ( & patched .Spec ) ,
431312 },
432313 & clusterv1.Machine {
433- Spec : desired .Spec ,
314+ Spec : * inplace . CleanupMachineSpecForDiff ( & desired .Spec ) ,
434315 },
435316 )
436317}
0 commit comments