@@ -21,8 +21,10 @@ import (
21
21
"fmt"
22
22
23
23
"github.com/pkg/errors"
24
+ corev1 "k8s.io/api/core/v1"
24
25
apierrors "k8s.io/apimachinery/pkg/api/errors"
25
26
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
27
+ "k8s.io/apimachinery/pkg/runtime/schema"
26
28
"sigs.k8s.io/controller-runtime/pkg/client"
27
29
28
30
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -41,7 +43,7 @@ func (r *Reconciler) getCurrentState(ctx context.Context, s *scope.Scope) (*scop
41
43
// Reference to the InfrastructureCluster can be nil and is expected to be on the first reconcile.
42
44
// In this case the method should still be allowed to continue.
43
45
if currentState .Cluster .Spec .InfrastructureRef != nil {
44
- infra , err := r .getCurrentInfrastructureClusterState (ctx , currentState .Cluster )
46
+ infra , err := r .getCurrentInfrastructureClusterState (ctx , s . Blueprint . InfrastructureClusterTemplate , currentState .Cluster )
45
47
if err != nil {
46
48
return nil , err
47
49
}
@@ -52,7 +54,7 @@ func (r *Reconciler) getCurrentState(ctx context.Context, s *scope.Scope) (*scop
52
54
// should still be allowed to continue.
53
55
currentState .ControlPlane = & scope.ControlPlaneState {}
54
56
if currentState .Cluster .Spec .ControlPlaneRef != nil {
55
- cp , err := r .getCurrentControlPlaneState (ctx , currentState . Cluster , s .Blueprint )
57
+ cp , err := r .getCurrentControlPlaneState (ctx , s . Blueprint . ControlPlane , s .Blueprint . HasControlPlaneInfrastructureMachine (), currentState . Cluster )
56
58
if err != nil {
57
59
return nil , err
58
60
}
@@ -61,7 +63,7 @@ func (r *Reconciler) getCurrentState(ctx context.Context, s *scope.Scope) (*scop
61
63
62
64
// A Cluster may have zero or more MachineDeployments and a Cluster is expected to have zero MachineDeployments on
63
65
// first reconcile.
64
- m , err := r .getCurrentMachineDeploymentState (ctx , currentState .Cluster )
66
+ m , err := r .getCurrentMachineDeploymentState (ctx , s . Blueprint . MachineDeployments , currentState .Cluster )
65
67
if err != nil {
66
68
return nil , err
67
69
}
@@ -72,8 +74,12 @@ func (r *Reconciler) getCurrentState(ctx context.Context, s *scope.Scope) (*scop
72
74
73
75
// getCurrentInfrastructureClusterState looks for the state of the InfrastructureCluster. If a reference is set but not
74
76
// found, either from an error or the object not being found, an error is thrown.
75
- func (r * Reconciler ) getCurrentInfrastructureClusterState (ctx context.Context , cluster * clusterv1.Cluster ) (* unstructured.Unstructured , error ) {
76
- infra , err := r .getReference (ctx , cluster .Spec .InfrastructureRef )
77
+ func (r * Reconciler ) getCurrentInfrastructureClusterState (ctx context.Context , blueprintInfrastructureClusterTemplate * unstructured.Unstructured , cluster * clusterv1.Cluster ) (* unstructured.Unstructured , error ) {
78
+ ref , err := alignRefAPIVersion (blueprintInfrastructureClusterTemplate , cluster .Spec .InfrastructureRef )
79
+ if err != nil {
80
+ return nil , errors .Wrapf (err , "failed to read %s" , tlog.KRef {Ref : cluster .Spec .InfrastructureRef })
81
+ }
82
+ infra , err := r .getReference (ctx , ref )
77
83
if err != nil {
78
84
return nil , errors .Wrapf (err , "failed to read %s" , tlog.KRef {Ref : cluster .Spec .InfrastructureRef })
79
85
}
@@ -89,12 +95,16 @@ func (r *Reconciler) getCurrentInfrastructureClusterState(ctx context.Context, c
89
95
// getCurrentControlPlaneState returns information on the ControlPlane being used by the Cluster. If a reference is not found,
90
96
// an error is thrown. If the ControlPlane requires MachineInfrastructure according to its ClusterClass an error will be
91
97
// thrown if the ControlPlane has no MachineTemplates.
92
- func (r * Reconciler ) getCurrentControlPlaneState (ctx context.Context , cluster * clusterv1. Cluster , blueprint * scope. ClusterBlueprint ) (* scope.ControlPlaneState , error ) {
98
+ func (r * Reconciler ) getCurrentControlPlaneState (ctx context.Context , blueprintControlPlane * scope. ControlPlaneBlueprint , blueprintHasControlPlaneInfrastructureMachine bool , cluster * clusterv1. Cluster ) (* scope.ControlPlaneState , error ) {
93
99
var err error
94
100
res := & scope.ControlPlaneState {}
95
101
96
102
// Get the control plane object.
97
- res .Object , err = r .getReference (ctx , cluster .Spec .ControlPlaneRef )
103
+ ref , err := alignRefAPIVersion (blueprintControlPlane .Template , cluster .Spec .ControlPlaneRef )
104
+ if err != nil {
105
+ return nil , errors .Wrapf (err , "failed to read %s" , tlog.KRef {Ref : cluster .Spec .ControlPlaneRef })
106
+ }
107
+ res .Object , err = r .getReference (ctx , ref )
98
108
if err != nil {
99
109
return nil , errors .Wrapf (err , "failed to read %s" , tlog.KRef {Ref : cluster .Spec .ControlPlaneRef })
100
110
}
@@ -106,7 +116,7 @@ func (r *Reconciler) getCurrentControlPlaneState(ctx context.Context, cluster *c
106
116
}
107
117
108
118
// If the clusterClass does not mandate the controlPlane has infrastructureMachines, return.
109
- if ! blueprint . HasControlPlaneInfrastructureMachine () {
119
+ if ! blueprintHasControlPlaneInfrastructureMachine {
110
120
return res , nil
111
121
}
112
122
@@ -115,7 +125,11 @@ func (r *Reconciler) getCurrentControlPlaneState(ctx context.Context, cluster *c
115
125
if err != nil {
116
126
return res , errors .Wrapf (err , "failed to get InfrastructureMachineTemplate reference for %s" , tlog.KObj {Obj : res .Object })
117
127
}
118
- res .InfrastructureMachineTemplate , err = r .getReference (ctx , machineInfrastructureRef )
128
+ ref , err = alignRefAPIVersion (blueprintControlPlane .InfrastructureMachineTemplate , machineInfrastructureRef )
129
+ if err != nil {
130
+ return nil , errors .Wrapf (err , "failed to get InfrastructureMachineTemplate for %s" , tlog.KObj {Obj : res .Object })
131
+ }
132
+ res .InfrastructureMachineTemplate , err = r .getReference (ctx , ref )
119
133
if err != nil {
120
134
return nil , errors .Wrapf (err , "failed to get InfrastructureMachineTemplate for %s" , tlog.KObj {Obj : res .Object })
121
135
}
@@ -143,7 +157,7 @@ func (r *Reconciler) getCurrentControlPlaneState(ctx context.Context, cluster *c
143
157
// whether they are managed by a ClusterClass using labels. A Cluster may have zero or more MachineDeployments. Zero is
144
158
// expected on first reconcile. If MachineDeployments are found for the Cluster their Infrastructure and Bootstrap references
145
159
// are inspected. Where these are not found the function will throw an error.
146
- func (r * Reconciler ) getCurrentMachineDeploymentState (ctx context.Context , cluster * clusterv1.Cluster ) (map [string ]* scope.MachineDeploymentState , error ) {
160
+ func (r * Reconciler ) getCurrentMachineDeploymentState (ctx context.Context , blueprintMachineDeployments map [ string ] * scope. MachineDeploymentBlueprint , cluster * clusterv1.Cluster ) (map [string ]* scope.MachineDeploymentState , error ) {
147
161
state := make (scope.MachineDeploymentsStateMap )
148
162
149
163
// List all the machine deployments in the current cluster and in a managed topology.
@@ -178,12 +192,26 @@ func (r *Reconciler) getCurrentMachineDeploymentState(ctx context.Context, clust
178
192
return nil , fmt .Errorf ("duplicate %s found for label %s: %s" , tlog.KObj {Obj : m }, clusterv1 .ClusterTopologyMachineDeploymentLabelName , mdTopologyName )
179
193
}
180
194
195
+ mdClassName := getMDClassName (cluster , mdTopologyName )
196
+ if mdClassName == "" {
197
+ return nil , fmt .Errorf ("failed to find MachineDeployment topology %s in %s" , mdTopologyName , tlog.KObj {Obj : cluster })
198
+ }
199
+
200
+ mdBluePrint , ok := blueprintMachineDeployments [mdClassName ]
201
+ if ! ok {
202
+ return nil , fmt .Errorf ("failed to find MachineDeployment class %s in ClusterClass" , mdClassName )
203
+ }
204
+
181
205
// Gets the BootstrapTemplate
182
206
bootstrapRef := m .Spec .Template .Spec .Bootstrap .ConfigRef
183
207
if bootstrapRef == nil {
184
208
return nil , fmt .Errorf ("%s does not have a reference to a Bootstrap Config" , tlog.KObj {Obj : m })
185
209
}
186
- b , err := r .getReference (ctx , bootstrapRef )
210
+ ref , err := alignRefAPIVersion (mdBluePrint .BootstrapTemplate , bootstrapRef )
211
+ if err != nil {
212
+ return nil , errors .Wrap (err , fmt .Sprintf ("%s Bootstrap reference could not be retrieved" , tlog.KObj {Obj : m }))
213
+ }
214
+ b , err := r .getReference (ctx , ref )
187
215
if err != nil {
188
216
return nil , errors .Wrap (err , fmt .Sprintf ("%s Bootstrap reference could not be retrieved" , tlog.KObj {Obj : m }))
189
217
}
@@ -199,7 +227,11 @@ func (r *Reconciler) getCurrentMachineDeploymentState(ctx context.Context, clust
199
227
if infraRef .Name == "" {
200
228
return nil , fmt .Errorf ("%s does not have a reference to a InfrastructureMachineTemplate" , tlog.KObj {Obj : m })
201
229
}
202
- infra , err := r .getReference (ctx , & infraRef )
230
+ ref , err = alignRefAPIVersion (mdBluePrint .InfrastructureMachineTemplate , & infraRef )
231
+ if err != nil {
232
+ return nil , errors .Wrap (err , fmt .Sprintf ("%s Infrastructure reference could not be retrieved" , tlog.KObj {Obj : m }))
233
+ }
234
+ infra , err := r .getReference (ctx , ref )
203
235
if err != nil {
204
236
return nil , errors .Wrap (err , fmt .Sprintf ("%s Infrastructure reference could not be retrieved" , tlog.KObj {Obj : m }))
205
237
}
@@ -232,3 +264,44 @@ func (r *Reconciler) getCurrentMachineDeploymentState(ctx context.Context, clust
232
264
}
233
265
return state , nil
234
266
}
267
+
268
+ // alignRefAPIVersion returns an aligned copy of the currentRef so it matches the apiVersion in ClusterClass.
269
+ // This is required so the topology controller can diff current and desired state objects of the same
270
+ // version during reconcile.
271
+ // If group or kind was changed in the ClusterClass, an exact copy of the currentRef is returned because
272
+ // it will end up in a diff and a rollout anyway.
273
+ // Only bootstrap template refs in a ClusterClass can change their group and kind.
274
+ func alignRefAPIVersion (templateFromClusterClass * unstructured.Unstructured , currentRef * corev1.ObjectReference ) (* corev1.ObjectReference , error ) {
275
+ currentGV , err := schema .ParseGroupVersion (currentRef .APIVersion )
276
+ if err != nil {
277
+ return nil , errors .Wrapf (err , "failed to parse apiVersion: %q" , currentRef .APIVersion )
278
+ }
279
+
280
+ apiVersion := currentRef .APIVersion
281
+ // Use apiVersion from ClusterClass if group and kind is the same.
282
+ if templateFromClusterClass .GroupVersionKind ().Group == currentGV .Group &&
283
+ templateFromClusterClass .GetKind () == currentRef .Kind {
284
+ apiVersion = templateFromClusterClass .GetAPIVersion ()
285
+ }
286
+
287
+ return & corev1.ObjectReference {
288
+ APIVersion : apiVersion ,
289
+ Kind : currentRef .Kind ,
290
+ Namespace : currentRef .Namespace ,
291
+ Name : currentRef .Name ,
292
+ }, nil
293
+ }
294
+
295
+ // getMDClassName retrieves the MDClass name by looking up the MDTopology in the Cluster.
296
+ func getMDClassName (cluster * clusterv1.Cluster , mdTopologyName string ) string {
297
+ if cluster .Spec .Topology .Workers == nil {
298
+ return ""
299
+ }
300
+
301
+ for _ , mdTopology := range cluster .Spec .Topology .Workers .MachineDeployments {
302
+ if mdTopology .Name == mdTopologyName {
303
+ return mdTopology .Class
304
+ }
305
+ }
306
+ return ""
307
+ }
0 commit comments