sigs.k8s.io/cluster-api-provider-azure@v1.14.3/api/v1beta1/azuremanagedmachinepooltemplate_webhook.go (about)

     1  /*
     2  Copyright 2023 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  	"context"
    21  
    22  	"github.com/pkg/errors"
    23  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    24  	"k8s.io/apimachinery/pkg/runtime"
    25  	kerrors "k8s.io/apimachinery/pkg/util/errors"
    26  	"k8s.io/apimachinery/pkg/util/validation/field"
    27  	"k8s.io/utils/ptr"
    28  	"sigs.k8s.io/cluster-api-provider-azure/feature"
    29  	webhookutils "sigs.k8s.io/cluster-api-provider-azure/util/webhook"
    30  	capifeature "sigs.k8s.io/cluster-api/feature"
    31  	ctrl "sigs.k8s.io/controller-runtime"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    34  )
    35  
    36  // SetupAzureManagedMachinePoolTemplateWebhookWithManager will set up the webhook to be managed by the specified manager.
    37  func SetupAzureManagedMachinePoolTemplateWebhookWithManager(mgr ctrl.Manager) error {
    38  	mpw := &azureManagedMachinePoolTemplateWebhook{Client: mgr.GetClient()}
    39  	return ctrl.NewWebhookManagedBy(mgr).
    40  		For(&AzureManagedMachinePoolTemplate{}).
    41  		WithDefaulter(mpw).
    42  		WithValidator(mpw).
    43  		Complete()
    44  }
    45  
    46  //+kubebuilder:webhook:path=/mutate-infrastructure-cluster-x-k8s-io-v1beta1-azuremanagedmachinepooltemplate,mutating=true,failurePolicy=fail,groups=infrastructure.cluster.x-k8s.io,resources=azuremanagedmachinepooltemplates,verbs=create;update,versions=v1beta1,name=default.azuremanagedmachinepooltemplates.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1
    47  
    48  type azureManagedMachinePoolTemplateWebhook struct {
    49  	Client client.Client
    50  }
    51  
    52  // Default implements webhook.Defaulter so a webhook will be registered for the type.
    53  func (mpw *azureManagedMachinePoolTemplateWebhook) Default(ctx context.Context, obj runtime.Object) error {
    54  	mp, ok := obj.(*AzureManagedMachinePoolTemplate)
    55  	if !ok {
    56  		return apierrors.NewBadRequest("expected an AzureManagedMachinePoolTemplate")
    57  	}
    58  	if mp.Labels == nil {
    59  		mp.Labels = make(map[string]string)
    60  	}
    61  	mp.Labels[LabelAgentPoolMode] = mp.Spec.Template.Spec.Mode
    62  
    63  	if mp.Spec.Template.Spec.Name == nil || *mp.Spec.Template.Spec.Name == "" {
    64  		mp.Spec.Template.Spec.Name = &mp.Name
    65  	}
    66  
    67  	setDefault[*string](&mp.Spec.Template.Spec.OSType, ptr.To(DefaultOSType))
    68  
    69  	return nil
    70  }
    71  
    72  //+kubebuilder:webhook:verbs=create;update;delete,path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-azuremanagedmachinepooltemplate,mutating=false,failurePolicy=fail,groups=infrastructure.cluster.x-k8s.io,resources=azuremanagedmachinepooltemplates,versions=v1beta1,name=validation.azuremanagedmachinepooltemplates.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1
    73  
    74  // ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
    75  func (mpw *azureManagedMachinePoolTemplateWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
    76  	mp, ok := obj.(*AzureManagedMachinePoolTemplate)
    77  	if !ok {
    78  		return nil, apierrors.NewBadRequest("expected an AzureManagedMachinePoolTemplate")
    79  	}
    80  
    81  	if !feature.Gates.Enabled(capifeature.MachinePool) {
    82  		return nil, field.Forbidden(
    83  			field.NewPath("spec"),
    84  			"can be set only if the Cluster API 'MachinePool' feature flag is enabled",
    85  		)
    86  	}
    87  
    88  	var errs []error
    89  
    90  	errs = append(errs, validateMaxPods(
    91  		mp.Spec.Template.Spec.MaxPods,
    92  		field.NewPath("Spec", "Template", "Spec", "MaxPods")))
    93  
    94  	errs = append(errs, validateOSType(
    95  		mp.Spec.Template.Spec.Mode,
    96  		mp.Spec.Template.Spec.OSType,
    97  		field.NewPath("Spec", "Template", "Spec", "OSType")))
    98  
    99  	errs = append(errs, validateMPName(
   100  		mp.Name,
   101  		mp.Spec.Template.Spec.Name,
   102  		mp.Spec.Template.Spec.OSType,
   103  		field.NewPath("Spec", "Template", "Spec", "Name")))
   104  
   105  	errs = append(errs, validateNodeLabels(
   106  		mp.Spec.Template.Spec.NodeLabels,
   107  		field.NewPath("Spec", "Template", "Spec", "NodeLabels")))
   108  
   109  	errs = append(errs, validateNodePublicIPPrefixID(
   110  		mp.Spec.Template.Spec.NodePublicIPPrefixID,
   111  		field.NewPath("Spec", "Template", "Spec", "NodePublicIPPrefixID")))
   112  
   113  	errs = append(errs, validateEnableNodePublicIP(
   114  		mp.Spec.Template.Spec.EnableNodePublicIP,
   115  		mp.Spec.Template.Spec.NodePublicIPPrefixID,
   116  		field.NewPath("Spec", "Template", "Spec", "EnableNodePublicIP")))
   117  
   118  	errs = append(errs, validateKubeletConfig(
   119  		mp.Spec.Template.Spec.KubeletConfig,
   120  		field.NewPath("Spec", "Template", "Spec", "KubeletConfig")))
   121  
   122  	errs = append(errs, validateLinuxOSConfig(
   123  		mp.Spec.Template.Spec.LinuxOSConfig,
   124  		mp.Spec.Template.Spec.KubeletConfig,
   125  		field.NewPath("Spec", "Template", "Spec", "LinuxOSConfig")))
   126  
   127  	return nil, kerrors.NewAggregate(errs)
   128  }
   129  
   130  // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
   131  func (mpw *azureManagedMachinePoolTemplateWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
   132  	var allErrs field.ErrorList
   133  	old, ok := oldObj.(*AzureManagedMachinePoolTemplate)
   134  	if !ok {
   135  		return nil, apierrors.NewBadRequest("expected an AzureManagedMachinePoolTemplate")
   136  	}
   137  	mp, ok := newObj.(*AzureManagedMachinePoolTemplate)
   138  	if !ok {
   139  		return nil, apierrors.NewBadRequest("expected an AzureManagedMachinePoolTemplate")
   140  	}
   141  
   142  	if err := webhookutils.ValidateImmutable(
   143  		field.NewPath("Spec", "Template", "Spec", "Name"),
   144  		old.Spec.Template.Spec.Name,
   145  		mp.Spec.Template.Spec.Name); err != nil {
   146  		allErrs = append(allErrs, err)
   147  	}
   148  
   149  	if err := validateNodeLabels(mp.Spec.Template.Spec.NodeLabels, field.NewPath("Spec", "Template", "Spec", "NodeLabels")); err != nil {
   150  		allErrs = append(allErrs,
   151  			field.Invalid(
   152  				field.NewPath("Spec", "Template", "Spec", "NodeLabels"),
   153  				mp.Spec.Template.Spec.NodeLabels,
   154  				err.Error()))
   155  	}
   156  
   157  	if err := webhookutils.ValidateImmutable(
   158  		field.NewPath("Spec", "Template", "Spec", "OSType"),
   159  		old.Spec.Template.Spec.OSType,
   160  		mp.Spec.Template.Spec.OSType); err != nil {
   161  		allErrs = append(allErrs, err)
   162  	}
   163  
   164  	if err := webhookutils.ValidateImmutable(
   165  		field.NewPath("Spec", "Template", "Spec", "SKU"),
   166  		old.Spec.Template.Spec.SKU,
   167  		mp.Spec.Template.Spec.SKU); err != nil {
   168  		allErrs = append(allErrs, err)
   169  	}
   170  
   171  	if err := webhookutils.ValidateImmutable(
   172  		field.NewPath("Spec", "Template", "Spec", "OSDiskSizeGB"),
   173  		old.Spec.Template.Spec.OSDiskSizeGB,
   174  		mp.Spec.Template.Spec.OSDiskSizeGB); err != nil {
   175  		allErrs = append(allErrs, err)
   176  	}
   177  
   178  	if err := webhookutils.ValidateImmutable(
   179  		field.NewPath("Spec", "Template", "Spec", "SubnetName"),
   180  		old.Spec.Template.Spec.SubnetName,
   181  		mp.Spec.Template.Spec.SubnetName); err != nil && old.Spec.Template.Spec.SubnetName != nil {
   182  		allErrs = append(allErrs, err)
   183  	}
   184  
   185  	if err := webhookutils.ValidateImmutable(
   186  		field.NewPath("Spec", "Template", "Spec", "EnableFIPS"),
   187  		old.Spec.Template.Spec.EnableFIPS,
   188  		mp.Spec.Template.Spec.EnableFIPS); err != nil && old.Spec.Template.Spec.EnableFIPS != nil {
   189  		allErrs = append(allErrs, err)
   190  	}
   191  
   192  	if !webhookutils.EnsureStringSlicesAreEquivalent(mp.Spec.Template.Spec.AvailabilityZones, old.Spec.Template.Spec.AvailabilityZones) {
   193  		allErrs = append(allErrs,
   194  			field.Invalid(
   195  				field.NewPath("Spec", "Template", "Spec", "AvailabilityZones"),
   196  				mp.Spec.Template.Spec.AvailabilityZones,
   197  				"field is immutable"))
   198  	}
   199  
   200  	if mp.Spec.Template.Spec.Mode != string(NodePoolModeSystem) && old.Spec.Template.Spec.Mode == string(NodePoolModeSystem) {
   201  		// validate for last system node pool
   202  		if err := validateLastSystemNodePool(mpw.Client, mp.Spec.Template.Spec.NodeLabels, mp.Namespace, mp.Annotations); err != nil {
   203  			allErrs = append(allErrs, field.Forbidden(
   204  				field.NewPath("Spec", "Template", "Spec", "Mode"),
   205  				"Cannot change node pool mode to User, you must have at least one System node pool in your cluster"))
   206  		}
   207  	}
   208  
   209  	if err := webhookutils.ValidateImmutable(
   210  		field.NewPath("Spec", "Template", "Spec", "MaxPods"),
   211  		old.Spec.Template.Spec.MaxPods,
   212  		mp.Spec.Template.Spec.MaxPods); err != nil {
   213  		allErrs = append(allErrs, err)
   214  	}
   215  
   216  	if err := webhookutils.ValidateImmutable(
   217  		field.NewPath("Spec", "Template", "Spec", "OsDiskType"),
   218  		old.Spec.Template.Spec.OsDiskType,
   219  		mp.Spec.Template.Spec.OsDiskType); err != nil {
   220  		allErrs = append(allErrs, err)
   221  	}
   222  
   223  	if err := webhookutils.ValidateImmutable(
   224  		field.NewPath("Spec", "Template", "Spec", "ScaleSetPriority"),
   225  		old.Spec.Template.Spec.ScaleSetPriority,
   226  		mp.Spec.Template.Spec.ScaleSetPriority); err != nil {
   227  		allErrs = append(allErrs, err)
   228  	}
   229  
   230  	if err := webhookutils.ValidateImmutable(
   231  		field.NewPath("Spec", "Template", "Spec", "EnableUltraSSD"),
   232  		old.Spec.Template.Spec.EnableUltraSSD,
   233  		mp.Spec.Template.Spec.EnableUltraSSD); err != nil {
   234  		allErrs = append(allErrs, err)
   235  	}
   236  	if err := webhookutils.ValidateImmutable(
   237  		field.NewPath("Spec", "Template", "Spec", "EnableNodePublicIP"),
   238  		old.Spec.Template.Spec.EnableNodePublicIP,
   239  		mp.Spec.Template.Spec.EnableNodePublicIP); err != nil {
   240  		allErrs = append(allErrs, err)
   241  	}
   242  	if err := webhookutils.ValidateImmutable(
   243  		field.NewPath("Spec", "Template", "Spec", "NodePublicIPPrefixID"),
   244  		old.Spec.Template.Spec.NodePublicIPPrefixID,
   245  		mp.Spec.Template.Spec.NodePublicIPPrefixID); err != nil {
   246  		allErrs = append(allErrs, err)
   247  	}
   248  
   249  	if err := webhookutils.ValidateImmutable(
   250  		field.NewPath("Spec", "Template", "Spec", "KubeletConfig"),
   251  		old.Spec.Template.Spec.KubeletConfig,
   252  		mp.Spec.Template.Spec.KubeletConfig); err != nil {
   253  		allErrs = append(allErrs, err)
   254  	}
   255  
   256  	if err := webhookutils.ValidateImmutable(
   257  		field.NewPath("Spec", "Template", "Spec", "KubeletDiskType"),
   258  		old.Spec.Template.Spec.KubeletDiskType,
   259  		mp.Spec.Template.Spec.KubeletDiskType); err != nil {
   260  		allErrs = append(allErrs, err)
   261  	}
   262  
   263  	if err := webhookutils.ValidateImmutable(
   264  		field.NewPath("Spec", "Template", "Spec", "LinuxOSConfig"),
   265  		old.Spec.Template.Spec.LinuxOSConfig,
   266  		mp.Spec.Template.Spec.LinuxOSConfig); err != nil {
   267  		allErrs = append(allErrs, err)
   268  	}
   269  
   270  	if len(allErrs) == 0 {
   271  		return nil, nil
   272  	}
   273  	return nil, apierrors.NewInvalid(GroupVersion.WithKind(AzureManagedMachinePoolTemplateKind).GroupKind(), mp.Name, allErrs)
   274  }
   275  
   276  // ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
   277  func (mpw *azureManagedMachinePoolTemplateWebhook) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
   278  	mp, ok := obj.(*AzureManagedMachinePoolTemplate)
   279  	if !ok {
   280  		return nil, apierrors.NewBadRequest("expected an AzureManagedMachinePoolTemplate")
   281  	}
   282  	if mp.Spec.Template.Spec.Mode != string(NodePoolModeSystem) {
   283  		return nil, nil
   284  	}
   285  
   286  	return nil, errors.Wrapf(validateLastSystemNodePool(mpw.Client, mp.Spec.Template.Spec.NodeLabels, mp.Namespace, mp.Annotations), "if the delete is triggered via owner MachinePool please refer to trouble shooting section in https://capz.sigs.k8s.io/topics/managedcluster.html")
   287  }