sigs.k8s.io/cluster-api-provider-azure@v1.17.0/api/v1beta1/azuremachinetemplate_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 "fmt" 22 "reflect" 23 24 apierrors "k8s.io/apimachinery/pkg/api/errors" 25 "k8s.io/apimachinery/pkg/runtime" 26 "k8s.io/apimachinery/pkg/util/validation/field" 27 "k8s.io/utils/ptr" 28 "sigs.k8s.io/cluster-api/util/topology" 29 ctrl "sigs.k8s.io/controller-runtime" 30 "sigs.k8s.io/controller-runtime/pkg/webhook" 31 "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 32 ) 33 34 // AzureMachineTemplateImmutableMsg ... 35 const ( 36 AzureMachineTemplateImmutableMsg = "AzureMachineTemplate spec.template.spec field is immutable. Please create new resource instead. ref doc: https://cluster-api.sigs.k8s.io/tasks/updating-machine-templates.html" 37 AzureMachineTemplateRoleAssignmentNameMsg = "AzureMachineTemplate spec.template.spec.roleAssignmentName field can't be set" 38 AzureMachineTemplateSystemAssignedIdentityRoleNameMsg = "AzureMachineTemplate spec.template.spec.systemAssignedIdentityRole.name field can't be set" 39 ) 40 41 // SetupWebhookWithManager sets up and registers the webhook with the manager. 42 func (r *AzureMachineTemplate) SetupWebhookWithManager(mgr ctrl.Manager) error { 43 return ctrl.NewWebhookManagedBy(mgr). 44 For(r). 45 WithValidator(r). 46 WithDefaulter(r). 47 Complete() 48 } 49 50 // +kubebuilder:webhook:verbs=create;update,path=/mutate-infrastructure-cluster-x-k8s-io-v1beta1-azuremachinetemplate,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=azuremachinetemplates,versions=v1beta1,name=default.azuremachinetemplate.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 51 // +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-azuremachinetemplate,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=azuremachinetemplates,versions=v1beta1,name=validation.azuremachinetemplate.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 52 53 var _ webhook.CustomDefaulter = &AzureMachineTemplate{} 54 var _ webhook.CustomValidator = &AzureMachineTemplate{} 55 56 // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type. 57 func (r *AzureMachineTemplate) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { 58 t := obj.(*AzureMachineTemplate) 59 spec := t.Spec.Template.Spec 60 61 allErrs := ValidateAzureMachineSpec(spec) 62 63 if spec.RoleAssignmentName != "" { 64 allErrs = append(allErrs, 65 field.Invalid(field.NewPath("AzureMachineTemplate", "spec", "template", "spec", "roleAssignmentName"), t, AzureMachineTemplateRoleAssignmentNameMsg), 66 ) 67 } 68 69 if spec.SystemAssignedIdentityRole != nil && spec.SystemAssignedIdentityRole.Name != "" { 70 allErrs = append(allErrs, 71 field.Invalid(field.NewPath("AzureMachineTemplate", "spec", "template", "spec", "systemAssignedIdentityRole", "name"), t, AzureMachineTemplateSystemAssignedIdentityRoleNameMsg), 72 ) 73 } 74 75 if (r.Spec.Template.Spec.NetworkInterfaces != nil) && len(r.Spec.Template.Spec.NetworkInterfaces) > 0 && r.Spec.Template.Spec.SubnetName != "" { 76 allErrs = append(allErrs, field.Invalid(field.NewPath("AzureMachineTemplate", "spec", "template", "spec", "networkInterfaces"), r.Spec.Template.Spec.NetworkInterfaces, "cannot set both NetworkInterfaces and machine SubnetName")) 77 } 78 79 if (r.Spec.Template.Spec.NetworkInterfaces != nil) && len(r.Spec.Template.Spec.NetworkInterfaces) > 0 && r.Spec.Template.Spec.AcceleratedNetworking != nil { 80 allErrs = append(allErrs, field.Invalid(field.NewPath("AzureMachineTemplate", "spec", "template", "spec", "acceleratedNetworking"), r.Spec.Template.Spec.NetworkInterfaces, "cannot set both NetworkInterfaces and machine AcceleratedNetworking")) 81 } 82 83 for i, networkInterface := range r.Spec.Template.Spec.NetworkInterfaces { 84 if networkInterface.PrivateIPConfigs < 1 { 85 allErrs = append(allErrs, field.Invalid(field.NewPath("AzureMachineTemplate", "spec", "template", "spec", "networkInterfaces", "privateIPConfigs"), r.Spec.Template.Spec.NetworkInterfaces[i].PrivateIPConfigs, "networkInterface privateIPConfigs must be set to a minimum value of 1")) 86 } 87 } 88 89 if ptr.Deref(r.Spec.Template.Spec.DisableExtensionOperations, false) && len(r.Spec.Template.Spec.VMExtensions) > 0 { 90 allErrs = append(allErrs, field.Forbidden(field.NewPath("AzureMachineTemplate", "spec", "template", "spec", "vmExtensions"), "VMExtensions must be empty when DisableExtensionOperations is true")) 91 } 92 93 if len(allErrs) == 0 { 94 return nil, nil 95 } 96 97 return nil, apierrors.NewInvalid(GroupVersion.WithKind(AzureMachineTemplateKind).GroupKind(), t.Name, allErrs) 98 } 99 100 // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. 101 func (r *AzureMachineTemplate) ValidateUpdate(ctx context.Context, oldRaw runtime.Object, newRaw runtime.Object) (admission.Warnings, error) { 102 var allErrs field.ErrorList 103 old := oldRaw.(*AzureMachineTemplate) 104 t := newRaw.(*AzureMachineTemplate) 105 106 req, err := admission.RequestFromContext(ctx) 107 if err != nil { 108 return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a admission.Request inside context: %v", err)) 109 } 110 111 if !topology.ShouldSkipImmutabilityChecks(req, t) && 112 !reflect.DeepEqual(t.Spec.Template.Spec, old.Spec.Template.Spec) { 113 // The equality failure could be because of default mismatch between v1alpha3 and v1beta1. This happens because 114 // the new object `r` will have run through the default webhooks but the old object `old` would not have so. 115 // This means if the old object was in v1alpha3, it would not get the new defaults set in v1beta1 resulting 116 // in object inequality. To workaround this, we set the v1beta1 defaults here so that the old object also gets 117 // the new defaults. 118 119 // We need to set ssh key explicitly, otherwise Default() will create a new one. 120 if old.Spec.Template.Spec.SSHPublicKey == "" { 121 old.Spec.Template.Spec.SSHPublicKey = t.Spec.Template.Spec.SSHPublicKey 122 } 123 124 if err := r.Default(ctx, old); err != nil { 125 allErrs = append(allErrs, 126 field.Invalid(field.NewPath("AzureMachineTemplate"), r, fmt.Sprintf("Unable to apply defaults: %v", err)), 127 ) 128 } 129 130 // if it's still not equal, return error. 131 if !reflect.DeepEqual(t.Spec.Template.Spec, old.Spec.Template.Spec) { 132 allErrs = append(allErrs, 133 field.Invalid(field.NewPath("AzureMachineTemplate", "spec", "template", "spec"), t, AzureMachineTemplateImmutableMsg), 134 ) 135 } 136 } 137 138 if len(allErrs) == 0 { 139 return nil, nil 140 } 141 return nil, apierrors.NewInvalid(GroupVersion.WithKind(AzureMachineTemplateKind).GroupKind(), t.Name, allErrs) 142 } 143 144 // ValidateDelete implements webhook.Validator so a webhook will be registered for the type. 145 func (r *AzureMachineTemplate) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { 146 return nil, nil 147 } 148 149 // Default implements webhookutil.defaulter so a webhook will be registered for the type. 150 func (r *AzureMachineTemplate) Default(ctx context.Context, obj runtime.Object) error { 151 t := obj.(*AzureMachineTemplate) 152 if err := t.Spec.Template.Spec.SetDefaultSSHPublicKey(); err != nil { 153 ctrl.Log.WithName("SetDefault").Error(err, "SetDefaultSSHPublicKey failed") 154 } 155 t.Spec.Template.Spec.SetDefaultCachingType() 156 t.Spec.Template.Spec.SetDataDisksDefaults() 157 t.Spec.Template.Spec.SetNetworkInterfacesDefaults() 158 return nil 159 }