Skip to content

Commit 43e229f

Browse files
Move Cluster variable validation to defaulting webhook
Signed-off-by: killianmuldoon <[email protected]>
1 parent 0d3a728 commit 43e229f

File tree

3 files changed

+252
-226
lines changed

3 files changed

+252
-226
lines changed

internal/controllers/topology/cluster/cluster_controller.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
apierrors "k8s.io/apimachinery/pkg/api/errors"
2626
"k8s.io/apimachinery/pkg/types"
2727
kerrors "k8s.io/apimachinery/pkg/util/errors"
28-
"k8s.io/apimachinery/pkg/util/validation/field"
2928
"k8s.io/client-go/tools/record"
3029
ctrl "sigs.k8s.io/controller-runtime"
3130
"sigs.k8s.io/controller-runtime/pkg/builder"
@@ -221,15 +220,13 @@ func (r *Reconciler) reconcile(ctx context.Context, s *scope.Scope) (ctrl.Result
221220
return ctrl.Result{}, nil
222221
}
223222

224-
// Default and Validate the Cluster based on information from the ClusterClass.
223+
// Default and Validate the Cluster variables based on information from the ClusterClass.
225224
// This step is needed as if the ClusterClass does not exist at Cluster creation some fields may not be defaulted or
226225
// validated in the webhook.
227-
if errs := webhooks.DefaultVariables(s.Current.Cluster, clusterClass); len(errs) > 0 {
228-
return ctrl.Result{}, apierrors.NewInvalid(clusterv1.GroupVersion.WithKind("Cluster").GroupKind(), s.Current.Cluster.Name, errs)
229-
}
230-
if errs := webhooks.ValidateClusterForClusterClass(s.Current.Cluster, clusterClass, field.NewPath("spec", "topology")); len(errs) > 0 {
226+
if errs := webhooks.DefaultAndValidateVariables(s.Current.Cluster, clusterClass); len(errs) > 0 {
231227
return ctrl.Result{}, apierrors.NewInvalid(clusterv1.GroupVersion.WithKind("Cluster").GroupKind(), s.Current.Cluster.Name, errs)
232228
}
229+
233230
// Gets the blueprint with the ClusterClass and the referenced templates
234231
// and store it in the request scope.
235232
s.Blueprint, err = r.getBlueprint(ctx, s.Current.Cluster, s.Blueprint.ClusterClass)

internal/webhooks/cluster.go

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ func (webhook *Cluster) Default(ctx context.Context, obj runtime.Object) error {
9797
return apierrors.NewInternalError(errors.Wrapf(err, "Cluster %s can't be defaulted. ClusterClass %s can not be retrieved", cluster.Name, cluster.Spec.Topology.Class))
9898
}
9999

100-
allErrs = append(allErrs, DefaultVariables(cluster, clusterClass)...)
100+
// Doing both defaulting and validating here prevents a race condition where the ClusterClass could be
101+
// different in the defaulting and validating webhook.
102+
allErrs = append(allErrs, DefaultAndValidateVariables(cluster, clusterClass)...)
103+
101104
if len(allErrs) > 0 {
102105
return apierrors.NewInvalid(clusterv1.GroupVersion.WithKind("Cluster").GroupKind(), cluster.Name, allErrs)
103106
}
@@ -256,7 +259,7 @@ func (webhook *Cluster) validateTopology(ctx context.Context, oldCluster, newClu
256259
}
257260
if clusterClassPollErr == nil {
258261
// If there's no error validate the Cluster based on the ClusterClass.
259-
allErrs = append(allErrs, ValidateClusterForClusterClass(newCluster, clusterClass, fldPath)...)
262+
allErrs = append(allErrs, ValidateClusterForClusterClass(newCluster, clusterClass)...)
260263
}
261264
if oldCluster != nil { // On update
262265
// The ClusterClass must exist to proceed with update validation. Return an error if the ClusterClass was
@@ -453,6 +456,29 @@ func validateCIDRBlocks(fldPath *field.Path, cidrs []string) field.ErrorList {
453456
return allErrs
454457
}
455458

459+
// DefaultAndValidateVariables defaults and validates variables in the Cluster and MachineDeployment topologies based
460+
// on the definitions in the ClusterClass.
461+
func DefaultAndValidateVariables(cluster *clusterv1.Cluster, clusterClass *clusterv1.ClusterClass) field.ErrorList {
462+
var allErrs field.ErrorList
463+
allErrs = append(allErrs, DefaultVariables(cluster, clusterClass)...)
464+
465+
// Variables must be validated in the defaulting webhook. Variable definitions are stored in the ClusterClass status
466+
// and are patched in the ClusterClass reconcile.
467+
allErrs = append(allErrs, variables.ValidateClusterVariables(cluster.Spec.Topology.Variables, clusterClass.Status.Variables,
468+
field.NewPath("spec", "topology", "variables"))...)
469+
if cluster.Spec.Topology.Workers != nil {
470+
for i, md := range cluster.Spec.Topology.Workers.MachineDeployments {
471+
// Continue if there are no variable overrides.
472+
if md.Variables == nil || len(md.Variables.Overrides) == 0 {
473+
continue
474+
}
475+
allErrs = append(allErrs, variables.ValidateMachineDeploymentVariables(md.Variables.Overrides, clusterClass.Status.Variables,
476+
field.NewPath("spec", "topology", "workers", "machineDeployments").Index(i).Child("variables", "overrides"))...)
477+
}
478+
}
479+
return allErrs
480+
}
481+
456482
// DefaultVariables defaults variables in the Cluster based on information in the ClusterClass.
457483
func DefaultVariables(cluster *clusterv1.Cluster, clusterClass *clusterv1.ClusterClass) field.ErrorList {
458484
var allErrs field.ErrorList
@@ -489,7 +515,7 @@ func DefaultVariables(cluster *clusterv1.Cluster, clusterClass *clusterv1.Cluste
489515
}
490516

491517
// ValidateClusterForClusterClass uses information in the ClusterClass to validate the Cluster.
492-
func ValidateClusterForClusterClass(cluster *clusterv1.Cluster, clusterClass *clusterv1.ClusterClass, fldPath *field.Path) field.ErrorList {
518+
func ValidateClusterForClusterClass(cluster *clusterv1.Cluster, clusterClass *clusterv1.ClusterClass) field.ErrorList {
493519
var allErrs field.ErrorList
494520
if cluster == nil {
495521
return field.ErrorList{field.InternalError(field.NewPath(""), errors.New("Cluster can not be nil"))}
@@ -499,24 +525,8 @@ func ValidateClusterForClusterClass(cluster *clusterv1.Cluster, clusterClass *cl
499525
}
500526
allErrs = append(allErrs, check.MachineDeploymentTopologiesAreValidAndDefinedInClusterClass(cluster, clusterClass)...)
501527

502-
// validate the MachineHealthChecks defined in the cluster topology
528+
// Validate the MachineHealthChecks defined in the cluster topology.
503529
allErrs = append(allErrs, validateMachineHealthChecks(cluster, clusterClass)...)
504-
505-
// Check if the variables defined in the ClusterClass are valid.
506-
allErrs = append(allErrs, variables.ValidateClusterVariables(cluster.Spec.Topology.Variables, clusterClass.Status.Variables,
507-
fldPath.Child("variables"))...)
508-
509-
if cluster.Spec.Topology.Workers != nil {
510-
for i, md := range cluster.Spec.Topology.Workers.MachineDeployments {
511-
// Continue if there are no variable overrides.
512-
if md.Variables == nil || len(md.Variables.Overrides) == 0 {
513-
continue
514-
}
515-
allErrs = append(allErrs, variables.ValidateMachineDeploymentVariables(md.Variables.Overrides, clusterClass.Status.Variables,
516-
fldPath.Child("workers", "machineDeployments").Index(i).Child("variables", "overrides"))...)
517-
}
518-
}
519-
520530
return allErrs
521531
}
522532

0 commit comments

Comments
 (0)