@@ -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,7 @@ 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/patch"
4439 "sigs.k8s.io/cluster-api/internal/util/ssa"
4540)
4641
@@ -75,7 +70,7 @@ func (r *KubeadmControlPlaneReconciler) canUpdateMachine(ctx context.Context, ma
7570 return false , nil
7671 }
7772 if len (extensionHandlers ) > 1 {
78- return false , errors .Errorf ("found multiple CanUpdateMachine hooks (%s) (more than one is not supported yet) " , strings .Join (extensionHandlers , "," ))
73+ return false , errors .Errorf ("found multiple CanUpdateMachine hooks (%s): only one hook is supported" , strings .Join (extensionHandlers , "," ))
7974 }
8075
8176 canUpdateMachine , reasons , err := r .canExtensionsUpdateMachine (ctx , machine , machineUpToDateResult , extensionHandlers )
@@ -186,19 +181,19 @@ func createRequest(ctx context.Context, c client.Client, currentMachine *cluster
186181 },
187182 }
188183 var err error
189- req .Current .BootstrapConfig , err = convertToRawExtension (cleanupKubeadmConfig (currentKubeadmConfigForDiff ))
184+ req .Current .BootstrapConfig , err = patch . ConvertToRawExtension (cleanupKubeadmConfig (currentKubeadmConfigForDiff ))
190185 if err != nil {
191186 return nil , err
192187 }
193- req .Desired .BootstrapConfig , err = convertToRawExtension (cleanupKubeadmConfig (desiredKubeadmConfigForDiff ))
188+ req .Desired .BootstrapConfig , err = patch . ConvertToRawExtension (cleanupKubeadmConfig (desiredKubeadmConfigForDiff ))
194189 if err != nil {
195190 return nil , err
196191 }
197- req .Current .InfrastructureMachine , err = convertToRawExtension (cleanupUnstructured (currentInfraMachineForDiff ))
192+ req .Current .InfrastructureMachine , err = patch . ConvertToRawExtension (cleanupUnstructured (currentInfraMachineForDiff ))
198193 if err != nil {
199194 return nil , err
200195 }
201- req .Desired .InfrastructureMachine , err = convertToRawExtension (cleanupUnstructured (desiredInfraMachineForDiff ))
196+ req .Desired .InfrastructureMachine , err = patch . ConvertToRawExtension (cleanupUnstructured (desiredInfraMachineForDiff ))
202197 if err != nil {
203198 return nil , err
204199 }
@@ -255,143 +250,28 @@ func cleanupUnstructured(u *unstructured.Unstructured) *unstructured.Unstructure
255250 return cleanedUpU
256251}
257252
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-
281253func applyPatchesToRequest (ctx context.Context , req * runtimehooksv1.CanUpdateMachineRequest , resp * runtimehooksv1.CanUpdateMachineResponse ) error {
282254 if resp .MachinePatch .IsDefined () {
283- if err := applyPatchToMachine (ctx , & req .Current .Machine , resp .MachinePatch ); err != nil {
255+ if err := patch . ApplyPatchToTypedObject (ctx , & req .Current .Machine , resp .MachinePatch , "spec" ); err != nil {
284256 return err
285257 }
286258 }
287259
288260 if resp .BootstrapConfigPatch .IsDefined () {
289- if _ , err := applyPatchToObject (ctx , & req .Current .BootstrapConfig , resp .BootstrapConfigPatch ); err != nil {
261+ if _ , err := patch . ApplyPatchToObject (ctx , & req .Current .BootstrapConfig , resp .BootstrapConfigPatch , "spec" ); err != nil {
290262 return err
291263 }
292264 }
293265
294266 if resp .InfrastructureMachinePatch .IsDefined () {
295- if _ , err := applyPatchToObject (ctx , & req .Current .InfrastructureMachine , resp .InfrastructureMachinePatch ); err != nil {
267+ if _ , err := patch . ApplyPatchToObject (ctx , & req .Current .InfrastructureMachine , resp .InfrastructureMachinePatch , "spec" ); err != nil {
296268 return err
297269 }
298270 }
299271
300272 return nil
301273}
302274
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-
395275func matchesMachine (req * runtimehooksv1.CanUpdateMachineRequest ) (bool , []string , error ) {
396276 var reasons []string
397277 match , diff , err := matchesMachineSpec (& req .Current .Machine , & req .Desired .Machine )
0 commit comments