Skip to content

Commit 7233fd7

Browse files
authored
Merge pull request #5534 from sbueringer/pr-clusterclass-patches-engine
✨ Add ClusterClass patch engine
2 parents 3699cd5 + 1c52899 commit 7233fd7

File tree

13 files changed

+2314
-17
lines changed

13 files changed

+2314
-17
lines changed

controllers/topology/cluster_controller.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
2727
"sigs.k8s.io/cluster-api/api/v1beta1/index"
2828
"sigs.k8s.io/cluster-api/controllers/external"
29+
"sigs.k8s.io/cluster-api/controllers/topology/internal/extensions/patches"
2930
"sigs.k8s.io/cluster-api/controllers/topology/internal/scope"
3031
"sigs.k8s.io/cluster-api/util"
3132
"sigs.k8s.io/cluster-api/util/annotations"
@@ -58,6 +59,9 @@ type ClusterReconciler struct {
5859
UnstructuredCachingClient client.Client
5960

6061
externalTracker external.ObjectTracker
62+
63+
// patchEngine is used to apply patches during computeDesiredState.
64+
patchEngine patches.Engine
6165
}
6266

6367
func (r *ClusterReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
@@ -83,6 +87,8 @@ func (r *ClusterReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manag
8387
r.externalTracker = external.ObjectTracker{
8488
Controller: c,
8589
}
90+
r.patchEngine = patches.NewEngine()
91+
8692
return nil
8793
}
8894

controllers/topology/desired_state.go

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import (
3636

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

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

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

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

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

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

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

8188
return desiredState, nil
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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 api contains the API definition for the patch engine.
18+
// NOTE: We are introducing this API as a decoupling layer between the patch engine and the concrete components
19+
// responsible for generating patches, because we aim to provide support for external patches in a future iteration.
20+
// We also assume that this API and all the related types will be moved in a separate (versioned) package thus
21+
// providing a versioned contract between Cluster API and the components implementing external patch extensions.
22+
package api
23+
24+
import (
25+
"context"
26+
27+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
28+
)
29+
30+
// Generator defines a component that can generate patches for ClusterClass templates.
31+
type Generator interface {
32+
// Generate generates patches for templates.
33+
// GenerateRequest contains templates and the corresponding variables.
34+
// GenerateResponse contains the patches which should be applied to the templates of the GenerateRequest.
35+
Generate(ctx context.Context, request *GenerateRequest) (*GenerateResponse, error)
36+
}
37+
38+
// GenerateRequest defines the input for a Generate request.
39+
type GenerateRequest struct {
40+
// Variables is a name/value map containing variables.
41+
Variables map[string]apiextensionsv1.JSON
42+
43+
// Items contains the list of templates to generate patches for.
44+
Items []*GenerateRequestTemplate
45+
}
46+
47+
// GenerateRequestTemplate defines one of the ClusterClass templates to generate patches for.
48+
type GenerateRequestTemplate struct {
49+
// TemplateRef identifies a template to generate patches for;
50+
// the same TemplateRef must be used when specifying to which template a generated patch should be applied to.
51+
TemplateRef TemplateRef
52+
53+
// Variables is a name/value map containing variables specifically for the current template.
54+
// For example some builtin variables like MachineDeployment replicas and version are context-sensitive
55+
// and thus are only added to templates for MachineDeployments and with values which correspond to the
56+
// current MachineDeployment.
57+
Variables map[string]apiextensionsv1.JSON
58+
59+
// Template contains the template.
60+
Template apiextensionsv1.JSON
61+
}
62+
63+
// TemplateRef identifies one of the ClusterClass templates to generate patches for;
64+
// the same TemplateRef must be used when specifying where a generated patch should apply to.
65+
type TemplateRef struct {
66+
// APIVersion of the current template.
67+
APIVersion string
68+
69+
// Kind of the current template.
70+
Kind string
71+
72+
// TemplateType defines where the template is used.
73+
TemplateType TemplateType
74+
75+
// MachineDeployment specifies the MachineDeployment in which the template is used.
76+
// This field is only set if the template is used in the context of a MachineDeployment.
77+
MachineDeploymentRef MachineDeploymentRef
78+
}
79+
80+
// MachineDeploymentRef specifies the MachineDeployment in which the template is used.
81+
type MachineDeploymentRef struct {
82+
// TopologyName is the name of the MachineDeploymentTopology.
83+
TopologyName string
84+
85+
// Class is the name of the MachineDeploymentClass.
86+
Class string
87+
}
88+
89+
// TemplateType define the type for target types enum.
90+
type TemplateType string
91+
92+
const (
93+
// InfrastructureClusterTemplateType identifies a template for the InfrastructureCluster object.
94+
InfrastructureClusterTemplateType TemplateType = "InfrastructureClusterTemplate"
95+
96+
// ControlPlaneTemplateType identifies a template for the ControlPlane object.
97+
ControlPlaneTemplateType TemplateType = "ControlPlaneTemplate"
98+
99+
// ControlPlaneInfrastructureMachineTemplateType identifies a template for the InfrastructureMachines to be used for the ControlPlane object.
100+
ControlPlaneInfrastructureMachineTemplateType TemplateType = "ControlPlane/InfrastructureMachineTemplate"
101+
102+
// MachineDeploymentBootstrapConfigTemplateType identifies a template for the BootstrapConfig to be used for a MachineDeployment object.
103+
MachineDeploymentBootstrapConfigTemplateType TemplateType = "MachineDeployment/BootstrapConfigTemplate"
104+
105+
// MachineDeploymentInfrastructureMachineTemplateType identifies a template for the InfrastructureMachines to be used for a MachineDeployment object.
106+
MachineDeploymentInfrastructureMachineTemplateType TemplateType = "MachineDeployment/InfrastructureMachineTemplate"
107+
)
108+
109+
// PatchType define the type for patch types enum.
110+
type PatchType string
111+
112+
const (
113+
// JSONPatchType identifies a https://datatracker.ietf.org/doc/html/rfc6902 json patch.
114+
JSONPatchType PatchType = "JSONPatch"
115+
116+
// JSONMergePatchType identifies a https://datatracker.ietf.org/doc/html/rfc7386 json merge patch.
117+
JSONMergePatchType PatchType = "JSONMergePatch"
118+
)
119+
120+
// GenerateResponse defines the response of a Generate request.
121+
// NOTE: Patches defined in GenerateResponse will be applied in the same order to the original
122+
// GenerateRequest object, thus adding changes on templates across all the subsequent Generate calls.
123+
type GenerateResponse struct {
124+
// Items contains the list of generated patches.
125+
Items []GenerateResponsePatch
126+
}
127+
128+
// GenerateResponsePatch defines a Patch targeting a specific GenerateRequestTemplate.
129+
type GenerateResponsePatch struct {
130+
// TemplateRef identifies the template the patch should apply to.
131+
TemplateRef TemplateRef
132+
133+
// Patch contains the patch.
134+
Patch apiextensionsv1.JSON
135+
136+
// Patch defines the type of the JSON patch.
137+
PatchType PatchType
138+
}

0 commit comments

Comments
 (0)