sigs.k8s.io/cluster-api-provider-aws@v1.5.5/exp/api/v1beta1/awsmachinepool_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 v1beta1
    18  
    19  import (
    20  	"time"
    21  
    22  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    23  	"k8s.io/apimachinery/pkg/runtime"
    24  	"k8s.io/apimachinery/pkg/util/validation/field"
    25  	ctrl "sigs.k8s.io/controller-runtime"
    26  	logf "sigs.k8s.io/controller-runtime/pkg/log"
    27  	"sigs.k8s.io/controller-runtime/pkg/webhook"
    28  
    29  	"sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    30  )
    31  
    32  var log = logf.Log.WithName("awsmachinepool-resource")
    33  
    34  // SetupWebhookWithManager will setup the webhooks for the AWSMachinePool.
    35  func (r *AWSMachinePool) SetupWebhookWithManager(mgr ctrl.Manager) error {
    36  	return ctrl.NewWebhookManagedBy(mgr).
    37  		For(r).
    38  		Complete()
    39  }
    40  
    41  // +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-awsmachinepool,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=awsmachinepools,versions=v1beta1,name=validation.awsmachinepool.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1beta1
    42  // +kubebuilder:webhook:verbs=create;update,path=/mutate-infrastructure-cluster-x-k8s-io-v1beta1-awsmachinepool,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=awsmachinepools,versions=v1beta1,name=default.awsmachinepool.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1beta1
    43  
    44  var _ webhook.Defaulter = &AWSMachinePool{}
    45  var _ webhook.Validator = &AWSMachinePool{}
    46  
    47  func (r *AWSMachinePool) validateDefaultCoolDown() field.ErrorList {
    48  	var allErrs field.ErrorList
    49  
    50  	if int(r.Spec.DefaultCoolDown.Duration.Seconds()) < 0 {
    51  		allErrs = append(allErrs, field.Required(field.NewPath("spec.DefaultCoolDown"), "DefaultCoolDown must be greater than zero"))
    52  	}
    53  
    54  	return allErrs
    55  }
    56  
    57  func (r *AWSMachinePool) validateRootVolume() field.ErrorList {
    58  	var allErrs field.ErrorList
    59  
    60  	if r.Spec.AWSLaunchTemplate.RootVolume == nil {
    61  		return allErrs
    62  	}
    63  
    64  	if v1beta1.VolumeTypesProvisioned.Has(string(r.Spec.AWSLaunchTemplate.RootVolume.Type)) && r.Spec.AWSLaunchTemplate.RootVolume.IOPS == 0 {
    65  		allErrs = append(allErrs, field.Required(field.NewPath("spec.awsLaunchTemplate.rootVolume.iops"), "iops required if type is 'io1' or 'io2'"))
    66  	}
    67  
    68  	if r.Spec.AWSLaunchTemplate.RootVolume.Throughput != nil {
    69  		if r.Spec.AWSLaunchTemplate.RootVolume.Type != v1beta1.VolumeTypeGP3 {
    70  			allErrs = append(allErrs, field.Required(field.NewPath("spec.awsLaunchTemplate.rootVolume.throughput"), "throughput is valid only for type 'gp3'"))
    71  		}
    72  		if *r.Spec.AWSLaunchTemplate.RootVolume.Throughput < 0 {
    73  			allErrs = append(allErrs, field.Required(field.NewPath("spec.awsLaunchTemplate.rootVolume.throughput"), "throughput must be nonnegative"))
    74  		}
    75  	}
    76  
    77  	if r.Spec.AWSLaunchTemplate.RootVolume.DeviceName != "" {
    78  		allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.awsLaunchTemplate.rootVolume.deviceName"), "root volume shouldn't have device name"))
    79  	}
    80  
    81  	return allErrs
    82  }
    83  
    84  func (r *AWSMachinePool) validateSubnets() field.ErrorList {
    85  	var allErrs field.ErrorList
    86  
    87  	if r.Spec.Subnets == nil {
    88  		return allErrs
    89  	}
    90  
    91  	for _, subnet := range r.Spec.Subnets {
    92  		if subnet.ARN != nil {
    93  			log.Info("ARN field is deprecated and is no operation function.")
    94  		}
    95  		if subnet.ID != nil && subnet.Filters != nil {
    96  			allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.subnets.filters"), "providing either subnet ID or filter is supported, should not provide both"))
    97  			break
    98  		}
    99  	}
   100  
   101  	return allErrs
   102  }
   103  
   104  func (r *AWSMachinePool) validateAdditionalSecurityGroups() field.ErrorList {
   105  	var allErrs field.ErrorList
   106  	for _, sg := range r.Spec.AWSLaunchTemplate.AdditionalSecurityGroups {
   107  		if sg.ID != nil && sg.Filters != nil {
   108  			allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.awsLaunchTemplate.AdditionalSecurityGroups"), "either ID or filters should be used"))
   109  		}
   110  		if sg.ARN != nil {
   111  			log.Info("ARN field is deprecated and is no operation function.")
   112  		}
   113  	}
   114  	return allErrs
   115  }
   116  
   117  // ValidateCreate will do any extra validation when creating a AWSMachinePool.
   118  func (r *AWSMachinePool) ValidateCreate() error {
   119  	log.Info("AWSMachinePool validate create", "name", r.Name)
   120  
   121  	var allErrs field.ErrorList
   122  
   123  	allErrs = append(allErrs, r.validateDefaultCoolDown()...)
   124  	allErrs = append(allErrs, r.validateRootVolume()...)
   125  	allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
   126  	allErrs = append(allErrs, r.validateSubnets()...)
   127  	allErrs = append(allErrs, r.validateAdditionalSecurityGroups()...)
   128  
   129  	if len(allErrs) == 0 {
   130  		return nil
   131  	}
   132  
   133  	return apierrors.NewInvalid(
   134  		r.GroupVersionKind().GroupKind(),
   135  		r.Name,
   136  		allErrs,
   137  	)
   138  }
   139  
   140  // ValidateUpdate will do any extra validation when updating a AWSMachinePool.
   141  func (r *AWSMachinePool) ValidateUpdate(old runtime.Object) error {
   142  	var allErrs field.ErrorList
   143  
   144  	allErrs = append(allErrs, r.validateDefaultCoolDown()...)
   145  	allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
   146  	allErrs = append(allErrs, r.validateSubnets()...)
   147  	allErrs = append(allErrs, r.validateAdditionalSecurityGroups()...)
   148  
   149  	if len(allErrs) == 0 {
   150  		return nil
   151  	}
   152  
   153  	return apierrors.NewInvalid(
   154  		r.GroupVersionKind().GroupKind(),
   155  		r.Name,
   156  		allErrs,
   157  	)
   158  }
   159  
   160  // ValidateDelete allows you to add any extra validation when deleting.
   161  func (r *AWSMachinePool) ValidateDelete() error {
   162  	return nil
   163  }
   164  
   165  // Default will set default values for the AWSMachinePool.
   166  func (r *AWSMachinePool) Default() {
   167  	if int(r.Spec.DefaultCoolDown.Duration.Seconds()) == 0 {
   168  		log.Info("DefaultCoolDown is zero, setting 300 seconds as default")
   169  		r.Spec.DefaultCoolDown.Duration = 300 * time.Second
   170  	}
   171  }