Skip to content

Commit

Permalink
Merge pull request #5534 from sbueringer/pr-clusterclass-patches-engine
Browse files Browse the repository at this point in the history
✨  Add ClusterClass patch engine
  • Loading branch information
k8s-ci-robot authored Nov 9, 2021
2 parents 3699cd5 + 1c52899 commit 7233fd7
Show file tree
Hide file tree
Showing 13 changed files with 2,314 additions and 17 deletions.
6 changes: 6 additions & 0 deletions controllers/topology/cluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/cluster-api/api/v1beta1/index"
"sigs.k8s.io/cluster-api/controllers/external"
"sigs.k8s.io/cluster-api/controllers/topology/internal/extensions/patches"
"sigs.k8s.io/cluster-api/controllers/topology/internal/scope"
"sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/cluster-api/util/annotations"
Expand Down Expand Up @@ -58,6 +59,9 @@ type ClusterReconciler struct {
UnstructuredCachingClient client.Client

externalTracker external.ObjectTracker

// patchEngine is used to apply patches during computeDesiredState.
patchEngine patches.Engine
}

func (r *ClusterReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
Expand All @@ -83,6 +87,8 @@ func (r *ClusterReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manag
r.externalTracker = external.ObjectTracker{
Controller: c,
}
r.patchEngine = patches.NewEngine()

return nil
}

Expand Down
31 changes: 19 additions & 12 deletions controllers/topology/desired_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import (

// computeDesiredState computes the desired state of the cluster topology.
// NOTE: We are assuming all the required objects are provided as input; also, in case of any error,
// the entire compute operation operation will fail. This might be improved in the future if support for reconciling
// the entire compute operation will fail. This might be improved in the future if support for reconciling
// subset of a topology will be implemented.
func (r *ClusterReconciler) computeDesiredState(ctx context.Context, s *scope.Scope) (*scope.ClusterState, error) {
var err error
Expand All @@ -46,36 +46,43 @@ func (r *ClusterReconciler) computeDesiredState(ctx context.Context, s *scope.Sc

// Compute the desired state of the InfrastructureCluster object.
if desiredState.InfrastructureCluster, err = computeInfrastructureCluster(ctx, s); err != nil {
return nil, err
return nil, errors.Wrapf(err, "failed to compute InfrastructureCluster")
}

// If the clusterClass mandates the controlPlane has infrastructureMachines, compute the InfrastructureMachineTemplate for the ControlPlane.
if s.Blueprint.HasControlPlaneInfrastructureMachine() {
if desiredState.ControlPlane.InfrastructureMachineTemplate, err = computeControlPlaneInfrastructureMachineTemplate(ctx, s); err != nil {
return nil, err
return nil, errors.Wrapf(err, "failed to compute ControlPlane InfrastructureMachineTemplate")
}
}

// Compute the desired state of the ControlPlane object, eventually adding a reference to the
// InfrastructureMachineTemplate generated by the previous step.
if desiredState.ControlPlane.Object, err = computeControlPlane(ctx, s, desiredState.ControlPlane.InfrastructureMachineTemplate); err != nil {
return nil, err
return nil, errors.Wrapf(err, "failed to compute ControlPlane")
}

// Compute the desired state for the Cluster object adding a reference to the
// InfrastructureCluster and the ControlPlane objects generated by the previous step.
desiredState.Cluster = computeCluster(ctx, s, desiredState.InfrastructureCluster, desiredState.ControlPlane.Object)

// If required by the blueprint, compute the desired state of the MachineDeployment objects for the worker nodes, if any.
if !s.Blueprint.HasMachineDeployments() {
return desiredState, nil
// If required, compute the desired state of the MachineDeployments from the list of MachineDeploymentTopologies
// defined in the cluster.
if s.Blueprint.HasMachineDeployments() {
desiredState.MachineDeployments, err = computeMachineDeployments(ctx, s, desiredState.ControlPlane)
if err != nil {
return nil, errors.Wrapf(err, "failed to compute MachineDeployments")
}
}

// Compute the desired state of the MachineDeployments from the list of MachineDeploymentTopologies
// defined in the cluster.
desiredState.MachineDeployments, err = computeMachineDeployments(ctx, s, desiredState.ControlPlane)
if err != nil {
return nil, err
// Apply patches the desired state according to the patches from the ClusterClass, variables from the Cluster
// and builtin variables.
// NOTE: We have to make sure all spec fields that were explicitly set in desired objects during the computation above
// are preserved during patching. When desired objects are computed their spec is copied from a template, in some cases
// further modifications to the spec are made afterwards. In those cases we have to make sure those fields are not overwritten
// in apply patches. Some examples are .spec.machineTemplate and .spec.version in control planes.
if err := r.patchEngine.Apply(ctx, s.Blueprint, desiredState); err != nil {
return nil, errors.Wrap(err, "failed to apply patches")
}

return desiredState, nil
Expand Down
138 changes: 138 additions & 0 deletions controllers/topology/internal/extensions/patches/api/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package api contains the API definition for the patch engine.
// NOTE: We are introducing this API as a decoupling layer between the patch engine and the concrete components
// responsible for generating patches, because we aim to provide support for external patches in a future iteration.
// We also assume that this API and all the related types will be moved in a separate (versioned) package thus
// providing a versioned contract between Cluster API and the components implementing external patch extensions.
package api

import (
"context"

apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)

// Generator defines a component that can generate patches for ClusterClass templates.
type Generator interface {
// Generate generates patches for templates.
// GenerateRequest contains templates and the corresponding variables.
// GenerateResponse contains the patches which should be applied to the templates of the GenerateRequest.
Generate(ctx context.Context, request *GenerateRequest) (*GenerateResponse, error)
}

// GenerateRequest defines the input for a Generate request.
type GenerateRequest struct {
// Variables is a name/value map containing variables.
Variables map[string]apiextensionsv1.JSON

// Items contains the list of templates to generate patches for.
Items []*GenerateRequestTemplate
}

// GenerateRequestTemplate defines one of the ClusterClass templates to generate patches for.
type GenerateRequestTemplate struct {
// TemplateRef identifies a template to generate patches for;
// the same TemplateRef must be used when specifying to which template a generated patch should be applied to.
TemplateRef TemplateRef

// Variables is a name/value map containing variables specifically for the current template.
// For example some builtin variables like MachineDeployment replicas and version are context-sensitive
// and thus are only added to templates for MachineDeployments and with values which correspond to the
// current MachineDeployment.
Variables map[string]apiextensionsv1.JSON

// Template contains the template.
Template apiextensionsv1.JSON
}

// TemplateRef identifies one of the ClusterClass templates to generate patches for;
// the same TemplateRef must be used when specifying where a generated patch should apply to.
type TemplateRef struct {
// APIVersion of the current template.
APIVersion string

// Kind of the current template.
Kind string

// TemplateType defines where the template is used.
TemplateType TemplateType

// MachineDeployment specifies the MachineDeployment in which the template is used.
// This field is only set if the template is used in the context of a MachineDeployment.
MachineDeploymentRef MachineDeploymentRef
}

// MachineDeploymentRef specifies the MachineDeployment in which the template is used.
type MachineDeploymentRef struct {
// TopologyName is the name of the MachineDeploymentTopology.
TopologyName string

// Class is the name of the MachineDeploymentClass.
Class string
}

// TemplateType define the type for target types enum.
type TemplateType string

const (
// InfrastructureClusterTemplateType identifies a template for the InfrastructureCluster object.
InfrastructureClusterTemplateType TemplateType = "InfrastructureClusterTemplate"

// ControlPlaneTemplateType identifies a template for the ControlPlane object.
ControlPlaneTemplateType TemplateType = "ControlPlaneTemplate"

// ControlPlaneInfrastructureMachineTemplateType identifies a template for the InfrastructureMachines to be used for the ControlPlane object.
ControlPlaneInfrastructureMachineTemplateType TemplateType = "ControlPlane/InfrastructureMachineTemplate"

// MachineDeploymentBootstrapConfigTemplateType identifies a template for the BootstrapConfig to be used for a MachineDeployment object.
MachineDeploymentBootstrapConfigTemplateType TemplateType = "MachineDeployment/BootstrapConfigTemplate"

// MachineDeploymentInfrastructureMachineTemplateType identifies a template for the InfrastructureMachines to be used for a MachineDeployment object.
MachineDeploymentInfrastructureMachineTemplateType TemplateType = "MachineDeployment/InfrastructureMachineTemplate"
)

// PatchType define the type for patch types enum.
type PatchType string

const (
// JSONPatchType identifies a https://datatracker.ietf.org/doc/html/rfc6902 json patch.
JSONPatchType PatchType = "JSONPatch"

// JSONMergePatchType identifies a https://datatracker.ietf.org/doc/html/rfc7386 json merge patch.
JSONMergePatchType PatchType = "JSONMergePatch"
)

// GenerateResponse defines the response of a Generate request.
// NOTE: Patches defined in GenerateResponse will be applied in the same order to the original
// GenerateRequest object, thus adding changes on templates across all the subsequent Generate calls.
type GenerateResponse struct {
// Items contains the list of generated patches.
Items []GenerateResponsePatch
}

// GenerateResponsePatch defines a Patch targeting a specific GenerateRequestTemplate.
type GenerateResponsePatch struct {
// TemplateRef identifies the template the patch should apply to.
TemplateRef TemplateRef

// Patch contains the patch.
Patch apiextensionsv1.JSON

// Patch defines the type of the JSON patch.
PatchType PatchType
}
Loading

0 comments on commit 7233fd7

Please sign in to comment.