@@ -139,6 +139,7 @@ func (webhook *Cluster) ValidateDelete(_ context.Context, _ runtime.Object) (adm
139139
140140func (webhook * Cluster ) validate (ctx context.Context , oldCluster , newCluster * clusterv1.Cluster ) (admission.Warnings , error ) {
141141 var allErrs field.ErrorList
142+ var allWarnings admission.Warnings
142143 // The Cluster name is used as a label value. This check ensures that names which are not valid label values are rejected.
143144 if errs := validation .IsValidLabelValue (newCluster .Name ); len (errs ) != 0 {
144145 for _ , err := range errs {
@@ -191,7 +192,9 @@ func (webhook *Cluster) validate(ctx context.Context, oldCluster, newCluster *cl
191192
192193 // Validate the managed topology, if defined.
193194 if newCluster .Spec .Topology != nil {
194- allErrs = append (allErrs , webhook .validateTopology (ctx , oldCluster , newCluster , topologyPath )... )
195+ topologyWarnings , topologyErrs := webhook .validateTopology (ctx , oldCluster , newCluster , topologyPath )
196+ allWarnings = append (allWarnings , topologyWarnings ... )
197+ allErrs = append (allErrs , topologyErrs ... )
195198 }
196199
197200 // On update.
@@ -206,16 +209,18 @@ func (webhook *Cluster) validate(ctx context.Context, oldCluster, newCluster *cl
206209 }
207210
208211 if len (allErrs ) > 0 {
209- return nil , apierrors .NewInvalid (clusterv1 .GroupVersion .WithKind ("Cluster" ).GroupKind (), newCluster .Name , allErrs )
212+ return allWarnings , apierrors .NewInvalid (clusterv1 .GroupVersion .WithKind ("Cluster" ).GroupKind (), newCluster .Name , allErrs )
210213 }
211- return nil , nil
214+ return allWarnings , nil
212215}
213216
214- func (webhook * Cluster ) validateTopology (ctx context.Context , oldCluster , newCluster * clusterv1.Cluster , fldPath * field.Path ) field.ErrorList {
217+ func (webhook * Cluster ) validateTopology (ctx context.Context , oldCluster , newCluster * clusterv1.Cluster , fldPath * field.Path ) (admission.Warnings , field.ErrorList ) {
218+ var allWarnings admission.Warnings
219+
215220 // NOTE: ClusterClass and managed topologies are behind ClusterTopology feature gate flag; the web hook
216221 // must prevent the usage of Cluster.Topology in case the feature flag is disabled.
217222 if ! feature .Gates .Enabled (feature .ClusterTopology ) {
218- return field.ErrorList {
223+ return allWarnings , field.ErrorList {
219224 field .Forbidden (
220225 fldPath ,
221226 "can be set only if the ClusterTopology feature flag is enabled" ,
@@ -234,6 +239,8 @@ func (webhook *Cluster) validateTopology(ctx context.Context, oldCluster, newClu
234239 "class cannot be empty" ,
235240 ),
236241 )
242+ // Return early if there is no defined class to validate.
243+ return allWarnings , allErrs
237244 }
238245
239246 // version should be valid.
@@ -268,18 +275,11 @@ func (webhook *Cluster) validateTopology(ctx context.Context, oldCluster, newClu
268275 }
269276
270277 // Get the ClusterClass referenced in the Cluster.
271- clusterClass , clusterClassPollErr := webhook .pollClusterClassForCluster (ctx , newCluster )
272- if clusterClassPollErr != nil &&
273- // If the error is anything other than "NotFound" or "NotReconciled" return all errors at this point.
274- ! (apierrors .IsNotFound (clusterClassPollErr ) || errors .Is (clusterClassPollErr , errClusterClassNotReconciled )) {
275- allErrs = append (
276- allErrs , field .InternalError (
277- fldPath .Child ("class" ),
278- clusterClassPollErr ))
279- return allErrs
280- }
278+ clusterClass , warnings , clusterClassPollErr := webhook .validateClusterClassExistsAndIsReconciled (ctx , newCluster )
279+ allWarnings = append (allWarnings , warnings ... )
280+
281+ // If there's no error validate the Cluster based on the ClusterClass.
281282 if clusterClassPollErr == nil {
282- // If there's no error validate the Cluster based on the ClusterClass.
283283 allErrs = append (allErrs , ValidateClusterForClusterClass (newCluster , clusterClass )... )
284284 }
285285 if oldCluster != nil { // On update
@@ -290,13 +290,13 @@ func (webhook *Cluster) validateTopology(ctx context.Context, oldCluster, newClu
290290 allErrs , field .InternalError (
291291 fldPath .Child ("class" ),
292292 clusterClassPollErr ))
293- return allErrs
293+ return allWarnings , allErrs
294294 }
295295
296296 // Topology or Class can not be added on update unless ClusterTopologyUnsafeUpdateClassNameAnnotation is set.
297297 if oldCluster .Spec .Topology == nil || oldCluster .Spec .Topology .Class == "" {
298298 if _ , ok := newCluster .Annotations [clusterv1 .ClusterTopologyUnsafeUpdateClassNameAnnotation ]; ok {
299- return allErrs
299+ return allWarnings , allErrs
300300 }
301301
302302 allErrs = append (
@@ -307,7 +307,7 @@ func (webhook *Cluster) validateTopology(ctx context.Context, oldCluster, newClu
307307 ),
308308 )
309309 // return early here if there is no class to compare.
310- return allErrs
310+ return allWarnings , allErrs
311311 }
312312
313313 // Version could only be increased.
@@ -372,14 +372,14 @@ func (webhook *Cluster) validateTopology(ctx context.Context, oldCluster, newClu
372372 oldCluster .Spec .Topology .Class , newCluster .Spec .Topology .Class )))
373373
374374 // Return early with errors if the ClusterClass can't be retrieved.
375- return allErrs
375+ return allWarnings , allErrs
376376 }
377377
378378 // Check if the new and old ClusterClasses are compatible with one another.
379379 allErrs = append (allErrs , check .ClusterClassesAreCompatible (oldClusterClass , clusterClass )... )
380380 }
381381 }
382- return allErrs
382+ return allWarnings , allErrs
383383}
384384
385385func validateMachineHealthChecks (cluster * clusterv1.Cluster , clusterClass * clusterv1.ClusterClass ) field.ErrorList {
@@ -551,11 +551,29 @@ func ValidateClusterForClusterClass(cluster *clusterv1.Cluster, clusterClass *cl
551551 return allErrs
552552}
553553
554+ // 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.
555+ // In any other case it will return an error.
556+ func (webhook * Cluster ) validateClusterClassExistsAndIsReconciled (ctx context.Context , newCluster * clusterv1.Cluster ) (* clusterv1.ClusterClass , admission.Warnings , error ) {
557+ var allWarnings admission.Warnings
558+ clusterClass , clusterClassPollErr := webhook .pollClusterClassForCluster (ctx , newCluster )
559+ if clusterClassPollErr != nil {
560+ // Add a warning if the Class does not exist or if it has not been successfully reconciled.
561+ switch {
562+ case apierrors .IsNotFound (clusterClassPollErr ):
563+ allWarnings = append (allWarnings ,
564+ fmt .Sprintf ("Cluster specifies ClusterClass %s in the topology but it does not exist. Cluster variables have not been fully validated. The ClusterClass must be created to reconcile the Cluster" , newCluster .Spec .Topology .Class ))
565+ case errors .Is (clusterClassPollErr , errClusterClassNotReconciled ):
566+ allWarnings = append (allWarnings ,
567+ fmt .Sprintf ("Cluster specifies ClusterClass %s in `topology.spec.class` but it has not successfully been reconciled. Cluster variables have not been fully validated." , newCluster .Spec .Topology .Class ))
568+ }
569+ }
570+ return clusterClass , allWarnings , clusterClassPollErr
571+ }
572+
554573// pollClusterClassForCluster will retry getting the ClusterClass referenced in the Cluster for two seconds.
555574func (webhook * Cluster ) pollClusterClassForCluster (ctx context.Context , cluster * clusterv1.Cluster ) (* clusterv1.ClusterClass , error ) {
556575 clusterClass := & clusterv1.ClusterClass {}
557576 var clusterClassPollErr error
558- // TODO: Add a webhook warning if the ClusterClass is not up to date or not found.
559577 _ = wait .PollUntilContextTimeout (ctx , 200 * time .Millisecond , 2 * time .Second , true , func (ctx context.Context ) (bool , error ) {
560578 if clusterClassPollErr = webhook .Client .Get (ctx , client.ObjectKey {Namespace : cluster .Namespace , Name : cluster .Spec .Topology .Class }, clusterClass ); clusterClassPollErr != nil {
561579 return false , nil //nolint:nilerr
0 commit comments