sigs.k8s.io/cluster-api-provider-azure@v1.14.3/api/v1beta1/azuremachine_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  	"context"
    21  	"reflect"
    22  
    23  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    24  	"k8s.io/apimachinery/pkg/runtime"
    25  	"k8s.io/apimachinery/pkg/util/validation/field"
    26  	webhookutils "sigs.k8s.io/cluster-api-provider-azure/util/webhook"
    27  	ctrl "sigs.k8s.io/controller-runtime"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    30  )
    31  
    32  // SetupAzureMachineWebhookWithManager sets up and registers the webhook with the manager.
    33  func SetupAzureMachineWebhookWithManager(mgr ctrl.Manager) error {
    34  	mw := &azureMachineWebhook{Client: mgr.GetClient()}
    35  	return ctrl.NewWebhookManagedBy(mgr).
    36  		For(&AzureMachine{}).
    37  		WithDefaulter(mw).
    38  		WithValidator(mw).
    39  		Complete()
    40  }
    41  
    42  // +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-azuremachine,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=azuremachines,versions=v1beta1,name=validation.azuremachine.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1
    43  // +kubebuilder:webhook:verbs=create;update,path=/mutate-infrastructure-cluster-x-k8s-io-v1beta1-azuremachine,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=azuremachines,versions=v1beta1,name=default.azuremachine.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1
    44  
    45  // azureMachineWebhook implements a validating and defaulting webhook for AzureMachines.
    46  type azureMachineWebhook struct {
    47  	Client client.Client
    48  }
    49  
    50  // ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
    51  func (mw *azureMachineWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
    52  	m, ok := obj.(*AzureMachine)
    53  	if !ok {
    54  		return nil, apierrors.NewBadRequest("expected an AzureMachine resource")
    55  	}
    56  	spec := m.Spec
    57  
    58  	allErrs := ValidateAzureMachineSpec(spec)
    59  
    60  	roleAssignmentName := ""
    61  	if spec.SystemAssignedIdentityRole != nil {
    62  		roleAssignmentName = spec.SystemAssignedIdentityRole.Name
    63  	}
    64  
    65  	if errs := ValidateSystemAssignedIdentity(spec.Identity, "", roleAssignmentName, field.NewPath("roleAssignmentName")); len(errs) > 0 {
    66  		allErrs = append(allErrs, errs...)
    67  	}
    68  
    69  	if len(allErrs) == 0 {
    70  		return nil, nil
    71  	}
    72  
    73  	return nil, apierrors.NewInvalid(GroupVersion.WithKind(AzureMachineKind).GroupKind(), m.Name, allErrs)
    74  }
    75  
    76  // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
    77  func (mw *azureMachineWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
    78  	var allErrs field.ErrorList
    79  	old, ok := oldObj.(*AzureMachine)
    80  	if !ok {
    81  		return nil, apierrors.NewBadRequest("expected an AzureMachine resource")
    82  	}
    83  	m, ok := newObj.(*AzureMachine)
    84  	if !ok {
    85  		return nil, apierrors.NewBadRequest("expected an AzureMachine resource")
    86  	}
    87  
    88  	if err := webhookutils.ValidateImmutable(
    89  		field.NewPath("Spec", "Image"),
    90  		old.Spec.Image,
    91  		m.Spec.Image); err != nil {
    92  		allErrs = append(allErrs, err)
    93  	}
    94  
    95  	if err := webhookutils.ValidateImmutable(
    96  		field.NewPath("Spec", "Identity"),
    97  		old.Spec.Identity,
    98  		m.Spec.Identity); err != nil {
    99  		allErrs = append(allErrs, err)
   100  	}
   101  
   102  	if err := webhookutils.ValidateImmutable(
   103  		field.NewPath("Spec", "SystemAssignedIdentityRole"),
   104  		old.Spec.SystemAssignedIdentityRole,
   105  		m.Spec.SystemAssignedIdentityRole); err != nil {
   106  		allErrs = append(allErrs, err)
   107  	}
   108  
   109  	if err := webhookutils.ValidateImmutable(
   110  		field.NewPath("Spec", "UserAssignedIdentities"),
   111  		old.Spec.UserAssignedIdentities,
   112  		m.Spec.UserAssignedIdentities); err != nil {
   113  		allErrs = append(allErrs, err)
   114  	}
   115  
   116  	if err := webhookutils.ValidateImmutable(
   117  		field.NewPath("Spec", "RoleAssignmentName"),
   118  		old.Spec.RoleAssignmentName,
   119  		m.Spec.RoleAssignmentName); err != nil {
   120  		allErrs = append(allErrs, err)
   121  	}
   122  
   123  	if err := webhookutils.ValidateImmutable(
   124  		field.NewPath("Spec", "OSDisk"),
   125  		old.Spec.OSDisk,
   126  		m.Spec.OSDisk); err != nil {
   127  		allErrs = append(allErrs, err)
   128  	}
   129  
   130  	if err := webhookutils.ValidateImmutable(
   131  		field.NewPath("Spec", "DataDisks"),
   132  		old.Spec.DataDisks,
   133  		m.Spec.DataDisks); err != nil {
   134  		allErrs = append(allErrs, err)
   135  	}
   136  
   137  	if err := webhookutils.ValidateImmutable(
   138  		field.NewPath("Spec", "SSHPublicKey"),
   139  		old.Spec.SSHPublicKey,
   140  		m.Spec.SSHPublicKey); err != nil {
   141  		allErrs = append(allErrs, err)
   142  	}
   143  
   144  	if err := webhookutils.ValidateImmutable(
   145  		field.NewPath("Spec", "AllocatePublicIP"),
   146  		old.Spec.AllocatePublicIP,
   147  		m.Spec.AllocatePublicIP); err != nil {
   148  		allErrs = append(allErrs, err)
   149  	}
   150  
   151  	if err := webhookutils.ValidateImmutable(
   152  		field.NewPath("Spec", "EnableIPForwarding"),
   153  		old.Spec.EnableIPForwarding,
   154  		m.Spec.EnableIPForwarding); err != nil {
   155  		allErrs = append(allErrs, err)
   156  	}
   157  
   158  	// Spec.AcceleratedNetworking can only be reset to nil and no other changes apart from that
   159  	// is accepted if the field is set.
   160  	// Ref issue #3518
   161  	if err := webhookutils.ValidateZeroTransition(
   162  		field.NewPath("Spec", "AcceleratedNetworking"),
   163  		old.Spec.AcceleratedNetworking,
   164  		m.Spec.AcceleratedNetworking); err != nil {
   165  		allErrs = append(allErrs, err)
   166  	}
   167  
   168  	if err := webhookutils.ValidateImmutable(
   169  		field.NewPath("Spec", "SpotVMOptions"),
   170  		old.Spec.SpotVMOptions,
   171  		m.Spec.SpotVMOptions); err != nil {
   172  		allErrs = append(allErrs, err)
   173  	}
   174  
   175  	if err := webhookutils.ValidateImmutable(
   176  		field.NewPath("Spec", "SecurityProfile"),
   177  		old.Spec.SecurityProfile,
   178  		m.Spec.SecurityProfile); err != nil {
   179  		allErrs = append(allErrs, err)
   180  	}
   181  
   182  	if old.Spec.Diagnostics != nil {
   183  		if err := webhookutils.ValidateImmutable(
   184  			field.NewPath("Spec", "Diagnostics"),
   185  			old.Spec.Diagnostics,
   186  			m.Spec.Diagnostics); err != nil {
   187  			allErrs = append(allErrs, err)
   188  		}
   189  	}
   190  
   191  	if !reflect.DeepEqual(m.Spec.NetworkInterfaces, old.Spec.NetworkInterfaces) {
   192  		// The defaulting webhook may have migrated values from the old SubnetName field to the new NetworkInterfaces format.
   193  		old.Spec.SetNetworkInterfacesDefaults()
   194  
   195  		// The reconciler will populate the SubnetName on the first interface if the user left it blank.
   196  		if old.Spec.NetworkInterfaces[0].SubnetName == "" && m.Spec.NetworkInterfaces[0].SubnetName != "" {
   197  			old.Spec.NetworkInterfaces[0].SubnetName = m.Spec.NetworkInterfaces[0].SubnetName
   198  		}
   199  
   200  		// Enforce immutability for all other changes to NetworkInterfaces.
   201  		if !reflect.DeepEqual(m.Spec.NetworkInterfaces, old.Spec.NetworkInterfaces) {
   202  			allErrs = append(allErrs,
   203  				field.Invalid(field.NewPath("spec", "networkInterfaces"),
   204  					m.Spec.NetworkInterfaces, "field is immutable"),
   205  			)
   206  		}
   207  	}
   208  
   209  	if len(allErrs) == 0 {
   210  		return nil, nil
   211  	}
   212  	return nil, apierrors.NewInvalid(GroupVersion.WithKind(AzureMachineKind).GroupKind(), m.Name, allErrs)
   213  }
   214  
   215  // ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
   216  func (mw *azureMachineWebhook) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
   217  	return nil, nil
   218  }
   219  
   220  // Default implements webhook.Defaulter so a webhook will be registered for the type.
   221  func (mw *azureMachineWebhook) Default(ctx context.Context, obj runtime.Object) error {
   222  	m, ok := obj.(*AzureMachine)
   223  	if !ok {
   224  		return apierrors.NewBadRequest("expected an AzureMachine resource")
   225  	}
   226  	return m.SetDefaults(mw.Client)
   227  }