From e219df1bd7cd40a66f5d575e9bc22de50c9ee526 Mon Sep 17 00:00:00 2001 From: Richard Chen Date: Thu, 16 Feb 2023 15:36:51 -0800 Subject: [PATCH] Add validation webhooks (GKE part 7) --- exp/api/v1beta1/gcpmanagedcluster_webhook.go | 36 ++++++- .../v1beta1/gcpmanagedcontrolplane_webhook.go | 57 ++++++++++- .../v1beta1/gcpmanagedmachinepool_webhook.go | 98 ++++++++++++++++++- 3 files changed, 182 insertions(+), 9 deletions(-) diff --git a/exp/api/v1beta1/gcpmanagedcluster_webhook.go b/exp/api/v1beta1/gcpmanagedcluster_webhook.go index 9943375ed..4b2c33844 100644 --- a/exp/api/v1beta1/gcpmanagedcluster_webhook.go +++ b/exp/api/v1beta1/gcpmanagedcluster_webhook.go @@ -17,7 +17,10 @@ limitations under the License. package v1beta1 import ( + "github.com/google/go-cmp/cmp" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" @@ -53,10 +56,37 @@ func (r *GCPManagedCluster) ValidateCreate() error { } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. -func (r *GCPManagedCluster) ValidateUpdate(old runtime.Object) error { +func (r *GCPManagedCluster) ValidateUpdate(oldRaw runtime.Object) error { gcpmanagedclusterlog.Info("validate update", "name", r.Name) - - return nil + var allErrs field.ErrorList + old := oldRaw.(*GCPManagedCluster) + + if !cmp.Equal(r.Spec.Project, old.Spec.Project) { + allErrs = append(allErrs, + field.Invalid(field.NewPath("spec", "Project"), + r.Spec.Project, "field is immutable"), + ) + } + + if !cmp.Equal(r.Spec.Region, old.Spec.Region) { + allErrs = append(allErrs, + field.Invalid(field.NewPath("spec", "Region"), + r.Spec.Region, "field is immutable"), + ) + } + + if !cmp.Equal(r.Spec.CredentialsRef, old.Spec.CredentialsRef) { + allErrs = append(allErrs, + field.Invalid(field.NewPath("spec", "CredentialsRef"), + r.Spec.CredentialsRef, "field is immutable"), + ) + } + + if len(allErrs) == 0 { + return nil + } + + return apierrors.NewInvalid(GroupVersion.WithKind("GCPManagedCluster").GroupKind(), r.Name, allErrs) } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type. diff --git a/exp/api/v1beta1/gcpmanagedcontrolplane_webhook.go b/exp/api/v1beta1/gcpmanagedcontrolplane_webhook.go index 1c2e9d8a7..04a57cf17 100644 --- a/exp/api/v1beta1/gcpmanagedcontrolplane_webhook.go +++ b/exp/api/v1beta1/gcpmanagedcontrolplane_webhook.go @@ -20,6 +20,11 @@ import ( "fmt" "strings" + "github.com/google/go-cmp/cmp" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/validation/field" + "github.com/pkg/errors" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/cluster-api-provider-gcp/util/hash" @@ -70,15 +75,61 @@ var _ webhook.Validator = &GCPManagedControlPlane{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type. func (r *GCPManagedControlPlane) ValidateCreate() error { gcpmanagedcontrolplanelog.Info("validate create", "name", r.Name) + var allErrs field.ErrorList - return nil + if len(r.Spec.ClusterName) > maxClusterNameLength { + allErrs = append(allErrs, + field.Invalid(field.NewPath("spec", "ClusterName"), + r.Spec.ClusterName, fmt.Sprintf("cluster name cannot have more than %d characters", maxClusterNameLength)), + ) + } + + if len(allErrs) == 0 { + return nil + } + + return apierrors.NewInvalid(GroupVersion.WithKind("GCPManagedControlPlane").GroupKind(), r.Name, allErrs) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. -func (r *GCPManagedControlPlane) ValidateUpdate(old runtime.Object) error { +func (r *GCPManagedControlPlane) ValidateUpdate(oldRaw runtime.Object) error { gcpmanagedcontrolplanelog.Info("validate update", "name", r.Name) + var allErrs field.ErrorList + old := oldRaw.(*GCPManagedControlPlane) + + if !cmp.Equal(r.Spec.ClusterName, old.Spec.ClusterName) { + allErrs = append(allErrs, + field.Invalid(field.NewPath("spec", "ClusterName"), + r.Spec.ClusterName, "field is immutable"), + ) + } - return nil + if !cmp.Equal(r.Spec.Project, old.Spec.Project) { + allErrs = append(allErrs, + field.Invalid(field.NewPath("spec", "Project"), + r.Spec.Project, "field is immutable"), + ) + } + + if !cmp.Equal(r.Spec.Location, old.Spec.Location) { + allErrs = append(allErrs, + field.Invalid(field.NewPath("spec", "Location"), + r.Spec.Location, "field is immutable"), + ) + } + + if !cmp.Equal(r.Spec.EnableAutopilot, old.Spec.EnableAutopilot) { + allErrs = append(allErrs, + field.Invalid(field.NewPath("spec", "EnableAutopilot"), + r.Spec.EnableAutopilot, "field is immutable"), + ) + } + + if len(allErrs) == 0 { + return nil + } + + return apierrors.NewInvalid(GroupVersion.WithKind("GCPManagedControlPlane").GroupKind(), r.Name, allErrs) } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type. diff --git a/exp/api/v1beta1/gcpmanagedmachinepool_webhook.go b/exp/api/v1beta1/gcpmanagedmachinepool_webhook.go index 88e3e2e36..ca442f342 100644 --- a/exp/api/v1beta1/gcpmanagedmachinepool_webhook.go +++ b/exp/api/v1beta1/gcpmanagedmachinepool_webhook.go @@ -17,12 +17,21 @@ limitations under the License. package v1beta1 import ( + "fmt" + + "github.com/google/go-cmp/cmp" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" ) +const ( + maxNodePoolNameLength = 40 +) + // log is for logging in this package. var gcpmanagedmachinepoollog = logf.Log.WithName("gcpmanagedmachinepool-resource") @@ -45,18 +54,101 @@ func (r *GCPManagedMachinePool) Default() { var _ webhook.Validator = &GCPManagedMachinePool{} +func (r *GCPManagedMachinePool) validateNodeCount() field.ErrorList { + var allErrs field.ErrorList + if r.Spec.InitialNodeCount < 0 { + allErrs = append(allErrs, + field.Invalid(field.NewPath("spec", "InitialNodeCount"), + r.Spec.InitialNodeCount, "must be greater or equal to zero"), + ) + } + if len(allErrs) == 0 { + return nil + } + return allErrs +} + +func (r *GCPManagedMachinePool) validateScaling() field.ErrorList { + var allErrs field.ErrorList + if r.Spec.Scaling != nil { + minField := field.NewPath("spec", "scaling", "minCount") + maxField := field.NewPath("spec", "scaling", "maxCount") + min := r.Spec.Scaling.MinCount + max := r.Spec.Scaling.MaxCount + if min != nil { + if *min < 0 { + allErrs = append(allErrs, field.Invalid(minField, *min, "must be greater or equal zero")) + } + if *min > r.Spec.InitialNodeCount { + allErrs = append(allErrs, field.Invalid(minField, *min, fmt.Sprintf("must be less or equal to %d", r.Spec.InitialNodeCount))) + } + if max != nil && *max < *min { + allErrs = append(allErrs, field.Invalid(maxField, *max, fmt.Sprintf("must be greater than field %s", minField.String()))) + } + } + if max != nil && *max < r.Spec.InitialNodeCount { + allErrs = append(allErrs, field.Invalid(maxField, *max, fmt.Sprintf("must be greater or equal to %d", r.Spec.InitialNodeCount))) + } + } + if len(allErrs) == 0 { + return nil + } + return allErrs +} + // ValidateCreate implements webhook.Validator so a webhook will be registered for the type. func (r *GCPManagedMachinePool) ValidateCreate() error { gcpmanagedmachinepoollog.Info("validate create", "name", r.Name) + var allErrs field.ErrorList - return nil + if len(r.Spec.NodePoolName) > maxNodePoolNameLength { + allErrs = append(allErrs, + field.Invalid(field.NewPath("spec", "NodePoolName"), + r.Spec.NodePoolName, fmt.Sprintf("node pool name cannot have more than %d characters", maxNodePoolNameLength)), + ) + } + + if errs := r.validateNodeCount(); errs != nil || len(errs) == 0 { + allErrs = append(allErrs, errs...) + } + + if errs := r.validateScaling(); errs != nil || len(errs) == 0 { + allErrs = append(allErrs, errs...) + } + + if len(allErrs) == 0 { + return nil + } + + return apierrors.NewInvalid(GroupVersion.WithKind("GCPManagedMachinePool").GroupKind(), r.Name, allErrs) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. -func (r *GCPManagedMachinePool) ValidateUpdate(old runtime.Object) error { +func (r *GCPManagedMachinePool) ValidateUpdate(oldRaw runtime.Object) error { gcpmanagedmachinepoollog.Info("validate update", "name", r.Name) + var allErrs field.ErrorList + old := oldRaw.(*GCPManagedMachinePool) - return nil + if !cmp.Equal(r.Spec.NodePoolName, old.Spec.NodePoolName) { + allErrs = append(allErrs, + field.Invalid(field.NewPath("spec", "NodePoolName"), + r.Spec.NodePoolName, "field is immutable"), + ) + } + + if errs := r.validateNodeCount(); errs != nil || len(errs) == 0 { + allErrs = append(allErrs, errs...) + } + + if errs := r.validateScaling(); errs != nil || len(errs) == 0 { + allErrs = append(allErrs, errs...) + } + + if len(allErrs) == 0 { + return nil + } + + return apierrors.NewInvalid(GroupVersion.WithKind("GCPManagedMachinePool").GroupKind(), r.Name, allErrs) } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type.