sigs.k8s.io/cluster-api@v1.7.1/exp/addons/internal/webhooks/clusterresourceset_webhook.go (about)

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package webhooks
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"reflect"
    23  
    24  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apimachinery/pkg/util/validation/field"
    28  	ctrl "sigs.k8s.io/controller-runtime"
    29  	"sigs.k8s.io/controller-runtime/pkg/webhook"
    30  	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    31  
    32  	addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1"
    33  	"sigs.k8s.io/cluster-api/feature"
    34  )
    35  
    36  // ClusterResourceSet implements a validation and defaulting webhook for ClusterResourceSet.
    37  type ClusterResourceSet struct{}
    38  
    39  func (webhook *ClusterResourceSet) SetupWebhookWithManager(mgr ctrl.Manager) error {
    40  	return ctrl.NewWebhookManagedBy(mgr).
    41  		For(&addonsv1.ClusterResourceSet{}).
    42  		WithDefaulter(webhook).
    43  		WithValidator(webhook).
    44  		Complete()
    45  }
    46  
    47  // +kubebuilder:webhook:verbs=create;update,path=/validate-addons-cluster-x-k8s-io-v1beta1-clusterresourceset,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=addons.cluster.x-k8s.io,resources=clusterresourcesets,versions=v1beta1,name=validation.clusterresourceset.addons.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1
    48  // +kubebuilder:webhook:verbs=create;update,path=/mutate-addons-cluster-x-k8s-io-v1beta1-clusterresourceset,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=addons.cluster.x-k8s.io,resources=clusterresourcesets,versions=v1beta1,name=default.clusterresourceset.addons.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1
    49  
    50  var _ webhook.CustomDefaulter = &ClusterResourceSet{}
    51  var _ webhook.CustomValidator = &ClusterResourceSet{}
    52  
    53  // Default implements webhook.Defaulter so a webhook will be registered for the type.
    54  func (webhook *ClusterResourceSet) Default(_ context.Context, obj runtime.Object) error {
    55  	crs, ok := obj.(*addonsv1.ClusterResourceSet)
    56  	if !ok {
    57  		return apierrors.NewBadRequest(fmt.Sprintf("expected a ClusterResourceSet but got a %T", obj))
    58  	}
    59  	// ClusterResourceSet Strategy defaults to ApplyOnce.
    60  	if crs.Spec.Strategy == "" {
    61  		crs.Spec.Strategy = string(addonsv1.ClusterResourceSetStrategyApplyOnce)
    62  	}
    63  	return nil
    64  }
    65  
    66  // ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
    67  func (webhook *ClusterResourceSet) ValidateCreate(_ context.Context, newObj runtime.Object) (admission.Warnings, error) {
    68  	newCRS, ok := newObj.(*addonsv1.ClusterResourceSet)
    69  	if !ok {
    70  		return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a ClusterResourceSet but got a %T", newObj))
    71  	}
    72  	return nil, webhook.validate(nil, newCRS)
    73  }
    74  
    75  // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
    76  func (webhook *ClusterResourceSet) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
    77  	oldCRS, ok := oldObj.(*addonsv1.ClusterResourceSet)
    78  	if !ok {
    79  		return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a ClusterResourceSet but got a %T", oldObj))
    80  	}
    81  	newCRS, ok := newObj.(*addonsv1.ClusterResourceSet)
    82  	if !ok {
    83  		return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a ClusterResourceSet but got a %T", newObj))
    84  	}
    85  	return nil, webhook.validate(oldCRS, newCRS)
    86  }
    87  
    88  // ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
    89  func (webhook *ClusterResourceSet) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
    90  	return nil, nil
    91  }
    92  
    93  func (webhook *ClusterResourceSet) validate(oldCRS, newCRS *addonsv1.ClusterResourceSet) error {
    94  	// NOTE: ClusterResourceSet is behind ClusterResourceSet feature gate flag; the web hook
    95  	// must prevent creating new objects when the feature flag is disabled.
    96  	if !feature.Gates.Enabled(feature.ClusterResourceSet) {
    97  		return field.Forbidden(
    98  			field.NewPath("spec"),
    99  			"can be set only if the ClusterResourceSet feature flag is enabled",
   100  		)
   101  	}
   102  	var allErrs field.ErrorList
   103  
   104  	// Validate selector parses as Selector
   105  	selector, err := metav1.LabelSelectorAsSelector(&newCRS.Spec.ClusterSelector)
   106  	if err != nil {
   107  		allErrs = append(
   108  			allErrs,
   109  			field.Invalid(field.NewPath("spec", "clusterSelector"), newCRS.Spec.ClusterSelector, err.Error()),
   110  		)
   111  	}
   112  
   113  	// Validate that the selector isn't empty as null selectors do not select any objects.
   114  	if selector != nil && selector.Empty() {
   115  		allErrs = append(
   116  			allErrs,
   117  			field.Invalid(field.NewPath("spec", "clusterSelector"), newCRS.Spec.ClusterSelector, "selector must not be empty"),
   118  		)
   119  	}
   120  
   121  	if oldCRS != nil && oldCRS.Spec.Strategy != "" && oldCRS.Spec.Strategy != newCRS.Spec.Strategy {
   122  		allErrs = append(
   123  			allErrs,
   124  			field.Invalid(field.NewPath("spec", "strategy"), newCRS.Spec.Strategy, "field is immutable"),
   125  		)
   126  	}
   127  
   128  	if oldCRS != nil && !reflect.DeepEqual(oldCRS.Spec.ClusterSelector, newCRS.Spec.ClusterSelector) {
   129  		allErrs = append(
   130  			allErrs,
   131  			field.Invalid(field.NewPath("spec", "clusterSelector"), newCRS.Spec.ClusterSelector, "field is immutable"),
   132  		)
   133  	}
   134  
   135  	if len(allErrs) == 0 {
   136  		return nil
   137  	}
   138  
   139  	return apierrors.NewInvalid(addonsv1.GroupVersion.WithKind("ClusterResourceSet").GroupKind(), newCRS.Name, allErrs)
   140  }