@@ -62,6 +62,8 @@ type Cluster struct {
62
62
var _ webhook.CustomDefaulter = & Cluster {}
63
63
var _ webhook.CustomValidator = & Cluster {}
64
64
65
+ var errClusterClassNotReconciled = errors .New ("ClusterClass is not up to date" )
66
+
65
67
// Default satisfies the defaulting webhook interface.
66
68
func (webhook * Cluster ) Default (ctx context.Context , obj runtime.Object ) error {
67
69
// We gather all defaulting errors and return them together.
@@ -88,11 +90,11 @@ func (webhook *Cluster) Default(ctx context.Context, obj runtime.Object) error {
88
90
}
89
91
clusterClass , err := webhook .pollClusterClassForCluster (ctx , cluster )
90
92
if err != nil {
91
- // If the ClusterClass can't be found ignore the error.
92
- if apierrors .IsNotFound (err ) {
93
+ // If the ClusterClass can't be found or is not up to date ignore the error.
94
+ if apierrors .IsNotFound (err ) || errors . Is ( err , errClusterClassNotReconciled ) {
93
95
return nil
94
96
}
95
- return apierrors .NewInternalError (errors .Wrapf (err , "Cluster %s can't be defaulted. Valid ClusterClass %s can not be retrieved" , cluster .Name , cluster .Spec .Topology .Class ))
97
+ return apierrors .NewInternalError (errors .Wrapf (err , "Cluster %s can't be defaulted. ClusterClass %s can not be retrieved" , cluster .Name , cluster .Spec .Topology .Class ))
96
98
}
97
99
98
100
allErrs = append (allErrs , DefaultVariables (cluster , clusterClass )... )
@@ -242,27 +244,28 @@ func (webhook *Cluster) validateTopology(ctx context.Context, oldCluster, newClu
242
244
}
243
245
244
246
// Get the ClusterClass referenced in the Cluster.
245
- clusterClass , clusterClassGetErr := webhook .pollClusterClassForCluster (ctx , newCluster )
246
- if clusterClassGetErr != nil && ! apierrors .IsNotFound (clusterClassGetErr ) {
247
- // If the error is anything other than "Not Found" return all errors at this point.
247
+ clusterClass , clusterClassPollErr := webhook .pollClusterClassForCluster (ctx , newCluster )
248
+ if clusterClassPollErr != nil &&
249
+ // If the error is anything other than "NotFound" or "NotReconciled" return all errors at this point.
250
+ ! (apierrors .IsNotFound (clusterClassPollErr ) || errors .Is (clusterClassPollErr , errClusterClassNotReconciled )) {
248
251
allErrs = append (
249
252
allErrs , field .InternalError (
250
253
fldPath .Child ("class" ),
251
- clusterClassGetErr ))
254
+ clusterClassPollErr ))
252
255
return allErrs
253
256
}
254
- if clusterClassGetErr == nil {
257
+ if clusterClassPollErr == nil {
255
258
// If there's no error validate the Cluster based on the ClusterClass.
256
259
allErrs = append (allErrs , ValidateClusterForClusterClass (newCluster , clusterClass , fldPath )... )
257
260
}
258
261
if oldCluster != nil { // On update
259
262
// The ClusterClass must exist to proceed with update validation. Return an error if the ClusterClass was
260
263
// not found.
261
- if clusterClassGetErr != nil {
264
+ if apierrors . IsNotFound ( clusterClassPollErr ) {
262
265
allErrs = append (
263
266
allErrs , field .InternalError (
264
267
fldPath .Child ("class" ),
265
- clusterClassGetErr ))
268
+ clusterClassPollErr ))
266
269
return allErrs
267
270
}
268
271
@@ -341,7 +344,7 @@ func (webhook *Cluster) validateTopology(ctx context.Context, oldCluster, newClu
341
344
allErrs = append (
342
345
allErrs , field .Forbidden (
343
346
fldPath .Child ("class" ),
344
- fmt .Sprintf ("ClusterClass with name %q could not be found, change from class %[1]q to class %q cannot be validated" ,
347
+ fmt .Sprintf ("valid ClusterClass with name %q could not be found, change from class %[1]q to class %q cannot be validated" ,
345
348
oldCluster .Spec .Topology .Class , newCluster .Spec .Topology .Class )))
346
349
347
350
// Return early with errors if the ClusterClass can't be retrieved.
@@ -529,24 +532,35 @@ func ValidateClusterForClusterClass(cluster *clusterv1.Cluster, clusterClass *cl
529
532
// pollClusterClassForCluster will retry getting the ClusterClass referenced in the Cluster for two seconds.
530
533
func (webhook * Cluster ) pollClusterClassForCluster (ctx context.Context , cluster * clusterv1.Cluster ) (* clusterv1.ClusterClass , error ) {
531
534
clusterClass := & clusterv1.ClusterClass {}
532
- var clusterClassGetErr error
535
+ var clusterClassPollErr error
536
+ // TODO: Add a webhook warning if the ClusterClass is not up to date or not found.
533
537
_ = util .PollImmediate (200 * time .Millisecond , 2 * time .Second , func () (bool , error ) {
534
- if clusterClassGetErr = webhook .Client .Get (ctx , client.ObjectKey {Namespace : cluster .Namespace , Name : cluster .Spec .Topology .Class }, clusterClass ); clusterClassGetErr != nil {
538
+ if clusterClassPollErr = webhook .Client .Get (ctx , client.ObjectKey {Namespace : cluster .Namespace , Name : cluster .Spec .Topology .Class }, clusterClass ); clusterClassPollErr != nil {
535
539
return false , nil //nolint:nilerr
536
540
}
537
541
538
- // Return an error if the ClusterClass has not successfully reconciled because variables aren't correctly
539
- // reconciled.
540
- // TODO: Decide whether to check generation here. This requires creating templates before creating the Cluster and
541
- // may interfere with the way clusterctl move works.
542
- if ! conditions .Has (clusterClass , clusterv1 .ClusterClassVariablesReconciledCondition ) ||
543
- conditions .IsFalse (clusterClass , clusterv1 .ClusterClassVariablesReconciledCondition ) {
544
- clusterClassGetErr = errors .New ("ClusterClass is not up to date. If this persists check ClusterClass status" )
545
- return false , nil
542
+ if clusterClassPollErr = clusterClassIsReconciled (clusterClass ); clusterClassPollErr != nil {
543
+ return false , nil //nolint:nilerr
546
544
}
545
+ clusterClassPollErr = nil
547
546
return true , nil
548
547
})
549
- return clusterClass , clusterClassGetErr
548
+ return clusterClass , clusterClassPollErr
549
+ }
550
+
551
+ // clusterClassIsReconciled returns errClusterClassNotReconciled if the ClusterClass has not successfully reconciled or if the
552
+ // ClusterClass variables have not been successfully reconciled.
553
+ func clusterClassIsReconciled (clusterClass * clusterv1.ClusterClass ) error {
554
+ // If the clusterClass metadata generation does not match the status observed generation, the ClusterClass has not been successfully reconciled.
555
+ if clusterClass .Generation != clusterClass .Status .ObservedGeneration {
556
+ return errClusterClassNotReconciled
557
+ }
558
+ // If the clusterClass does not have ClusterClassVariablesReconciled==True, the ClusterClass has not been successfully reconciled.
559
+ if ! conditions .Has (clusterClass , clusterv1 .ClusterClassVariablesReconciledCondition ) ||
560
+ conditions .IsFalse (clusterClass , clusterv1 .ClusterClassVariablesReconciledCondition ) {
561
+ return errClusterClassNotReconciled
562
+ }
563
+ return nil
550
564
}
551
565
552
566
// TODO: This function will not be needed once per-definition defaulting and validation is implemented.
0 commit comments