Skip to content

Commit 16c46dd

Browse files
authored
Merge pull request #11428 from sbueringer/pr-cc-v1beta2-conditions
🌱 Add v1beta2 conditions to ClusterClass
2 parents 71176a3 + b734c57 commit 16c46dd

6 files changed

+416
-141
lines changed

api/v1beta1/clusterclass_types.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,40 @@ import (
2828
// ClusterClassKind represents the Kind of ClusterClass.
2929
const ClusterClassKind = "ClusterClass"
3030

31-
// Conditions that will be used for the ClusterClass object in v1Beta2 API version.
31+
// ClusterClass VariablesReady condition and corresponding reasons that will be used in v1Beta2 API version.
3232
const (
3333
// ClusterClassVariablesReadyV1Beta2Condition is true if the ClusterClass variables, including both inline and external
3434
// variables, have been successfully reconciled and thus ready to be used to default and validate variables on Clusters using
3535
// this ClusterClass.
3636
ClusterClassVariablesReadyV1Beta2Condition = "VariablesReady"
3737

38+
// ClusterClassVariablesReadyV1Beta2Reason surfaces that the variables are ready.
39+
ClusterClassVariablesReadyV1Beta2Reason = "VariablesReady"
40+
41+
// ClusterClassVariablesReadyVariableDiscoveryFailedV1Beta2Reason surfaces that variable discovery failed.
42+
ClusterClassVariablesReadyVariableDiscoveryFailedV1Beta2Reason = "VariableDiscoveryFailed"
43+
)
44+
45+
// ClusterClass RefVersionsUpToDate condition and corresponding reasons that will be used in v1Beta2 API version.
46+
const (
3847
// ClusterClassRefVersionsUpToDateV1Beta2Condition documents if the references in the ClusterClass are
3948
// up-to-date (i.e. they are using the latest apiVersion of the current Cluster API contract from
4049
// the corresponding CRD).
4150
ClusterClassRefVersionsUpToDateV1Beta2Condition = "RefVersionsUpToDate"
51+
52+
// ClusterClassRefVersionsUpToDateV1Beta2Reason surfaces that the references in the ClusterClass are
53+
// up-to-date (i.e. they are using the latest apiVersion of the current Cluster API contract from
54+
// the corresponding CRD).
55+
ClusterClassRefVersionsUpToDateV1Beta2Reason = "RefVersionsUpToDate"
56+
57+
// ClusterClassRefVersionsNotUpToDateV1Beta2Reason surfaces that the references in the ClusterClass are not
58+
// up-to-date (i.e. they are not using the latest apiVersion of the current Cluster API contract from
59+
// the corresponding CRD).
60+
ClusterClassRefVersionsNotUpToDateV1Beta2Reason = "RefVersionsNotUpToDate"
61+
62+
// ClusterClassRefVersionsUpToDateInternalErrorV1Beta2Reason surfaces that an unexpected error occurred when validating
63+
// if the references are up-to-date.
64+
ClusterClassRefVersionsUpToDateInternalErrorV1Beta2Reason = InternalErrorV1Beta2Reason
4265
)
4366

4467
// +kubebuilder:object:root=true

api/v1beta1/condition_consts.go

+4
Original file line numberDiff line numberDiff line change
@@ -353,4 +353,8 @@ const (
353353
// up-to-date (i.e. they are not using the latest apiVersion of the current Cluster API contract from
354354
// the corresponding CRD).
355355
ClusterClassOutdatedRefVersionsReason = "OutdatedRefVersions"
356+
357+
// ClusterClassRefVersionsUpToDateInternalErrorReason (Severity=Warning) surfaces that an unexpected error occurred when validating
358+
// if the references are up-to-date.
359+
ClusterClassRefVersionsUpToDateInternalErrorReason = "InternalError"
356360
)

internal/controllers/clusterclass/clusterclass_controller.go

+89-52
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import (
4747
"sigs.k8s.io/cluster-api/feature"
4848
runtimeclient "sigs.k8s.io/cluster-api/internal/runtime/client"
4949
"sigs.k8s.io/cluster-api/internal/topology/variables"
50-
"sigs.k8s.io/cluster-api/util/conditions"
50+
"sigs.k8s.io/cluster-api/util"
5151
"sigs.k8s.io/cluster-api/util/conversion"
5252
"sigs.k8s.io/cluster-api/util/patch"
5353
"sigs.k8s.io/cluster-api/util/paused"
@@ -94,7 +94,7 @@ func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opt
9494
return nil
9595
}
9696

97-
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) {
97+
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (retres ctrl.Result, reterr error) {
9898
clusterClass := &clusterv1.ClusterClass{}
9999
if err := r.Client.Get(ctx, req.NamespacedName, clusterClass); err != nil {
100100
if apierrors.IsNotFound(err) {
@@ -112,40 +112,94 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re
112112
return ctrl.Result{}, nil
113113
}
114114

115+
s := &scope{
116+
clusterClass: clusterClass,
117+
}
118+
115119
patchHelper, err := patch.NewHelper(clusterClass, r.Client)
116120
if err != nil {
117121
return ctrl.Result{}, err
118122
}
119123

120124
defer func() {
125+
updateStatus(ctx, s)
126+
127+
patchOpts := []patch.Option{
128+
patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{
129+
clusterv1.ClusterClassRefVersionsUpToDateCondition,
130+
clusterv1.ClusterClassVariablesReconciledCondition,
131+
}},
132+
patch.WithOwnedV1Beta2Conditions{Conditions: []string{
133+
clusterv1.ClusterClassRefVersionsUpToDateV1Beta2Condition,
134+
clusterv1.ClusterClassVariablesReadyV1Beta2Condition,
135+
}},
136+
}
137+
121138
// Patch ObservedGeneration only if the reconciliation completed successfully
122-
patchOpts := []patch.Option{}
123139
if reterr == nil {
124140
patchOpts = append(patchOpts, patch.WithStatusObservedGeneration{})
125141
}
126142
if err := patchHelper.Patch(ctx, clusterClass, patchOpts...); err != nil {
127143
reterr = kerrors.NewAggregate([]error{reterr, err})
128144
return
129145
}
146+
147+
if reterr != nil {
148+
retres = ctrl.Result{}
149+
}
130150
}()
131-
return ctrl.Result{}, r.reconcile(ctx, clusterClass)
151+
152+
reconcileNormal := []clusterClassReconcileFunc{
153+
r.reconcileExternalReferences,
154+
r.reconcileVariables,
155+
}
156+
return doReconcile(ctx, reconcileNormal, s)
132157
}
133158

134-
func (r *Reconciler) reconcile(ctx context.Context, clusterClass *clusterv1.ClusterClass) error {
135-
if err := r.reconcileVariables(ctx, clusterClass); err != nil {
136-
return err
159+
type clusterClassReconcileFunc func(context.Context, *scope) (ctrl.Result, error)
160+
161+
func doReconcile(ctx context.Context, phases []clusterClassReconcileFunc, s *scope) (ctrl.Result, error) {
162+
res := ctrl.Result{}
163+
errs := []error{}
164+
for _, phase := range phases {
165+
// Call the inner reconciliation methods.
166+
phaseResult, err := phase(ctx, s)
167+
if err != nil {
168+
errs = append(errs, err)
169+
}
170+
if len(errs) > 0 {
171+
continue
172+
}
173+
res = util.LowestNonZeroResult(res, phaseResult)
137174
}
138-
outdatedRefs, err := r.reconcileExternalReferences(ctx, clusterClass)
139-
if err != nil {
140-
return err
175+
176+
if len(errs) > 0 {
177+
return ctrl.Result{}, kerrors.NewAggregate(errs)
141178
}
142179

143-
reconcileConditions(clusterClass, outdatedRefs)
180+
return res, nil
181+
}
144182

145-
return nil
183+
// scope holds the different objects that are read and used during the reconcile.
184+
type scope struct {
185+
// clusterClass is the ClusterClass object being reconciled.
186+
// It is set at the beginning of the reconcile function.
187+
clusterClass *clusterv1.ClusterClass
188+
189+
reconcileExternalReferencesError error
190+
outdatedExternalReferences []outdatedRef
191+
192+
variableDiscoveryError error
193+
}
194+
195+
type outdatedRef struct {
196+
Outdated *corev1.ObjectReference
197+
UpToDate *corev1.ObjectReference
146198
}
147199

148-
func (r *Reconciler) reconcileExternalReferences(ctx context.Context, clusterClass *clusterv1.ClusterClass) (map[*corev1.ObjectReference]*corev1.ObjectReference, error) {
200+
func (r *Reconciler) reconcileExternalReferences(ctx context.Context, s *scope) (ctrl.Result, error) {
201+
clusterClass := s.clusterClass
202+
149203
// Collect all the reference from the ClusterClass to templates.
150204
refs := []*corev1.ObjectReference{}
151205

@@ -185,7 +239,7 @@ func (r *Reconciler) reconcileExternalReferences(ctx context.Context, clusterCla
185239
// or MachinePool classes.
186240
errs := []error{}
187241
reconciledRefs := sets.Set[string]{}
188-
outdatedRefs := map[*corev1.ObjectReference]*corev1.ObjectReference{}
242+
outdatedRefs := []outdatedRef{}
189243
for i := range refs {
190244
ref := refs[i]
191245
uniqueKey := uniqueObjectRefKey(ref)
@@ -212,16 +266,25 @@ func (r *Reconciler) reconcileExternalReferences(ctx context.Context, clusterCla
212266
errs = append(errs, err)
213267
}
214268
if ref.GroupVersionKind().Version != updatedRef.GroupVersionKind().Version {
215-
outdatedRefs[ref] = updatedRef
269+
outdatedRefs = append(outdatedRefs, outdatedRef{
270+
Outdated: ref,
271+
UpToDate: updatedRef,
272+
})
216273
}
217274
}
218275
if len(errs) > 0 {
219-
return nil, kerrors.NewAggregate(errs)
276+
err := kerrors.NewAggregate(errs)
277+
s.reconcileExternalReferencesError = err
278+
return ctrl.Result{}, err
220279
}
221-
return outdatedRefs, nil
280+
281+
s.outdatedExternalReferences = outdatedRefs
282+
return ctrl.Result{}, nil
222283
}
223284

224-
func (r *Reconciler) reconcileVariables(ctx context.Context, clusterClass *clusterv1.ClusterClass) error {
285+
func (r *Reconciler) reconcileVariables(ctx context.Context, s *scope) (ctrl.Result, error) {
286+
clusterClass := s.clusterClass
287+
225288
errs := []error{}
226289
allVariableDefinitions := map[string]*clusterv1.ClusterClassStatusVariable{}
227290
// Add inline variable definitions to the ClusterClass status.
@@ -270,10 +333,10 @@ func (r *Reconciler) reconcileVariables(ctx context.Context, clusterClass *clust
270333
}
271334
}
272335
if len(errs) > 0 {
336+
err := kerrors.NewAggregate(errs)
337+
s.variableDiscoveryError = errors.Wrapf(err, "VariableDiscovery failed")
273338
// TODO: Decide whether to remove old variables if discovery fails.
274-
conditions.MarkFalse(clusterClass, clusterv1.ClusterClassVariablesReconciledCondition, clusterv1.VariableDiscoveryFailedReason, clusterv1.ConditionSeverityError,
275-
"VariableDiscovery failed: %s", kerrors.NewAggregate(errs))
276-
return errors.Wrapf(kerrors.NewAggregate(errs), "failed to discover variables for ClusterClass %s", clusterClass.Name)
339+
return ctrl.Result{}, errors.Wrapf(err, "failed to discover variables for ClusterClass %s", clusterClass.Name)
277340
}
278341

279342
statusVarList := []clusterv1.ClusterClassStatusVariable{}
@@ -297,37 +360,11 @@ func (r *Reconciler) reconcileVariables(ctx context.Context, clusterClass *clust
297360

298361
if len(variablesWithConflict) > 0 {
299362
err := fmt.Errorf("the following variables have conflicting schemas: %s", strings.Join(variablesWithConflict, ","))
300-
conditions.MarkFalse(clusterClass, clusterv1.ClusterClassVariablesReconciledCondition, clusterv1.VariableDiscoveryFailedReason, clusterv1.ConditionSeverityError,
301-
"VariableDiscovery failed: %s", err)
302-
return errors.Wrapf(err, "failed to discover variables for ClusterClass %s", clusterClass.Name)
363+
s.variableDiscoveryError = errors.Wrapf(err, "VariableDiscovery failed")
364+
return ctrl.Result{}, errors.Wrapf(err, "failed to discover variables for ClusterClass %s", clusterClass.Name)
303365
}
304366

305-
conditions.MarkTrue(clusterClass, clusterv1.ClusterClassVariablesReconciledCondition)
306-
return nil
307-
}
308-
309-
func reconcileConditions(clusterClass *clusterv1.ClusterClass, outdatedRefs map[*corev1.ObjectReference]*corev1.ObjectReference) {
310-
if len(outdatedRefs) > 0 {
311-
var msg []string
312-
for currentRef, updatedRef := range outdatedRefs {
313-
msg = append(msg, fmt.Sprintf("Ref %q should be %q", refString(currentRef), refString(updatedRef)))
314-
}
315-
conditions.Set(
316-
clusterClass,
317-
conditions.FalseCondition(
318-
clusterv1.ClusterClassRefVersionsUpToDateCondition,
319-
clusterv1.ClusterClassOutdatedRefVersionsReason,
320-
clusterv1.ConditionSeverityWarning,
321-
strings.Join(msg, ", "),
322-
),
323-
)
324-
return
325-
}
326-
327-
conditions.Set(
328-
clusterClass,
329-
conditions.TrueCondition(clusterv1.ClusterClassRefVersionsUpToDateCondition),
330-
)
367+
return ctrl.Result{}, nil
331368
}
332369

333370
func addNewStatusVariable(variable clusterv1.ClusterClassVariable, from string) *clusterv1.ClusterClassStatusVariable {
@@ -373,7 +410,7 @@ func (r *Reconciler) reconcileExternal(ctx context.Context, clusterClass *cluste
373410
obj, err := external.Get(ctx, r.Client, ref, clusterClass.Namespace)
374411
if err != nil {
375412
if apierrors.IsNotFound(errors.Cause(err)) {
376-
return errors.Wrapf(err, "Could not find external object for the ClusterClass. refGroupVersionKind: %s, refName: %s", ref.GroupVersionKind(), ref.Name)
413+
return errors.Wrapf(err, "could not find external object for the ClusterClass. refGroupVersionKind: %s, refName: %s", ref.GroupVersionKind(), ref.Name)
377414
}
378415
return errors.Wrapf(err, "failed to get the external object for the ClusterClass. refGroupVersionKind: %s, refName: %s", ref.GroupVersionKind(), ref.Name)
379416
}
@@ -384,7 +421,7 @@ func (r *Reconciler) reconcileExternal(ctx context.Context, clusterClass *cluste
384421
return err
385422
}
386423

387-
// Set external object ControllerReference to the ClusterClass.
424+
// Set external object owner reference to the ClusterClass.
388425
if err := controllerutil.SetOwnerReference(clusterClass, obj, r.Client.Scheme()); err != nil {
389426
return errors.Wrapf(err, "failed to set ClusterClass owner reference for %s %s", obj.GetKind(), klog.KObj(obj))
390427
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
Copyright 2024 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 clusterclass
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"strings"
23+
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
26+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
27+
"sigs.k8s.io/cluster-api/util/conditions"
28+
v1beta2conditions "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
29+
)
30+
31+
func updateStatus(ctx context.Context, s *scope) {
32+
setRefVersionsUpToDateCondition(ctx, s.clusterClass, s.outdatedExternalReferences, s.reconcileExternalReferencesError)
33+
setVariablesReconciledCondition(ctx, s.clusterClass, s.variableDiscoveryError)
34+
}
35+
36+
func setRefVersionsUpToDateCondition(_ context.Context, clusterClass *clusterv1.ClusterClass, outdatedRefs []outdatedRef, reconcileExternalReferencesError error) {
37+
if reconcileExternalReferencesError != nil {
38+
conditions.MarkUnknown(clusterClass,
39+
clusterv1.ClusterClassRefVersionsUpToDateCondition,
40+
clusterv1.ClusterClassRefVersionsUpToDateInternalErrorReason,
41+
"Please check controller logs for errors",
42+
)
43+
v1beta2conditions.Set(clusterClass, metav1.Condition{
44+
Type: clusterv1.ClusterClassRefVersionsUpToDateV1Beta2Condition,
45+
Status: metav1.ConditionUnknown,
46+
Reason: clusterv1.ClusterClassRefVersionsUpToDateInternalErrorV1Beta2Reason,
47+
Message: "Please check controller logs for errors",
48+
})
49+
return
50+
}
51+
52+
if len(outdatedRefs) > 0 {
53+
var msg []string
54+
for _, outdatedRef := range outdatedRefs {
55+
msg = append(msg, fmt.Sprintf("* Ref %q should be %q", refString(outdatedRef.Outdated), refString(outdatedRef.UpToDate)))
56+
}
57+
conditions.Set(clusterClass,
58+
conditions.FalseCondition(
59+
clusterv1.ClusterClassRefVersionsUpToDateCondition,
60+
clusterv1.ClusterClassOutdatedRefVersionsReason,
61+
clusterv1.ConditionSeverityWarning,
62+
strings.Join(msg, "\n"),
63+
),
64+
)
65+
v1beta2conditions.Set(clusterClass, metav1.Condition{
66+
Type: clusterv1.ClusterClassRefVersionsUpToDateV1Beta2Condition,
67+
Status: metav1.ConditionFalse,
68+
Reason: clusterv1.ClusterClassRefVersionsNotUpToDateV1Beta2Reason,
69+
Message: strings.Join(msg, "\n"),
70+
})
71+
return
72+
}
73+
74+
conditions.Set(clusterClass,
75+
conditions.TrueCondition(clusterv1.ClusterClassRefVersionsUpToDateCondition),
76+
)
77+
v1beta2conditions.Set(clusterClass, metav1.Condition{
78+
Type: clusterv1.ClusterClassRefVersionsUpToDateV1Beta2Condition,
79+
Status: metav1.ConditionTrue,
80+
Reason: clusterv1.ClusterClassRefVersionsUpToDateV1Beta2Reason,
81+
})
82+
}
83+
84+
func setVariablesReconciledCondition(_ context.Context, clusterClass *clusterv1.ClusterClass, variableDiscoveryError error) {
85+
if variableDiscoveryError != nil {
86+
conditions.MarkFalse(clusterClass,
87+
clusterv1.ClusterClassVariablesReconciledCondition,
88+
clusterv1.VariableDiscoveryFailedReason,
89+
clusterv1.ConditionSeverityError,
90+
variableDiscoveryError.Error(),
91+
)
92+
v1beta2conditions.Set(clusterClass, metav1.Condition{
93+
Type: clusterv1.ClusterClassVariablesReadyV1Beta2Condition,
94+
Status: metav1.ConditionFalse,
95+
Reason: clusterv1.ClusterClassVariablesReadyVariableDiscoveryFailedV1Beta2Reason,
96+
Message: variableDiscoveryError.Error(),
97+
})
98+
return
99+
}
100+
101+
conditions.MarkTrue(clusterClass, clusterv1.ClusterClassVariablesReconciledCondition)
102+
v1beta2conditions.Set(clusterClass, metav1.Condition{
103+
Type: clusterv1.ClusterClassVariablesReadyV1Beta2Condition,
104+
Status: metav1.ConditionTrue,
105+
Reason: clusterv1.ClusterClassVariablesReadyV1Beta2Reason,
106+
})
107+
}

0 commit comments

Comments
 (0)