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 }