sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/topology/cluster/patches/engine.go (about)

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package patches implement the patch engine.
    18  package patches
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"runtime/debug"
    24  	"strings"
    25  
    26  	jsonpatch "github.com/evanphx/json-patch/v5"
    27  	"github.com/pkg/errors"
    28  	kerrors "k8s.io/apimachinery/pkg/util/errors"
    29  	"k8s.io/klog/v2"
    30  
    31  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    32  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    33  	runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
    34  	"sigs.k8s.io/cluster-api/exp/topology/scope"
    35  	"sigs.k8s.io/cluster-api/feature"
    36  	"sigs.k8s.io/cluster-api/internal/contract"
    37  	"sigs.k8s.io/cluster-api/internal/controllers/topology/cluster/patches/api"
    38  	"sigs.k8s.io/cluster-api/internal/controllers/topology/cluster/patches/external"
    39  	"sigs.k8s.io/cluster-api/internal/controllers/topology/cluster/patches/inline"
    40  	"sigs.k8s.io/cluster-api/internal/controllers/topology/cluster/patches/variables"
    41  	tlog "sigs.k8s.io/cluster-api/internal/log"
    42  	runtimeclient "sigs.k8s.io/cluster-api/internal/runtime/client"
    43  )
    44  
    45  // Engine is a patch engine which applies patches defined in a ClusterBlueprint to a ClusterState.
    46  type Engine interface {
    47  	Apply(ctx context.Context, blueprint *scope.ClusterBlueprint, desired *scope.ClusterState) error
    48  }
    49  
    50  // NewEngine creates a new patch engine.
    51  func NewEngine(runtimeClient runtimeclient.Client) Engine {
    52  	return &engine{
    53  		runtimeClient: runtimeClient,
    54  	}
    55  }
    56  
    57  // engine implements the Engine interface.
    58  type engine struct {
    59  	runtimeClient runtimeclient.Client
    60  }
    61  
    62  // Apply applies patches to the desired state according to the patches from the ClusterClass, variables from the Cluster
    63  // and builtin variables.
    64  //   - A GeneratePatchesRequest with all templates and global and template-specific variables is created.
    65  //   - Then for all ClusterClassPatches of a ClusterClass, JSON or JSON merge patches are generated
    66  //     and successively applied to the templates in the GeneratePatchesRequest.
    67  //   - Eventually the patched templates are used to update the specs of the desired objects.
    68  func (e *engine) Apply(ctx context.Context, blueprint *scope.ClusterBlueprint, desired *scope.ClusterState) error {
    69  	// Return if there are no patches.
    70  	if len(blueprint.ClusterClass.Spec.Patches) == 0 {
    71  		return nil
    72  	}
    73  
    74  	log := tlog.LoggerFrom(ctx)
    75  
    76  	// Create a patch generation request.
    77  	req, err := createRequest(blueprint, desired)
    78  	if err != nil {
    79  		return errors.Wrapf(err, "failed to generate patch request")
    80  	}
    81  
    82  	// Loop over patches in ClusterClass, generate patches and apply them to the request,
    83  	// respecting the order in which they are defined.
    84  	for i := range blueprint.ClusterClass.Spec.Patches {
    85  		clusterClassPatch := blueprint.ClusterClass.Spec.Patches[i]
    86  		ctx, log := log.WithValues("patch", clusterClassPatch.Name).Into(ctx)
    87  
    88  		definitionFrom := clusterClassPatch.Name
    89  		// If this isn't an external patch, use the inline patch name.
    90  		if clusterClassPatch.External == nil {
    91  			definitionFrom = clusterv1.VariableDefinitionFromInline
    92  		}
    93  		if err := addVariablesForPatch(blueprint, desired, req, definitionFrom); err != nil {
    94  			return errors.Wrapf(err, "failed to calculate variables for patch %q", clusterClassPatch.Name)
    95  		}
    96  		log.V(5).Infof("Applying patch to templates")
    97  
    98  		// Create patch generator for the current patch.
    99  		generator, err := createPatchGenerator(e.runtimeClient, &clusterClassPatch)
   100  		if err != nil {
   101  			return err
   102  		}
   103  
   104  		// Generate patches.
   105  		// NOTE: All the partial patches accumulate on top of the request, so the
   106  		// patch generator in the next iteration of the loop will get the modified
   107  		// version of the request (including the patched version of the templates).
   108  		resp, err := generator.Generate(ctx, desired.Cluster, req)
   109  		if err != nil {
   110  			return errors.Wrapf(err, "failed to generate patches for patch %q", clusterClassPatch.Name)
   111  		}
   112  
   113  		// Apply patches to the request.
   114  		if err := applyPatchesToRequest(ctx, req, resp); err != nil {
   115  			return errors.Wrapf(err, "failed to apply patches for patch %q", clusterClassPatch.Name)
   116  		}
   117  	}
   118  
   119  	// Convert request to validation request.
   120  	validationRequest := convertToValidationRequest(req)
   121  
   122  	// Loop over patches in ClusterClass and validate topology,
   123  	// respecting the order in which they are defined.
   124  	for i := range blueprint.ClusterClass.Spec.Patches {
   125  		clusterClassPatch := blueprint.ClusterClass.Spec.Patches[i]
   126  
   127  		if clusterClassPatch.External == nil || clusterClassPatch.External.ValidateExtension == nil {
   128  			continue
   129  		}
   130  
   131  		ctx, log := log.WithValues("patch", clusterClassPatch.Name).Into(ctx)
   132  
   133  		log.V(5).Infof("Validating topology")
   134  
   135  		validator := external.NewValidator(e.runtimeClient, &clusterClassPatch)
   136  
   137  		_, err := validator.Validate(ctx, desired.Cluster, validationRequest)
   138  		if err != nil {
   139  			return errors.Wrapf(err, "validation of patch %q failed", clusterClassPatch.Name)
   140  		}
   141  	}
   142  
   143  	// Use patched templates to update the desired state objects.
   144  	log.V(5).Infof("Applying patched templates to desired state")
   145  	if err := updateDesiredState(ctx, req, blueprint, desired); err != nil {
   146  		return errors.Wrapf(err, "failed to apply patches to desired state")
   147  	}
   148  
   149  	return nil
   150  }
   151  
   152  // addVariablesForPatch adds variables for a given ClusterClassPatch to the items in the PatchRequest.
   153  func addVariablesForPatch(blueprint *scope.ClusterBlueprint, desired *scope.ClusterState, req *runtimehooksv1.GeneratePatchesRequest, definitionFrom string) error {
   154  	// If there is no definitionFrom return an error.
   155  	if definitionFrom == "" {
   156  		return errors.New("failed to calculate variables: no patch name provided")
   157  	}
   158  
   159  	patchVariableDefinitions := definitionsForPatch(blueprint, definitionFrom)
   160  	// Calculate global variables.
   161  	globalVariables, err := variables.Global(blueprint.Topology, desired.Cluster, definitionFrom, patchVariableDefinitions)
   162  	if err != nil {
   163  		return errors.Wrapf(err, "failed to calculate global variables")
   164  	}
   165  	req.Variables = globalVariables
   166  
   167  	// Calculate the Control Plane variables.
   168  	controlPlaneVariables, err := variables.ControlPlane(&blueprint.Topology.ControlPlane, desired.ControlPlane.Object, desired.ControlPlane.InfrastructureMachineTemplate)
   169  	if err != nil {
   170  		return errors.Wrapf(err, "failed to calculate ControlPlane variables")
   171  	}
   172  
   173  	mdStateIndex := map[string]*scope.MachineDeploymentState{}
   174  	for _, md := range desired.MachineDeployments {
   175  		mdStateIndex[md.Object.Name] = md
   176  	}
   177  	mpStateIndex := map[string]*scope.MachinePoolState{}
   178  	for _, mp := range desired.MachinePools {
   179  		mpStateIndex[mp.Object.Name] = mp
   180  	}
   181  	for i, item := range req.Items {
   182  		// If the item is a Control Plane add the Control Plane variables.
   183  		if item.HolderReference.FieldPath == "spec.controlPlaneRef" {
   184  			item.Variables = controlPlaneVariables
   185  		}
   186  		// If the item holder reference is a Control Plane machine add the Control Plane variables.
   187  		if blueprint.HasControlPlaneInfrastructureMachine() &&
   188  			item.HolderReference.FieldPath == strings.Join(contract.ControlPlane().MachineTemplate().InfrastructureRef().Path(), ".") {
   189  			item.Variables = controlPlaneVariables
   190  		}
   191  		// If the item holder reference is a MachineDeployment calculate the variables for each MachineDeploymentTopology
   192  		// and add them to the variables for the MachineDeployment.
   193  		if item.HolderReference.Kind == "MachineDeployment" {
   194  			md, ok := mdStateIndex[item.HolderReference.Name]
   195  			if !ok {
   196  				return errors.Errorf("could not find desired state for MachineDeployment %s", klog.KRef(item.HolderReference.Namespace, item.HolderReference.Name))
   197  			}
   198  			mdTopology, err := getMDTopologyFromMD(blueprint, md.Object)
   199  			if err != nil {
   200  				return err
   201  			}
   202  
   203  			// Calculate MachineDeployment variables.
   204  			mdVariables, err := variables.MachineDeployment(mdTopology, md.Object, md.BootstrapTemplate, md.InfrastructureMachineTemplate, definitionFrom, patchVariableDefinitions)
   205  			if err != nil {
   206  				return errors.Wrapf(err, "failed to calculate variables for %s", klog.KObj(md.Object))
   207  			}
   208  			item.Variables = mdVariables
   209  		} else if item.HolderReference.Kind == "MachinePool" {
   210  			mp, ok := mpStateIndex[item.HolderReference.Name]
   211  			if !ok {
   212  				return errors.Errorf("could not find desired state for MachinePool %s", klog.KRef(item.HolderReference.Namespace, item.HolderReference.Name))
   213  			}
   214  			mpTopology, err := getMPTopologyFromMP(blueprint, mp.Object)
   215  			if err != nil {
   216  				return err
   217  			}
   218  
   219  			// Calculate MachinePool variables.
   220  			mpVariables, err := variables.MachinePool(mpTopology, mp.Object, mp.BootstrapObject, mp.InfrastructureMachinePoolObject, definitionFrom, patchVariableDefinitions)
   221  			if err != nil {
   222  				return errors.Wrapf(err, "failed to calculate variables for %s", klog.KObj(mp.Object))
   223  			}
   224  			item.Variables = mpVariables
   225  		}
   226  		req.Items[i] = item
   227  	}
   228  	return nil
   229  }
   230  
   231  func getMDTopologyFromMD(blueprint *scope.ClusterBlueprint, md *clusterv1.MachineDeployment) (*clusterv1.MachineDeploymentTopology, error) {
   232  	topologyName, ok := md.Labels[clusterv1.ClusterTopologyMachineDeploymentNameLabel]
   233  	if !ok {
   234  		return nil, errors.Errorf("failed to get topology name for %s", klog.KObj(md))
   235  	}
   236  	mdTopology, err := lookupMDTopology(blueprint.Topology, topologyName)
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  	return mdTopology, nil
   241  }
   242  
   243  func getMPTopologyFromMP(blueprint *scope.ClusterBlueprint, mp *expv1.MachinePool) (*clusterv1.MachinePoolTopology, error) {
   244  	topologyName, ok := mp.Labels[clusterv1.ClusterTopologyMachinePoolNameLabel]
   245  	if !ok {
   246  		return nil, errors.Errorf("failed to get topology name for %s", klog.KObj(mp))
   247  	}
   248  	mpTopology, err := lookupMPTopology(blueprint.Topology, topologyName)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	return mpTopology, nil
   253  }
   254  
   255  // createRequest creates a GeneratePatchesRequest based on the ClusterBlueprint and the desired state.
   256  // NOTE: GenerateRequestTemplates are created for the templates of each individual MachineDeployment in the desired
   257  // state. This is necessary because some builtin variables are MachineDeployment specific. For example version and
   258  // replicas of a MachineDeployment.
   259  // NOTE: A single GeneratePatchesRequest object is used to carry templates state across subsequent Generate calls.
   260  // NOTE: This function does not add variables to items for the request, as the variables depend on the specific patch.
   261  func createRequest(blueprint *scope.ClusterBlueprint, desired *scope.ClusterState) (*runtimehooksv1.GeneratePatchesRequest, error) {
   262  	req := &runtimehooksv1.GeneratePatchesRequest{}
   263  
   264  	// Add the InfrastructureClusterTemplate.
   265  	t, err := newRequestItemBuilder(blueprint.InfrastructureClusterTemplate).
   266  		WithHolder(desired.Cluster, clusterv1.GroupVersion.WithKind("Cluster"), "spec.infrastructureRef").
   267  		Build()
   268  	if err != nil {
   269  		return nil, errors.Wrapf(err, "failed to prepare InfrastructureCluster template %s for patching",
   270  			tlog.KObj{Obj: blueprint.InfrastructureClusterTemplate})
   271  	}
   272  	req.Items = append(req.Items, *t)
   273  
   274  	// Add the ControlPlaneTemplate.
   275  	t, err = newRequestItemBuilder(blueprint.ControlPlane.Template).
   276  		WithHolder(desired.Cluster, clusterv1.GroupVersion.WithKind("Cluster"), "spec.controlPlaneRef").
   277  		Build()
   278  	if err != nil {
   279  		return nil, errors.Wrapf(err, "failed to prepare ControlPlane template %s for patching",
   280  			tlog.KObj{Obj: blueprint.ControlPlane.Template})
   281  	}
   282  	req.Items = append(req.Items, *t)
   283  
   284  	// If the clusterClass mandates the controlPlane has infrastructureMachines,
   285  	// add the InfrastructureMachineTemplate for control plane machines.
   286  	if blueprint.HasControlPlaneInfrastructureMachine() {
   287  		t, err := newRequestItemBuilder(blueprint.ControlPlane.InfrastructureMachineTemplate).
   288  			WithHolder(desired.ControlPlane.Object, desired.ControlPlane.Object.GroupVersionKind(), strings.Join(contract.ControlPlane().MachineTemplate().InfrastructureRef().Path(), ".")).
   289  			Build()
   290  		if err != nil {
   291  			return nil, errors.Wrapf(err, "failed to prepare ControlPlane's machine template %s for patching",
   292  				tlog.KObj{Obj: blueprint.ControlPlane.InfrastructureMachineTemplate})
   293  		}
   294  		req.Items = append(req.Items, *t)
   295  	}
   296  
   297  	// Add BootstrapConfigTemplate and InfrastructureMachine template for all MachineDeploymentTopologies
   298  	// in the Cluster.
   299  	// NOTE: We intentionally iterate over MachineDeployment in the Cluster instead of over
   300  	// MachineDeploymentClasses in the ClusterClass because each MachineDeployment in a topology
   301  	// has its own state, e.g. version or replicas. This state is used to calculate builtin variables,
   302  	// which can then be used e.g. to compute the machine image for a specific Kubernetes version.
   303  	for mdTopologyName, md := range desired.MachineDeployments {
   304  		// Lookup MachineDeploymentTopology definition from cluster.spec.topology.
   305  		mdTopology, err := lookupMDTopology(blueprint.Topology, mdTopologyName)
   306  		if err != nil {
   307  			return nil, err
   308  		}
   309  
   310  		// Get corresponding MachineDeploymentClass from the ClusterClass.
   311  		mdClass, ok := blueprint.MachineDeployments[mdTopology.Class]
   312  		if !ok {
   313  			return nil, errors.Errorf("failed to lookup MachineDeployment class %q in ClusterClass", mdTopology.Class)
   314  		}
   315  
   316  		// Add the BootstrapTemplate.
   317  		t, err := newRequestItemBuilder(mdClass.BootstrapTemplate).
   318  			WithHolder(md.Object, clusterv1.GroupVersion.WithKind("MachineDeployment"), "spec.template.spec.bootstrap.configRef").
   319  			Build()
   320  		if err != nil {
   321  			return nil, errors.Wrapf(err, "failed to prepare BootstrapConfig template %s for MachineDeployment topology %s for patching",
   322  				tlog.KObj{Obj: mdClass.BootstrapTemplate}, mdTopologyName)
   323  		}
   324  		req.Items = append(req.Items, *t)
   325  
   326  		// Add the InfrastructureMachineTemplate.
   327  		t, err = newRequestItemBuilder(mdClass.InfrastructureMachineTemplate).
   328  			WithHolder(md.Object, clusterv1.GroupVersion.WithKind("MachineDeployment"), "spec.template.spec.infrastructureRef").
   329  			Build()
   330  		if err != nil {
   331  			return nil, errors.Wrapf(err, "failed to prepare InfrastructureMachine template %s for MachineDeployment topology %s for patching",
   332  				tlog.KObj{Obj: mdClass.InfrastructureMachineTemplate}, mdTopologyName)
   333  		}
   334  		req.Items = append(req.Items, *t)
   335  	}
   336  
   337  	// Add BootstrapConfigTemplate and InfrastructureMachinePoolTemplate for all MachinePoolTopologies
   338  	// in the Cluster.
   339  	// NOTE: We intentionally iterate over MachinePool in the Cluster instead of over
   340  	// MachinePoolClasses in the ClusterClass because each MachinePool in a topology
   341  	// has its own state, e.g. version or replicas. This state is used to calculate builtin variables,
   342  	// which can then be used e.g. to compute the machine image for a specific Kubernetes version.
   343  	for mpTopologyName, mp := range desired.MachinePools {
   344  		// Lookup MachinePoolTopology definition from cluster.spec.topology.
   345  		mpTopology, err := lookupMPTopology(blueprint.Topology, mpTopologyName)
   346  		if err != nil {
   347  			return nil, err
   348  		}
   349  
   350  		// Get corresponding MachinePoolClass from the ClusterClass.
   351  		mpClass, ok := blueprint.MachinePools[mpTopology.Class]
   352  		if !ok {
   353  			return nil, errors.Errorf("failed to lookup MachinePool class %q in ClusterClass", mpTopology.Class)
   354  		}
   355  
   356  		// Add the BootstrapTemplate.
   357  		t, err := newRequestItemBuilder(mpClass.BootstrapTemplate).
   358  			WithHolder(mp.Object, expv1.GroupVersion.WithKind("MachinePool"), "spec.template.spec.bootstrap.configRef").
   359  			Build()
   360  		if err != nil {
   361  			return nil, errors.Wrapf(err, "failed to prepare BootstrapConfig template %s for MachinePool topology %s for patching",
   362  				tlog.KObj{Obj: mpClass.BootstrapTemplate}, mpTopologyName)
   363  		}
   364  		req.Items = append(req.Items, *t)
   365  
   366  		// Add the InfrastructureMachineTemplate.
   367  		t, err = newRequestItemBuilder(mpClass.InfrastructureMachinePoolTemplate).
   368  			WithHolder(mp.Object, expv1.GroupVersion.WithKind("MachinePool"), "spec.template.spec.infrastructureRef").
   369  			Build()
   370  		if err != nil {
   371  			return nil, errors.Wrapf(err, "failed to prepare InfrastructureMachinePoolTemplate %s for MachinePool topology %s for patching",
   372  				tlog.KObj{Obj: mpClass.InfrastructureMachinePoolTemplate}, mpTopologyName)
   373  		}
   374  		req.Items = append(req.Items, *t)
   375  	}
   376  
   377  	return req, nil
   378  }
   379  
   380  // lookupMDTopology looks up the MachineDeploymentTopology based on a mdTopologyName in a topology.
   381  func lookupMDTopology(topology *clusterv1.Topology, mdTopologyName string) (*clusterv1.MachineDeploymentTopology, error) {
   382  	for _, mdTopology := range topology.Workers.MachineDeployments {
   383  		if mdTopology.Name == mdTopologyName {
   384  			return &mdTopology, nil
   385  		}
   386  	}
   387  	return nil, errors.Errorf("failed to lookup MachineDeployment topology %q in Cluster.spec.topology.workers.machineDeployments", mdTopologyName)
   388  }
   389  
   390  // lookupMPTopology looks up the MachinePoolTopology based on a mpTopologyName in a topology.
   391  func lookupMPTopology(topology *clusterv1.Topology, mpTopologyName string) (*clusterv1.MachinePoolTopology, error) {
   392  	for _, mpTopology := range topology.Workers.MachinePools {
   393  		if mpTopology.Name == mpTopologyName {
   394  			return &mpTopology, nil
   395  		}
   396  	}
   397  	return nil, errors.Errorf("failed to lookup MachinePool topology %q in Cluster.spec.topology.workers.machinePools", mpTopologyName)
   398  }
   399  
   400  // createPatchGenerator creates a patch generator for the given patch.
   401  // NOTE: Currently only inline JSON patches are supported; in the future we will add
   402  // external patches as well.
   403  func createPatchGenerator(runtimeClient runtimeclient.Client, patch *clusterv1.ClusterClassPatch) (api.Generator, error) {
   404  	// Return a jsonPatchGenerator if there are PatchDefinitions in the patch.
   405  	if len(patch.Definitions) > 0 {
   406  		return inline.NewGenerator(patch), nil
   407  	}
   408  	// Return an externalPatchGenerator if there is an external configuration in the patch.
   409  	if patch.External != nil && patch.External.GenerateExtension != nil {
   410  		if !feature.Gates.Enabled(feature.RuntimeSDK) {
   411  			return nil, errors.Errorf("can not use external patch %q if RuntimeSDK feature flag is disabled", patch.Name)
   412  		}
   413  		if runtimeClient == nil {
   414  			return nil, errors.Errorf("failed to create patch generator for patch %q: runtimeClient is not set up", patch.Name)
   415  		}
   416  		return external.NewGenerator(runtimeClient, patch), nil
   417  	}
   418  
   419  	return nil, errors.Errorf("failed to create patch generator for patch %q", patch.Name)
   420  }
   421  
   422  // applyPatchesToRequest updates the templates of a GeneratePatchesRequest by applying the patches
   423  // of a GeneratePatchesResponse.
   424  func applyPatchesToRequest(ctx context.Context, req *runtimehooksv1.GeneratePatchesRequest, resp *runtimehooksv1.GeneratePatchesResponse) error {
   425  	for _, patch := range resp.Items {
   426  		if err := applyPatchToRequest(ctx, req, patch); err != nil {
   427  			return err
   428  		}
   429  	}
   430  	return nil
   431  }
   432  
   433  func applyPatchToRequest(ctx context.Context, req *runtimehooksv1.GeneratePatchesRequest, patch runtimehooksv1.GeneratePatchesResponseItem) (reterr error) {
   434  	log := tlog.LoggerFrom(ctx).WithValues("uid", patch.UID)
   435  
   436  	defer func() {
   437  		if r := recover(); r != nil {
   438  			log.Infof("Observed a panic when applying patch: %v\n%s", r, string(debug.Stack()))
   439  			reterr = kerrors.NewAggregate([]error{reterr, fmt.Errorf("observed a panic when applying patch: %v", r)})
   440  		}
   441  	}()
   442  
   443  	// Get the request item the patch belongs to.
   444  	requestItem := getRequestItemByUID(req, patch.UID)
   445  
   446  	// If a patch doesn't have a corresponding request item, the patch is invalid.
   447  	if requestItem == nil {
   448  		log.Infof("Unable to find corresponding request item with uid %q for the patch", patch.UID)
   449  		return errors.Errorf("unable to find corresponding request item for patch")
   450  	}
   451  
   452  	// Use the patch to create a patched copy of the template.
   453  	var patchedTemplate []byte
   454  	var err error
   455  
   456  	switch patch.PatchType {
   457  	case runtimehooksv1.JSONPatchType:
   458  		log.V(5).Infof("Accumulating JSON patch: %s", string(patch.Patch))
   459  		jsonPatch, err := jsonpatch.DecodePatch(patch.Patch)
   460  		if err != nil {
   461  			log.Infof("Failed to apply patch with uid %q: error decoding json patch (RFC6902): %s: %s", requestItem.UID, string(patch.Patch), err)
   462  			return errors.Wrap(err, "failed to apply patch: error decoding json patch (RFC6902)")
   463  		}
   464  
   465  		patchedTemplate, err = jsonPatch.Apply(requestItem.Object.Raw)
   466  		if err != nil {
   467  			log.Infof("Failed to apply patch with uid %q: error applying json patch (RFC6902): %s: %s", requestItem.UID, string(patch.Patch), err)
   468  			return errors.Wrap(err, "failed to apply patch: error applying json patch (RFC6902)")
   469  		}
   470  	case runtimehooksv1.JSONMergePatchType:
   471  		log.V(5).Infof("Accumulating JSON merge patch: %s", string(patch.Patch))
   472  		patchedTemplate, err = jsonpatch.MergePatch(requestItem.Object.Raw, patch.Patch)
   473  		if err != nil {
   474  			log.Infof("Failed to apply patch with uid %q: error applying json merge patch (RFC7386): %s: %s", requestItem.UID, string(patch.Patch), err)
   475  			return errors.Wrap(err, "failed to apply patch: error applying json merge patch (RFC7386)")
   476  		}
   477  	}
   478  
   479  	// Overwrite the spec of template.Template with the spec of the patchedTemplate,
   480  	// to ensure that we only pick up changes to the spec.
   481  	if err := patchTemplateSpec(&requestItem.Object, patchedTemplate); err != nil {
   482  		log.Infof("Failed to apply patch to template %s: %s", requestItem.UID, err)
   483  		return errors.Wrap(err, "failed to apply patch to template")
   484  	}
   485  
   486  	return nil
   487  }
   488  
   489  // convertToValidationRequest converts a GeneratePatchesRequest to a ValidateTopologyRequest.
   490  func convertToValidationRequest(generateRequest *runtimehooksv1.GeneratePatchesRequest) *runtimehooksv1.ValidateTopologyRequest {
   491  	validationRequest := &runtimehooksv1.ValidateTopologyRequest{}
   492  	validationRequest.Variables = generateRequest.Variables
   493  
   494  	for i := range generateRequest.Items {
   495  		item := generateRequest.Items[i]
   496  
   497  		validationRequest.Items = append(validationRequest.Items, &runtimehooksv1.ValidateTopologyRequestItem{
   498  			HolderReference: item.HolderReference,
   499  			Object:          item.Object,
   500  			Variables:       item.Variables,
   501  		})
   502  	}
   503  
   504  	return validationRequest
   505  }
   506  
   507  // updateDesiredState uses the patched templates of a GeneratePatchesRequest to update the desired state.
   508  // NOTE: This func should be called after all the patches have been applied to the GeneratePatchesRequest.
   509  func updateDesiredState(ctx context.Context, req *runtimehooksv1.GeneratePatchesRequest, blueprint *scope.ClusterBlueprint, desired *scope.ClusterState) error {
   510  	var err error
   511  
   512  	// Update the InfrastructureCluster.
   513  	infrastructureClusterTemplate, err := getTemplateAsUnstructured(req, "Cluster", "spec.infrastructureRef", requestTopologyName{})
   514  	if err != nil {
   515  		return err
   516  	}
   517  	if err := patchObject(ctx, desired.InfrastructureCluster, infrastructureClusterTemplate); err != nil {
   518  		return err
   519  	}
   520  
   521  	// Update the ControlPlane.
   522  	controlPlaneTemplate, err := getTemplateAsUnstructured(req, "Cluster", "spec.controlPlaneRef", requestTopologyName{})
   523  	if err != nil {
   524  		return err
   525  	}
   526  	if err := patchObject(ctx, desired.ControlPlane.Object, controlPlaneTemplate, PreserveFields{
   527  		contract.ControlPlane().MachineTemplate().Metadata().Path(),
   528  		contract.ControlPlane().MachineTemplate().InfrastructureRef().Path(),
   529  		contract.ControlPlane().MachineTemplate().NodeDrainTimeout().Path(),
   530  		contract.ControlPlane().MachineTemplate().NodeVolumeDetachTimeout().Path(),
   531  		contract.ControlPlane().MachineTemplate().NodeDeletionTimeout().Path(),
   532  		contract.ControlPlane().Replicas().Path(),
   533  		contract.ControlPlane().Version().Path(),
   534  	}); err != nil {
   535  		return err
   536  	}
   537  
   538  	// If the ClusterClass mandates the ControlPlane has InfrastructureMachines,
   539  	// update the InfrastructureMachineTemplate for ControlPlane machines.
   540  	if blueprint.HasControlPlaneInfrastructureMachine() {
   541  		infrastructureMachineTemplate, err := getTemplateAsUnstructured(req, desired.ControlPlane.Object.GetKind(), strings.Join(contract.ControlPlane().MachineTemplate().InfrastructureRef().Path(), "."), requestTopologyName{})
   542  		if err != nil {
   543  			return err
   544  		}
   545  		if err := patchTemplate(ctx, desired.ControlPlane.InfrastructureMachineTemplate, infrastructureMachineTemplate); err != nil {
   546  			return err
   547  		}
   548  	}
   549  
   550  	// Update the templates for all MachineDeployments.
   551  	for mdTopologyName, md := range desired.MachineDeployments {
   552  		topologyName := requestTopologyName{mdTopologyName: mdTopologyName}
   553  		// Update the BootstrapConfigTemplate.
   554  		bootstrapTemplate, err := getTemplateAsUnstructured(req, "MachineDeployment", "spec.template.spec.bootstrap.configRef", topologyName)
   555  		if err != nil {
   556  			return err
   557  		}
   558  		if err := patchTemplate(ctx, md.BootstrapTemplate, bootstrapTemplate); err != nil {
   559  			return err
   560  		}
   561  
   562  		// Update the InfrastructureMachineTemplate.
   563  		infrastructureMachineTemplate, err := getTemplateAsUnstructured(req, "MachineDeployment", "spec.template.spec.infrastructureRef", topologyName)
   564  		if err != nil {
   565  			return err
   566  		}
   567  		if err := patchTemplate(ctx, md.InfrastructureMachineTemplate, infrastructureMachineTemplate); err != nil {
   568  			return err
   569  		}
   570  	}
   571  
   572  	// Update the templates for all MachinePools.
   573  	for mpTopologyName, mp := range desired.MachinePools {
   574  		topologyName := requestTopologyName{mpTopologyName: mpTopologyName}
   575  		// Update the BootstrapConfig.
   576  		bootstrapTemplate, err := getTemplateAsUnstructured(req, "MachinePool", "spec.template.spec.bootstrap.configRef", topologyName)
   577  		if err != nil {
   578  			return err
   579  		}
   580  		if err := patchObject(ctx, mp.BootstrapObject, bootstrapTemplate); err != nil {
   581  			return err
   582  		}
   583  
   584  		// Update the InfrastructureMachinePool.
   585  		infrastructureMachinePoolTemplate, err := getTemplateAsUnstructured(req, "MachinePool", "spec.template.spec.infrastructureRef", topologyName)
   586  		if err != nil {
   587  			return err
   588  		}
   589  		if err := patchObject(ctx, mp.InfrastructureMachinePoolObject, infrastructureMachinePoolTemplate); err != nil {
   590  			return err
   591  		}
   592  	}
   593  
   594  	return nil
   595  }
   596  
   597  // definitionsForPatch returns a set which of variables in a ClusterClass defined by "definitionFrom".
   598  func definitionsForPatch(blueprint *scope.ClusterBlueprint, definitionFrom string) map[string]bool {
   599  	variableDefinitionsForPatch := make(map[string]bool)
   600  	for _, definitionsWithName := range blueprint.ClusterClass.Status.Variables {
   601  		for _, definition := range definitionsWithName.Definitions {
   602  			if definition.From == definitionFrom {
   603  				variableDefinitionsForPatch[definitionsWithName.Name] = true
   604  			}
   605  		}
   606  	}
   607  	return variableDefinitionsForPatch
   608  }