@@ -79,8 +79,6 @@ type Cluster struct {
7979var _ webhook.CustomDefaulter = & Cluster {}
8080var _ webhook.CustomValidator = & Cluster {}
8181
82- var errClusterClassNotReconciled = errors .New ("ClusterClass is not successfully reconciled" )
83-
8482// Default satisfies the defaulting webhook interface.
8583func (webhook * Cluster ) Default (ctx context.Context , obj runtime.Object ) error {
8684 // We gather all defaulting errors and return them together.
@@ -109,14 +107,15 @@ func (webhook *Cluster) Default(ctx context.Context, obj runtime.Object) error {
109107 return apierrors .NewInvalid (clusterv1 .GroupVersion .WithKind ("Cluster" ).GroupKind (), cluster .Name , allErrs )
110108 }
111109
112- clusterClass , err := webhook .pollClusterClassForCluster (ctx , cluster )
110+ clusterClass , clusterClassNotReconciled , clusterClassNotFound , err := webhook .pollClusterClassForCluster (ctx , cluster )
113111 if err != nil {
114- // If the ClusterClass can't be found or is not up to date ignore the error.
115- if apierrors .IsNotFound (err ) || errors .Is (err , errClusterClassNotReconciled ) {
116- return nil
117- }
118112 return apierrors .NewInternalError (errors .Wrapf (err , "Cluster %s can't be defaulted. ClusterClass %s can not be retrieved" , cluster .Name , cluster .GetClassKey ().Name ))
119113 }
114+ if clusterClassNotReconciled || clusterClassNotFound {
115+ // If the ClusterClass can't be found or is not reconciled, return as we shouldn't
116+ // default and validate variables in that case.
117+ return nil
118+ }
120119
121120 // Validate cluster class variables transitions that may be enforced by CEL validation rules on variables.
122121 // If no request found in context, then this has not come via a webhook request, so skip validation of old cluster.
@@ -312,39 +311,34 @@ func (webhook *Cluster) validateTopology(ctx context.Context, oldCluster, newClu
312311 }
313312
314313 // Get the ClusterClass referenced in the Cluster.
315- clusterClass , warnings , clusterClassPollErr := webhook .validateClusterClassExistsAndIsReconciled (ctx , newCluster )
316- // If the error is anything other than "NotFound" or "NotReconciled" return all errors.
317- if clusterClassPollErr != nil && (! apierrors .IsNotFound (clusterClassPollErr ) && ! errors .Is (clusterClassPollErr , errClusterClassNotReconciled )) {
318- allErrs = append (
319- allErrs , field .InternalError (
320- fldPath .Child ("class" ),
321- clusterClassPollErr ))
322- return allWarnings , allErrs
314+ // Note: If the ClusterClass is not found, a warning and no err is returned and the clusterClass is nil.
315+ // Note: If the ClusterClass is not reconciled, a warning and no err is returned and the clusterClass is returned.
316+ clusterClass , warnings , err := webhook .validateClusterClassExistsAndIsReconciled (ctx , newCluster )
317+ if err != nil {
318+ return allWarnings , append (allErrs , field .InternalError (fldPath .Child ("class" ), err ))
323319 }
324-
325- // Add the warnings if no error was returned.
326320 allWarnings = append (allWarnings , warnings ... )
327321
328- // If there's no error validate the Cluster based on the ClusterClass.
329- if clusterClassPollErr = = nil {
322+ // If we could get the ClusterClass validate the Cluster based on the ClusterClass.
323+ if clusterClass ! = nil {
330324 allErrs = append (allErrs , ValidateClusterForClusterClass (newCluster , clusterClass )... )
331325 }
332326
333- // Validate the Cluster and associated ClusterClass' autoscaler annotations.
327+ // Validate the Cluster and associated ClusterClass' autoscaler annotations
328+ // Note: ClusterClass validation is only run if clusterClass is not nil.
334329 allErrs = append (allErrs , validateAutoscalerAnnotationsForCluster (newCluster , clusterClass )... )
335330
336331 if oldCluster != nil { // On update
337- // The ClusterClass must exist to proceed with update validation. Return an error if the ClusterClass was
338- // not found.
339- if apierrors .IsNotFound (clusterClassPollErr ) {
332+ // The ClusterClass must exist to proceed with update validation. Return an error if the ClusterClass was not found.
333+ if clusterClass == nil {
340334 allErrs = append (
341335 allErrs , field .InternalError (
342336 fldPath .Child ("class" ),
343- clusterClassPollErr ))
337+ errors . Errorf ( "ClusterClass %s not found" , newCluster . GetClassKey ()) ))
344338 return allWarnings , allErrs
345339 }
346340
347- // Topology or Class can not be added on update unless ClusterTopologyUnsafeUpdateClassNameAnnotation is set.
341+ // Topology or Class can not be added or update unless ClusterTopologyUnsafeUpdateClassNameAnnotation is set.
348342 if ! oldCluster .Spec .Topology .IsDefined () || oldCluster .GetClassKey ().Name == "" {
349343 if _ , ok := newCluster .Annotations [clusterv1 .ClusterTopologyUnsafeUpdateClassNameAnnotation ]; ok {
350344 return allWarnings , allErrs
@@ -402,29 +396,21 @@ func (webhook *Cluster) validateTopology(ctx context.Context, oldCluster, newClu
402396
403397 // If the ClusterClass referenced in the Topology has changed compatibility checks are needed.
404398 if oldCluster .GetClassKey () != newCluster .GetClassKey () {
405- if clusterClassPollErr != nil {
406- allErrs = append (
407- allErrs , field .Forbidden (
408- fldPath .Child ("class" ),
409- fmt .Sprintf ("cannot rebase to ClusterClass %q: %s" ,
410- newCluster .GetClassKey (), clusterClassPollErr .Error ())))
411- // Return early with errors if the new ClusterClass can't be retrieved.
412- return allWarnings , allErrs
413- }
414-
415399 // Check to see if the ClusterClass referenced in the old version of the Cluster exists.
416- oldClusterClass , err := webhook .pollClusterClassForCluster (ctx , oldCluster )
417- if err != nil {
400+ // Return early with errors if the old ClusterClass can't be retrieved.
401+ oldClusterClass := & clusterv1.ClusterClass {}
402+ if err := webhook .Client .Get (ctx , oldCluster .GetClassKey (), oldClusterClass ); err != nil {
418403 allErrs = append (
419404 allErrs , field .Forbidden (
420405 fldPath .Child ("class" ),
421406 fmt .Sprintf ("valid ClusterClass with name %q could not be retrieved, change from class %[1]q to class %q cannot be validated. Error: %s" ,
422407 oldCluster .GetClassKey (), newCluster .GetClassKey (), err .Error ())))
423-
424- // Return early with errors if the old ClusterClass can't be retrieved.
425408 return allWarnings , allErrs
426409 }
427410
411+ // Note: We don't care if the old ClusterClass is reconciled as the validation below doesn't need it
412+ // and we want to allow to rebase away from a broken ClusterClass.
413+
428414 // Check if the new and old ClusterClasses are compatible with one another.
429415 allErrs = append (allErrs , check .ClusterClassesAreCompatible (oldClusterClass , clusterClass )... )
430416 }
@@ -926,76 +912,80 @@ func ValidateClusterForClusterClass(cluster *clusterv1.Cluster, clusterClass *cl
926912
927913// validateClusterClassExistsAndIsReconciled will try to get the ClusterClass referenced in the Cluster. If it does not exist or is not reconciled it will add a warning.
928914// In any other case it will return an error.
929- func (webhook * Cluster ) validateClusterClassExistsAndIsReconciled (ctx context.Context , newCluster * clusterv1.Cluster ) (* clusterv1.ClusterClass , admission.Warnings , error ) {
915+ func (webhook * Cluster ) validateClusterClassExistsAndIsReconciled (ctx context.Context , newCluster * clusterv1.Cluster ) (_ * clusterv1.ClusterClass , _ admission.Warnings , _ error ) {
930916 var allWarnings admission.Warnings
931- clusterClass , clusterClassPollErr := webhook .pollClusterClassForCluster (ctx , newCluster )
932- if clusterClassPollErr != nil {
933- // Add a warning if the Class does not exist or if it has not been successfully reconciled.
934- switch {
935- case apierrors .IsNotFound (clusterClassPollErr ):
936- allWarnings = append (allWarnings ,
937- fmt .Sprintf (
938- "Cluster refers to ClusterClass %s, but this ClusterClass does not exist. " +
939- "Cluster topology has not been fully validated. " +
940- "The ClusterClass must be created to reconcile the Cluster" , newCluster .GetClassKey ()),
941- )
942- case errors .Is (clusterClassPollErr , errClusterClassNotReconciled ):
943- allWarnings = append (allWarnings ,
944- fmt .Sprintf (
945- "Cluster refers to ClusterClass %s, but this ClusterClass hasn't been successfully reconciled. " +
946- "Cluster topology has not been fully validated. " +
947- "Please take a look at the ClusterClass status" , newCluster .GetClassKey ()),
948- )
949- // If there's any other error return a generic warning with the error message.
950- default :
951- allWarnings = append (allWarnings ,
952- fmt .Sprintf (
953- "Cluster refers to ClusterClass %s, but this ClusterClass could not be retrieved. " +
954- "Cluster topology has not been fully validated: %s" , newCluster .GetClassKey (), clusterClassPollErr .Error ()),
955- )
956- }
917+ clusterClass , clusterClassNotReconciled , clusterClassNotFound , err := webhook .pollClusterClassForCluster (ctx , newCluster )
918+ // Add a warning if the Class does not exist or if it has not been successfully reconciled.
919+ switch {
920+ case err != nil :
921+ allWarnings = append (allWarnings ,
922+ fmt .Sprintf (
923+ "Cluster refers to ClusterClass %s, but this ClusterClass could not be retrieved. " +
924+ "Cluster topology has not been fully validated: %s" , newCluster .GetClassKey (), err .Error ()),
925+ )
926+ case clusterClassNotFound :
927+ allWarnings = append (allWarnings ,
928+ fmt .Sprintf (
929+ "Cluster refers to ClusterClass %s, but this ClusterClass does not exist. " +
930+ "Cluster topology has not been fully validated. " +
931+ "The ClusterClass must be created to reconcile the Cluster" , newCluster .GetClassKey ()),
932+ )
933+ case clusterClassNotReconciled :
934+ allWarnings = append (allWarnings ,
935+ fmt .Sprintf (
936+ "Cluster refers to ClusterClass %s, but this ClusterClass hasn't been successfully reconciled. " +
937+ "Cluster topology has not been fully validated. " +
938+ "Please take a look at the ClusterClass status" , newCluster .GetClassKey ()),
939+ )
957940 }
958- return clusterClass , allWarnings , clusterClassPollErr
941+ return clusterClass , allWarnings , err
959942}
960943
961944// pollClusterClassForCluster will retry getting the ClusterClass referenced in the Cluster for two seconds.
962- func (webhook * Cluster ) pollClusterClassForCluster (ctx context.Context , cluster * clusterv1.Cluster ) (* clusterv1.ClusterClass , error ) {
945+ func (webhook * Cluster ) pollClusterClassForCluster (ctx context.Context , cluster * clusterv1.Cluster ) (_ * clusterv1.ClusterClass , clusterClassNotReconciled , clusterClassNotFound bool , _ error ) {
946+ var errClusterClassNotReconciled = errors .New ("ClusterClass is not successfully reconciled" )
947+
963948 clusterClass := & clusterv1.ClusterClass {}
964949 var clusterClassPollErr error
965950 _ = wait .PollUntilContextTimeout (ctx , 200 * time .Millisecond , 2 * time .Second , true , func (ctx context.Context ) (bool , error ) {
966951 if clusterClassPollErr = webhook .Client .Get (ctx , cluster .GetClassKey (), clusterClass ); clusterClassPollErr != nil {
967952 return false , nil //nolint:nilerr
968953 }
969954
970- if clusterClassPollErr = clusterClassIsReconciled (clusterClass ); clusterClassPollErr != nil {
971- return false , nil //nolint:nilerr
955+ if ! clusterClassIsReconciled (clusterClass ) {
956+ clusterClassPollErr = errClusterClassNotReconciled
957+ return false , nil
972958 }
959+
973960 clusterClassPollErr = nil
974961 return true , nil
975962 })
976963 if clusterClassPollErr != nil {
964+ if apierrors .IsNotFound (clusterClassPollErr ) {
965+ return nil , false , true , nil
966+ }
977967 if errors .Is (clusterClassPollErr , errClusterClassNotReconciled ) {
978968 // Return ClusterClass if we were able to get it and it's just not reconciled.
979- return clusterClass , clusterClassPollErr
969+ return clusterClass , true , false , nil
980970 }
981- return nil , clusterClassPollErr
971+ return nil , false , false , clusterClassPollErr
982972 }
983- return clusterClass , nil
973+ return clusterClass , false , false , nil
984974}
985975
986976// clusterClassIsReconciled returns errClusterClassNotReconciled if the ClusterClass has not successfully reconciled or if the
987977// ClusterClass variables have not been successfully reconciled.
988- func clusterClassIsReconciled (clusterClass * clusterv1.ClusterClass ) error {
978+ func clusterClassIsReconciled (clusterClass * clusterv1.ClusterClass ) bool {
989979 // If the clusterClass metadata generation does not match the status observed generation, the ClusterClass has not been successfully reconciled.
990980 if clusterClass .Generation != clusterClass .Status .ObservedGeneration {
991- return errClusterClassNotReconciled
981+ return false
992982 }
993983 // If the clusterClass does not have ClusterClassVariablesReconciled==True, the ClusterClass has not been successfully reconciled.
994984 if ! conditions .Has (clusterClass , clusterv1 .ClusterClassVariablesReadyCondition ) ||
995985 conditions .IsFalse (clusterClass , clusterv1 .ClusterClassVariablesReadyCondition ) {
996- return errClusterClassNotReconciled
986+ return false
997987 }
998- return nil
988+ return true
999989}
1000990
1001991func validateTopologyMetadata (topology clusterv1.Topology , fldPath * field.Path ) field.ErrorList {
0 commit comments