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 }