diff --git a/Makefile b/Makefile index 6371c2f0cee..1312a0d3786 100644 --- a/Makefile +++ b/Makefile @@ -538,6 +538,7 @@ generate-manifests: $(CONTROLLER_GEN) ## Generate manifests e.g. CRD, RBAC etc. paths=./api/... \ paths=./$(EXP_DIR)/api/... \ paths=./internal/webhooks/... \ + paths=./internal/exp/webhooks/... \ crd:crdVersions=v1 \ rbac:roleName=base-manager-role \ output:crd:dir=$(CRD_ROOT) \ diff --git a/exp/api/v1beta1/azuremachinepool_test.go b/exp/api/v1beta1/azuremachinepool_test.go index 12bed0d3018..3e8e849cd4c 100644 --- a/exp/api/v1beta1/azuremachinepool_test.go +++ b/exp/api/v1beta1/azuremachinepool_test.go @@ -24,6 +24,7 @@ import ( infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1" + expwebhooks "sigs.k8s.io/cluster-api-provider-azure/internal/exp/webhooks" ) func TestAzureMachinePool_Validate(t *testing.T) { @@ -249,7 +250,7 @@ func TestAzureMachinePool_Validate(t *testing.T) { // Don't add t.Parallel() here or the test will fail. g := gomega.NewGomegaWithT(t) amp := c.Factory(g) - actualErr := amp.Validate(nil, nil) + actualErr := expwebhooks.ValidateAzureMachinePool(nil, amp, nil) c.Expect(g, actualErr) }) } diff --git a/exp/api/v1beta1/zz_generated.deepcopy.go b/exp/api/v1beta1/zz_generated.deepcopy.go index 2083b59c5b5..6c0af75a80f 100644 --- a/exp/api/v1beta1/zz_generated.deepcopy.go +++ b/exp/api/v1beta1/zz_generated.deepcopy.go @@ -22,7 +22,7 @@ package v1beta1 import ( "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" apiv1beta1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" corev1beta1 "sigs.k8s.io/cluster-api/api/core/v1beta1" diff --git a/exp/controllers/azuremachinepool_reconciler.go b/exp/controllers/azuremachinepool_reconciler.go index 8529eed9829..3a6680700b6 100644 --- a/exp/controllers/azuremachinepool_reconciler.go +++ b/exp/controllers/azuremachinepool_reconciler.go @@ -27,6 +27,7 @@ import ( "sigs.k8s.io/cluster-api-provider-azure/azure/services/roleassignments" "sigs.k8s.io/cluster-api-provider-azure/azure/services/scalesets" "sigs.k8s.io/cluster-api-provider-azure/azure/services/tags" + apiinternalexp "sigs.k8s.io/cluster-api-provider-azure/internal/exp/api/v1beta1" "sigs.k8s.io/cluster-api-provider-azure/util/tele" ) @@ -73,7 +74,7 @@ func (s *azureMachinePoolService) Reconcile(ctx context.Context) error { defer done() // Ensure that the deprecated networking field values have been migrated to the new NetworkInterfaces field. - s.scope.AzureMachinePool.SetNetworkInterfacesDefaults() + apiinternalexp.SetNetworkInterfacesDefaults(s.scope.AzureMachinePool) if err := s.scope.SetSubnetName(); err != nil { return errors.Wrap(err, "failed defaulting subnet name") diff --git a/exp/api/v1beta1/azuremachinepool_default.go b/internal/exp/api/v1beta1/azuremachinepool_default.go similarity index 78% rename from exp/api/v1beta1/azuremachinepool_default.go rename to internal/exp/api/v1beta1/azuremachinepool_default.go index 10c931518f1..6a510ba1bd5 100644 --- a/exp/api/v1beta1/azuremachinepool_default.go +++ b/internal/exp/api/v1beta1/azuremachinepool_default.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -27,30 +27,31 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" + infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1" apiinternal "sigs.k8s.io/cluster-api-provider-azure/internal/api/v1beta1" azureutil "sigs.k8s.io/cluster-api-provider-azure/util/azure" utilSSH "sigs.k8s.io/cluster-api-provider-azure/util/ssh" ) // SetDefaults sets the default values for an AzureMachinePool. -func (amp *AzureMachinePool) SetDefaults(client client.Client) error { +func SetDefaults(amp *infrav1exp.AzureMachinePool, c client.Client) error { var errs []error - if err := amp.SetDefaultSSHPublicKey(); err != nil { + if err := SetDefaultSSHPublicKey(amp); err != nil { errs = append(errs, errors.Wrap(err, "failed to set default SSH public key")) } - if err := amp.SetIdentityDefaults(client); err != nil { + if err := SetIdentityDefaults(amp, c); err != nil { errs = append(errs, errors.Wrap(err, "failed to set default managed identity defaults")) } - amp.SetDiagnosticsDefaults() - amp.SetNetworkInterfacesDefaults() - amp.SetOSDiskDefaults() + SetDiagnosticsDefaults(amp) + SetNetworkInterfacesDefaults(amp) + SetOSDiskDefaults(amp) return kerrors.NewAggregate(errs) } // SetDefaultSSHPublicKey sets the default SSHPublicKey for an AzureMachinePool. -func (amp *AzureMachinePool) SetDefaultSSHPublicKey() error { +func SetDefaultSSHPublicKey(amp *infrav1exp.AzureMachinePool) error { if sshKeyData := amp.Spec.Template.SSHPublicKey; sshKeyData == "" { _, publicRsaKey, err := utilSSH.GenerateSSHKey() if err != nil { @@ -63,25 +64,25 @@ func (amp *AzureMachinePool) SetDefaultSSHPublicKey() error { } // SetIdentityDefaults sets the defaults for VMSS Identity. -func (amp *AzureMachinePool) SetIdentityDefaults(client client.Client) error { +func SetIdentityDefaults(amp *infrav1exp.AzureMachinePool, c client.Client) error { // Ensure the deprecated fields and new fields are not populated simultaneously - if amp.Spec.RoleAssignmentName != "" && amp.Spec.SystemAssignedIdentityRole != nil && amp.Spec.SystemAssignedIdentityRole.Name != "" { + if amp.Spec.RoleAssignmentName != "" && amp.Spec.SystemAssignedIdentityRole != nil && amp.Spec.SystemAssignedIdentityRole.Name != "" { //nolint:staticcheck // Both the deprecated and the new fields are both set, return without changes // and reject the request in the validating webhook which runs later. return nil } if amp.Spec.Identity == infrav1.VMIdentitySystemAssigned { - machinePool, err := azureutil.FindParentMachinePoolWithRetryV1Beta1(amp.Name, client, 5) + machinePool, err := azureutil.FindParentMachinePoolWithRetryV1Beta1(amp.Name, c, 5) if err != nil { return errors.Wrap(err, "failed to find parent machine pool") } - ownerAzureClusterName, ownerAzureClusterNamespace, err := apiinternal.GetOwnerAzureClusterNameAndNamespace(client, machinePool.Spec.ClusterName, machinePool.Namespace, 5) + ownerAzureClusterName, ownerAzureClusterNamespace, err := apiinternal.GetOwnerAzureClusterNameAndNamespace(c, machinePool.Spec.ClusterName, machinePool.Namespace, 5) if err != nil { return errors.Wrap(err, "failed to get owner cluster") } - subscriptionID, err := apiinternal.GetSubscriptionID(client, ownerAzureClusterName, ownerAzureClusterNamespace, 5) + subscriptionID, err := apiinternal.GetSubscriptionID(c, ownerAzureClusterName, ownerAzureClusterNamespace, 5) if err != nil { return errors.Wrap(err, "failed to get subscription ID") } @@ -89,9 +90,9 @@ func (amp *AzureMachinePool) SetIdentityDefaults(client client.Client) error { if amp.Spec.SystemAssignedIdentityRole == nil { amp.Spec.SystemAssignedIdentityRole = &infrav1.SystemAssignedIdentityRole{} } - if amp.Spec.RoleAssignmentName != "" { - amp.Spec.SystemAssignedIdentityRole.Name = amp.Spec.RoleAssignmentName - amp.Spec.RoleAssignmentName = "" + if amp.Spec.RoleAssignmentName != "" { //nolint:staticcheck + amp.Spec.SystemAssignedIdentityRole.Name = amp.Spec.RoleAssignmentName //nolint:staticcheck + amp.Spec.RoleAssignmentName = "" //nolint:staticcheck } else if amp.Spec.SystemAssignedIdentityRole.Name == "" { amp.Spec.SystemAssignedIdentityRole.Name = string(uuid.NewUUID()) } @@ -108,7 +109,7 @@ func (amp *AzureMachinePool) SetIdentityDefaults(client client.Client) error { } // SetSpotEvictionPolicyDefaults sets the defaults for the spot VM eviction policy. -func (amp *AzureMachinePool) SetSpotEvictionPolicyDefaults() { +func SetSpotEvictionPolicyDefaults(amp *infrav1exp.AzureMachinePool) { if amp.Spec.Template.SpotVMOptions != nil && amp.Spec.Template.SpotVMOptions.EvictionPolicy == nil { defaultPolicy := infrav1.SpotEvictionPolicyDeallocate if amp.Spec.Template.OSDisk.DiffDiskSettings != nil && amp.Spec.Template.OSDisk.DiffDiskSettings.Option == "Local" { @@ -119,7 +120,7 @@ func (amp *AzureMachinePool) SetSpotEvictionPolicyDefaults() { } // SetDiagnosticsDefaults sets the defaults for Diagnostic settings for an AzureMachinePool. -func (amp *AzureMachinePool) SetDiagnosticsDefaults() { +func SetDiagnosticsDefaults(amp *infrav1exp.AzureMachinePool) { bootDefault := &infrav1.BootDiagnostics{ StorageAccountType: infrav1.ManagedDiagnosticsStorage, } @@ -136,9 +137,9 @@ func (amp *AzureMachinePool) SetDiagnosticsDefaults() { } // SetNetworkInterfacesDefaults sets the defaults for the network interfaces. -func (amp *AzureMachinePool) SetNetworkInterfacesDefaults() { +func SetNetworkInterfacesDefaults(amp *infrav1exp.AzureMachinePool) { // Ensure the deprecated fields and new fields are not populated simultaneously - if (amp.Spec.Template.SubnetName != "" || amp.Spec.Template.AcceleratedNetworking != nil) && len(amp.Spec.Template.NetworkInterfaces) > 0 { + if (amp.Spec.Template.SubnetName != "" || amp.Spec.Template.AcceleratedNetworking != nil) && len(amp.Spec.Template.NetworkInterfaces) > 0 { //nolint:staticcheck // Both the deprecated and the new fields are both set, return without changes // and reject the request in the validating webhook which runs later. return @@ -147,12 +148,12 @@ func (amp *AzureMachinePool) SetNetworkInterfacesDefaults() { if len(amp.Spec.Template.NetworkInterfaces) == 0 { amp.Spec.Template.NetworkInterfaces = []infrav1.NetworkInterface{ { - SubnetName: amp.Spec.Template.SubnetName, - AcceleratedNetworking: amp.Spec.Template.AcceleratedNetworking, + SubnetName: amp.Spec.Template.SubnetName, //nolint:staticcheck + AcceleratedNetworking: amp.Spec.Template.AcceleratedNetworking, //nolint:staticcheck }, } - amp.Spec.Template.SubnetName = "" - amp.Spec.Template.AcceleratedNetworking = nil + amp.Spec.Template.SubnetName = "" //nolint:staticcheck + amp.Spec.Template.AcceleratedNetworking = nil //nolint:staticcheck } // Ensure that PrivateIPConfigs defaults to 1 if not specified. @@ -164,7 +165,7 @@ func (amp *AzureMachinePool) SetNetworkInterfacesDefaults() { } // SetOSDiskDefaults sets the defaults for the OSDisk. -func (amp *AzureMachinePool) SetOSDiskDefaults() { +func SetOSDiskDefaults(amp *infrav1exp.AzureMachinePool) { if amp.Spec.Template.OSDisk.OSType == "" { amp.Spec.Template.OSDisk.OSType = "Linux" } diff --git a/exp/api/v1beta1/azuremachinepool_default_test.go b/internal/exp/api/v1beta1/azuremachinepool_default_test.go similarity index 72% rename from exp/api/v1beta1/azuremachinepool_default_test.go rename to internal/exp/api/v1beta1/azuremachinepool_default_test.go index 1fcdd895eee..9ade88c2773 100644 --- a/exp/api/v1beta1/azuremachinepool_default_test.go +++ b/internal/exp/api/v1beta1/azuremachinepool_default_test.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" + infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1" apiinternal "sigs.k8s.io/cluster-api-provider-azure/internal/api/v1beta1" ) @@ -36,19 +37,17 @@ func TestAzureMachinePool_SetDefaultSSHPublicKey(t *testing.T) { g := NewWithT(t) type test struct { - amp *AzureMachinePool + amp *infrav1exp.AzureMachinePool } existingPublicKey := "testpublickey" publicKeyExistTest := test{amp: createMachinePoolWithSSHPublicKey(existingPublicKey)} publicKeyNotExistTest := test{amp: createMachinePoolWithSSHPublicKey("")} - err := publicKeyExistTest.amp.SetDefaultSSHPublicKey() - g.Expect(err).NotTo(HaveOccurred()) + g.Expect(SetDefaultSSHPublicKey(publicKeyExistTest.amp)).To(Succeed()) g.Expect(publicKeyExistTest.amp.Spec.Template.SSHPublicKey).To(Equal(existingPublicKey)) - err = publicKeyNotExistTest.amp.SetDefaultSSHPublicKey() - g.Expect(err).NotTo(HaveOccurred()) + g.Expect(SetDefaultSSHPublicKey(publicKeyNotExistTest.amp)).To(Succeed()) g.Expect(publicKeyNotExistTest.amp.Spec.Template.SSHPublicKey).NotTo(BeEmpty()) } @@ -61,14 +60,14 @@ func TestAzureMachinePool_SetIdentityDefaults(t *testing.T) { tests := []struct { name string - machinePool *AzureMachinePool + machinePool *infrav1exp.AzureMachinePool wantErr bool expectedRoleAssignmentName string expectedSystemAssignedIdentityRole *infrav1.SystemAssignedIdentityRole }{ { name: "bothRoleAssignmentNamesPopulated", - machinePool: &AzureMachinePool{Spec: AzureMachinePoolSpec{ + machinePool: &infrav1exp.AzureMachinePool{Spec: infrav1exp.AzureMachinePoolSpec{ Identity: infrav1.VMIdentitySystemAssigned, RoleAssignmentName: existingRoleAssignmentName, SystemAssignedIdentityRole: &infrav1.SystemAssignedIdentityRole{ @@ -82,7 +81,7 @@ func TestAzureMachinePool_SetIdentityDefaults(t *testing.T) { }, { name: "roleAssignmentExist", - machinePool: &AzureMachinePool{Spec: AzureMachinePoolSpec{ + machinePool: &infrav1exp.AzureMachinePool{Spec: infrav1exp.AzureMachinePoolSpec{ Identity: infrav1.VMIdentitySystemAssigned, SystemAssignedIdentityRole: &infrav1.SystemAssignedIdentityRole{ Name: existingRoleAssignmentName, @@ -96,14 +95,14 @@ func TestAzureMachinePool_SetIdentityDefaults(t *testing.T) { }, { name: "notSystemAssigned", - machinePool: &AzureMachinePool{Spec: AzureMachinePoolSpec{ + machinePool: &infrav1exp.AzureMachinePool{Spec: infrav1exp.AzureMachinePoolSpec{ Identity: infrav1.VMIdentityUserAssigned, }}, expectedSystemAssignedIdentityRole: nil, }, { name: "systemAssignedIdentityRoleExist", - machinePool: &AzureMachinePool{Spec: AzureMachinePoolSpec{ + machinePool: &infrav1exp.AzureMachinePool{Spec: infrav1exp.AzureMachinePoolSpec{ Identity: infrav1.VMIdentitySystemAssigned, SystemAssignedIdentityRole: &infrav1.SystemAssignedIdentityRole{ Name: existingRoleAssignmentName, @@ -119,7 +118,7 @@ func TestAzureMachinePool_SetIdentityDefaults(t *testing.T) { }, { name: "deprecatedRoleAssignmentName", - machinePool: &AzureMachinePool{Spec: AzureMachinePoolSpec{ + machinePool: &infrav1exp.AzureMachinePool{Spec: infrav1exp.AzureMachinePoolSpec{ Identity: infrav1.VMIdentitySystemAssigned, RoleAssignmentName: existingRoleAssignmentName, }}, @@ -136,7 +135,7 @@ func TestAzureMachinePool_SetIdentityDefaults(t *testing.T) { g := NewWithT(t) scheme := runtime.NewScheme() - _ = AddToScheme(scheme) + _ = infrav1exp.AddToScheme(scheme) _ = infrav1.AddToScheme(scheme) _ = clusterv1.AddToScheme(scheme) @@ -176,12 +175,12 @@ func TestAzureMachinePool_SetIdentityDefaults(t *testing.T) { } fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(tc.machinePool, machinePool, azureCluster, cluster).Build() - err := tc.machinePool.SetIdentityDefaults(fakeClient) + err := SetIdentityDefaults(tc.machinePool, fakeClient) if tc.wantErr { g.Expect(err).To(HaveOccurred()) } else { g.Expect(err).NotTo(HaveOccurred()) - g.Expect(tc.machinePool.Spec.RoleAssignmentName).To(Equal(tc.expectedRoleAssignmentName)) + g.Expect(tc.machinePool.Spec.RoleAssignmentName).To(Equal(tc.expectedRoleAssignmentName)) //nolint:staticcheck g.Expect(tc.machinePool.Spec.SystemAssignedIdentityRole).To(Equal(tc.expectedSystemAssignedIdentityRole)) } }) @@ -192,16 +191,16 @@ func TestAzureMachinePool_SetDiagnosticsDefaults(t *testing.T) { g := NewWithT(t) type test struct { - machinePool *AzureMachinePool + machinePool *infrav1exp.AzureMachinePool } bootDiagnosticsDefault := &infrav1.BootDiagnostics{ StorageAccountType: infrav1.ManagedDiagnosticsStorage, } - managedStorageDiagnostics := test{machinePool: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + managedStorageDiagnostics := test{machinePool: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ Diagnostics: &infrav1.Diagnostics{ Boot: &infrav1.BootDiagnostics{ StorageAccountType: infrav1.ManagedDiagnosticsStorage, @@ -211,9 +210,9 @@ func TestAzureMachinePool_SetDiagnosticsDefaults(t *testing.T) { }, }} - disabledStorageDiagnostics := test{machinePool: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + disabledStorageDiagnostics := test{machinePool: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ Diagnostics: &infrav1.Diagnostics{ Boot: &infrav1.BootDiagnostics{ StorageAccountType: infrav1.DisabledDiagnosticsStorage, @@ -223,9 +222,9 @@ func TestAzureMachinePool_SetDiagnosticsDefaults(t *testing.T) { }, }} - userManagedDiagnostics := test{machinePool: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + userManagedDiagnostics := test{machinePool: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ Diagnostics: &infrav1.Diagnostics{ Boot: &infrav1.BootDiagnostics{ StorageAccountType: infrav1.UserManagedDiagnosticsStorage, @@ -238,36 +237,36 @@ func TestAzureMachinePool_SetDiagnosticsDefaults(t *testing.T) { }, }} - nilDiagnostics := test{machinePool: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + nilDiagnostics := test{machinePool: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ Diagnostics: nil, }, }, }} // Test that when no diagnostics are specified, the defaults are set correctly - nilBootDiagnostics := test{machinePool: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + nilBootDiagnostics := test{machinePool: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ Diagnostics: &infrav1.Diagnostics{}, }, }, }} - nilBootDiagnostics.machinePool.SetDiagnosticsDefaults() + SetDiagnosticsDefaults(nilBootDiagnostics.machinePool) g.Expect(nilBootDiagnostics.machinePool.Spec.Template.Diagnostics.Boot).To(Equal(bootDiagnosticsDefault)) - managedStorageDiagnostics.machinePool.SetDiagnosticsDefaults() + SetDiagnosticsDefaults(managedStorageDiagnostics.machinePool) g.Expect(managedStorageDiagnostics.machinePool.Spec.Template.Diagnostics.Boot.StorageAccountType).To(Equal(infrav1.ManagedDiagnosticsStorage)) - disabledStorageDiagnostics.machinePool.SetDiagnosticsDefaults() + SetDiagnosticsDefaults(disabledStorageDiagnostics.machinePool) g.Expect(disabledStorageDiagnostics.machinePool.Spec.Template.Diagnostics.Boot.StorageAccountType).To(Equal(infrav1.DisabledDiagnosticsStorage)) - userManagedDiagnostics.machinePool.SetDiagnosticsDefaults() + SetDiagnosticsDefaults(userManagedDiagnostics.machinePool) g.Expect(userManagedDiagnostics.machinePool.Spec.Template.Diagnostics.Boot.StorageAccountType).To(Equal(infrav1.UserManagedDiagnosticsStorage)) - nilDiagnostics.machinePool.SetDiagnosticsDefaults() + SetDiagnosticsDefaults(nilDiagnostics.machinePool) g.Expect(nilDiagnostics.machinePool.Spec.Template.Diagnostics.Boot.StorageAccountType).To(Equal(infrav1.ManagedDiagnosticsStorage)) } @@ -275,28 +274,28 @@ func TestAzureMachinePool_SetSpotEvictionPolicyDefaults(t *testing.T) { g := NewWithT(t) type test struct { - machinePool *AzureMachinePool + machinePool *infrav1exp.AzureMachinePool } // test to Ensure the default policy is set to Deallocate if EvictionPolicy is nil defaultEvictionPolicy := infrav1.SpotEvictionPolicyDeallocate - nilDiffDiskSettingsPolicy := test{machinePool: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + nilDiffDiskSettingsPolicy := test{machinePool: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ SpotVMOptions: &infrav1.SpotVMOptions{ EvictionPolicy: nil, }, }, }, }} - nilDiffDiskSettingsPolicy.machinePool.SetSpotEvictionPolicyDefaults() + SetSpotEvictionPolicyDefaults(nilDiffDiskSettingsPolicy.machinePool) g.Expect(nilDiffDiskSettingsPolicy.machinePool.Spec.Template.SpotVMOptions.EvictionPolicy).To(Equal(&defaultEvictionPolicy)) // test to Ensure the default policy is set to Delete if diffDiskSettings option is set to "Local" expectedEvictionPolicy := infrav1.SpotEvictionPolicyDelete - diffDiskSettingsPolicy := test{machinePool: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + diffDiskSettingsPolicy := test{machinePool: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ SpotVMOptions: &infrav1.SpotVMOptions{}, OSDisk: infrav1.OSDisk{ DiffDiskSettings: &infrav1.DiffDiskSettings{ @@ -306,28 +305,28 @@ func TestAzureMachinePool_SetSpotEvictionPolicyDefaults(t *testing.T) { }, }, }} - diffDiskSettingsPolicy.machinePool.SetSpotEvictionPolicyDefaults() + SetSpotEvictionPolicyDefaults(diffDiskSettingsPolicy.machinePool) g.Expect(diffDiskSettingsPolicy.machinePool.Spec.Template.SpotVMOptions.EvictionPolicy).To(Equal(&expectedEvictionPolicy)) } func TestAzureMachinePool_SetNetworkInterfacesDefaults(t *testing.T) { testCases := []struct { name string - machinePool *AzureMachinePool - want *AzureMachinePool + machinePool *infrav1exp.AzureMachinePool + want *infrav1exp.AzureMachinePool }{ { name: "defaulting webhook updates MachinePool with deprecated subnetName field", - machinePool: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + machinePool: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ SubnetName: "test-subnet", }, }, }, - want: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + want: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ SubnetName: "", NetworkInterfaces: []infrav1.NetworkInterface{ { @@ -341,17 +340,17 @@ func TestAzureMachinePool_SetNetworkInterfacesDefaults(t *testing.T) { }, { name: "defaulting webhook updates MachinePool with deprecated acceleratedNetworking field", - machinePool: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + machinePool: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ SubnetName: "test-subnet", AcceleratedNetworking: ptr.To(true), }, }, }, - want: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + want: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ SubnetName: "", AcceleratedNetworking: nil, NetworkInterfaces: []infrav1.NetworkInterface{ @@ -367,9 +366,9 @@ func TestAzureMachinePool_SetNetworkInterfacesDefaults(t *testing.T) { }, { name: "defaulting webhook does nothing if both new and deprecated subnetName fields are set", - machinePool: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + machinePool: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ SubnetName: "test-subnet", NetworkInterfaces: []infrav1.NetworkInterface{{ SubnetName: "test-subnet", @@ -377,9 +376,9 @@ func TestAzureMachinePool_SetNetworkInterfacesDefaults(t *testing.T) { }, }, }, - want: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + want: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ SubnetName: "test-subnet", AcceleratedNetworking: nil, NetworkInterfaces: []infrav1.NetworkInterface{ @@ -396,20 +395,16 @@ func TestAzureMachinePool_SetNetworkInterfacesDefaults(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { g := NewWithT(t) - tc.machinePool.SetNetworkInterfacesDefaults() + SetNetworkInterfacesDefaults(tc.machinePool) g.Expect(tc.machinePool).To(Equal(tc.want)) }) } } -func createMachinePoolWithSSHPublicKey(sshPublicKey string) *AzureMachinePool { - return hardcodedAzureMachinePoolWithSSHKey(sshPublicKey) -} - -func hardcodedAzureMachinePoolWithSSHKey(sshPublicKey string) *AzureMachinePool { - return &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ +func createMachinePoolWithSSHPublicKey(sshPublicKey string) *infrav1exp.AzureMachinePool { + return &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ SSHPublicKey: sshPublicKey, OSDisk: infrav1.OSDisk{ CachingType: "None", diff --git a/exp/api/v1beta1/azuremachinepool_webhook.go b/internal/exp/webhooks/azuremachinepool_webhook.go similarity index 73% rename from exp/api/v1beta1/azuremachinepool_webhook.go rename to internal/exp/webhooks/azuremachinepool_webhook.go index f444ff09372..edb076dcb94 100644 --- a/exp/api/v1beta1/azuremachinepool_webhook.go +++ b/internal/exp/webhooks/azuremachinepool_webhook.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1beta1 +package webhooks import ( "context" @@ -33,62 +33,63 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" + infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1" + apiinternalexp "sigs.k8s.io/cluster-api-provider-azure/internal/exp/api/v1beta1" "sigs.k8s.io/cluster-api-provider-azure/internal/webhooks" azureutil "sigs.k8s.io/cluster-api-provider-azure/util/azure" ) -// SetupAzureMachinePoolWebhookWithManager sets up and registers the webhook with the manager. -func SetupAzureMachinePoolWebhookWithManager(mgr ctrl.Manager) error { - ampw := &azureMachinePoolWebhook{Client: mgr.GetClient()} - return ctrl.NewWebhookManagedBy(mgr, &AzureMachinePool{}). - WithDefaulter(ampw). - WithValidator(ampw). +// SetupWebhookWithManager sets up and registers the webhook with the manager. +func (mw *AzureMachinePoolWebhook) SetupWebhookWithManager(mgr ctrl.Manager) error { + mw.Client = mgr.GetClient() + return ctrl.NewWebhookManagedBy(mgr, &infrav1exp.AzureMachinePool{}). + WithDefaulter(mw). + WithValidator(mw). Complete() } // +kubebuilder:webhook:path=/mutate-infrastructure-cluster-x-k8s-io-v1beta1-azuremachinepool,mutating=true,failurePolicy=fail,groups=infrastructure.cluster.x-k8s.io,resources=azuremachinepools,verbs=create;update,versions=v1beta1,name=default.azuremachinepool.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 +// +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-azuremachinepool,mutating=false,failurePolicy=fail,groups=infrastructure.cluster.x-k8s.io,resources=azuremachinepools,versions=v1beta1,name=validation.azuremachinepool.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 -// azureMachinePoolWebhook implements a validating and defaulting webhook for AzureMachinePool. -type azureMachinePoolWebhook struct { +// AzureMachinePoolWebhook implements a validating and defaulting webhook for AzureMachinePool. +type AzureMachinePoolWebhook struct { Client client.Client } // Default implements webhook.Defaulter so a webhook will be registered for the type. -func (ampw *azureMachinePoolWebhook) Default(_ context.Context, amp *AzureMachinePool) error { - return amp.SetDefaults(ampw.Client) +func (mw *AzureMachinePoolWebhook) Default(_ context.Context, amp *infrav1exp.AzureMachinePool) error { + return apiinternalexp.SetDefaults(amp, mw.Client) } -// +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-azuremachinepool,mutating=false,failurePolicy=fail,groups=infrastructure.cluster.x-k8s.io,resources=azuremachinepools,versions=v1beta1,name=validation.azuremachinepool.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 - // ValidateCreate implements webhook.Validator so a webhook will be registered for the type. -func (ampw *azureMachinePoolWebhook) ValidateCreate(_ context.Context, amp *AzureMachinePool) (admission.Warnings, error) { - return nil, amp.Validate(nil, ampw.Client) +func (mw *AzureMachinePoolWebhook) ValidateCreate(_ context.Context, amp *infrav1exp.AzureMachinePool) (admission.Warnings, error) { + return nil, ValidateAzureMachinePool(nil, amp, mw.Client) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. -func (ampw *azureMachinePoolWebhook) ValidateUpdate(_ context.Context, oldObj, amp *AzureMachinePool) (admission.Warnings, error) { - return nil, amp.Validate(oldObj, ampw.Client) +func (mw *AzureMachinePoolWebhook) ValidateUpdate(_ context.Context, oldObj, amp *infrav1exp.AzureMachinePool) (admission.Warnings, error) { + return nil, ValidateAzureMachinePool(oldObj, amp, mw.Client) } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type. -func (ampw *azureMachinePoolWebhook) ValidateDelete(_ context.Context, _ *AzureMachinePool) (admission.Warnings, error) { +func (mw *AzureMachinePoolWebhook) ValidateDelete(_ context.Context, _ *infrav1exp.AzureMachinePool) (admission.Warnings, error) { return nil, nil } -// Validate the Azure Machine Pool and return an aggregate error. -func (amp *AzureMachinePool) Validate(old runtime.Object, client client.Client) error { +// ValidateAzureMachinePool runs the Azure Machine Pool validators and returns an aggregate error. +func ValidateAzureMachinePool(old runtime.Object, amp *infrav1exp.AzureMachinePool, c client.Client) error { validators := []func() error{ - amp.ValidateImage, - amp.ValidateTerminateNotificationTimeout, - amp.ValidateSSHKey, - amp.ValidateUserAssignedIdentity, - amp.ValidateDiagnostics, - amp.ValidateOrchestrationMode(client), - amp.ValidateStrategy(), - amp.ValidateSystemAssignedIdentity(old), - amp.ValidateSystemAssignedIdentityRole, - amp.ValidateNetwork, - amp.ValidateOSDisk, + func() error { return validateImage(amp) }, + func() error { return validateTerminateNotificationTimeout(amp) }, + func() error { return validateSSHKey(amp) }, + func() error { return validateUserAssignedIdentity(amp) }, + func() error { return validateDiagnostics(amp) }, + validateOrchestrationMode(amp, c), + validateStrategy(amp), + validateSystemAssignedIdentity(amp, old), + func() error { return validateSystemAssignedIdentityRole(amp) }, + func() error { return validateNetwork(amp) }, + func() error { return validateOSDisk(amp) }, } var errs []error @@ -101,36 +102,31 @@ func (amp *AzureMachinePool) Validate(old runtime.Object, client client.Client) return kerrors.NewAggregate(errs) } -// ValidateNetwork of an AzureMachinePool. -func (amp *AzureMachinePool) ValidateNetwork() error { - if (amp.Spec.Template.NetworkInterfaces != nil) && len(amp.Spec.Template.NetworkInterfaces) > 0 && amp.Spec.Template.SubnetName != "" { +func validateNetwork(amp *infrav1exp.AzureMachinePool) error { + if (amp.Spec.Template.NetworkInterfaces != nil) && len(amp.Spec.Template.NetworkInterfaces) > 0 && amp.Spec.Template.SubnetName != "" { //nolint:staticcheck return errors.New("cannot set both NetworkInterfaces and machine SubnetName") } return nil } -// ValidateOSDisk of an AzureMachinePool. -func (amp *AzureMachinePool) ValidateOSDisk() error { +func validateOSDisk(amp *infrav1exp.AzureMachinePool) error { if errs := webhooks.ValidateOSDisk(amp.Spec.Template.OSDisk, field.NewPath("osDisk")); len(errs) > 0 { return errs.ToAggregate() } return nil } -// ValidateImage of an AzureMachinePool. -func (amp *AzureMachinePool) ValidateImage() error { +func validateImage(amp *infrav1exp.AzureMachinePool) error { if amp.Spec.Template.Image != nil { image := amp.Spec.Template.Image if errs := webhooks.ValidateImage(image, field.NewPath("image")); len(errs) > 0 { return errs.ToAggregate() } } - return nil } -// ValidateTerminateNotificationTimeout termination notification timeout to be between 5 and 15. -func (amp *AzureMachinePool) ValidateTerminateNotificationTimeout() error { +func validateTerminateNotificationTimeout(amp *infrav1exp.AzureMachinePool) error { if amp.Spec.Template.TerminateNotificationTimeout == nil { return nil } @@ -145,21 +141,18 @@ func (amp *AzureMachinePool) ValidateTerminateNotificationTimeout() error { return nil } -// ValidateSSHKey validates an SSHKey. -func (amp *AzureMachinePool) ValidateSSHKey() error { +func validateSSHKey(amp *infrav1exp.AzureMachinePool) error { if amp.Spec.Template.SSHPublicKey != "" { sshKey := amp.Spec.Template.SSHPublicKey if errs := webhooks.ValidateSSHKey(sshKey, field.NewPath("sshKey")); len(errs) > 0 { - agg := kerrors.NewAggregate(errs.ToAggregate().Errors()) - return agg + return kerrors.NewAggregate(errs.ToAggregate().Errors()) } } return nil } -// ValidateUserAssignedIdentity validates the user-assigned identities list. -func (amp *AzureMachinePool) ValidateUserAssignedIdentity() error { +func validateUserAssignedIdentity(amp *infrav1exp.AzureMachinePool) error { fldPath := field.NewPath("userAssignedIdentities") if errs := webhooks.ValidateUserAssignedIdentity(amp.Spec.Identity, amp.Spec.UserAssignedIdentities, fldPath); len(errs) > 0 { return kerrors.NewAggregate(errs.ToAggregate().Errors()) @@ -168,10 +161,9 @@ func (amp *AzureMachinePool) ValidateUserAssignedIdentity() error { return nil } -// ValidateStrategy validates the strategy. -func (amp *AzureMachinePool) ValidateStrategy() func() error { +func validateStrategy(amp *infrav1exp.AzureMachinePool) func() error { return func() error { - if amp.Spec.Strategy.Type == RollingUpdateAzureMachinePoolDeploymentStrategyType && amp.Spec.Strategy.RollingUpdate != nil { + if amp.Spec.Strategy.Type == infrav1exp.RollingUpdateAzureMachinePoolDeploymentStrategyType && amp.Spec.Strategy.RollingUpdate != nil { rollingUpdateStrategy := amp.Spec.Strategy.RollingUpdate maxSurge := rollingUpdateStrategy.MaxSurge maxUnavailable := rollingUpdateStrategy.MaxUnavailable @@ -185,12 +177,11 @@ func (amp *AzureMachinePool) ValidateStrategy() func() error { } } -// ValidateSystemAssignedIdentity validates system-assigned identity role. -func (amp *AzureMachinePool) ValidateSystemAssignedIdentity(old runtime.Object) func() error { +func validateSystemAssignedIdentity(amp *infrav1exp.AzureMachinePool, old runtime.Object) func() error { return func() error { var oldRole string if old != nil { - oldMachinePool, ok := old.(*AzureMachinePool) + oldMachinePool, ok := old.(*infrav1exp.AzureMachinePool) if !ok { return fmt.Errorf("unexpected type for old azure machine pool object. Expected: %q, Got: %q", "AzureMachinePool", reflect.TypeOf(old)) @@ -214,10 +205,9 @@ func (amp *AzureMachinePool) ValidateSystemAssignedIdentity(old runtime.Object) } } -// ValidateSystemAssignedIdentityRole validates the scope and roleDefinitionID for the system-assigned identity. -func (amp *AzureMachinePool) ValidateSystemAssignedIdentityRole() error { +func validateSystemAssignedIdentityRole(amp *infrav1exp.AzureMachinePool) error { var allErrs field.ErrorList - if amp.Spec.RoleAssignmentName != "" && amp.Spec.SystemAssignedIdentityRole != nil && amp.Spec.SystemAssignedIdentityRole.Name != "" { + if amp.Spec.RoleAssignmentName != "" && amp.Spec.SystemAssignedIdentityRole != nil && amp.Spec.SystemAssignedIdentityRole.Name != "" { //nolint:staticcheck allErrs = append(allErrs, field.Invalid(field.NewPath("systemAssignedIdentityRole"), amp.Spec.SystemAssignedIdentityRole.Name, "cannot set both roleAssignmentName and systemAssignedIdentityRole.name")) } if amp.Spec.Identity == infrav1.VMIdentitySystemAssigned { @@ -239,8 +229,7 @@ func (amp *AzureMachinePool) ValidateSystemAssignedIdentityRole() error { return nil } -// ValidateDiagnostics validates the Diagnostic spec. -func (amp *AzureMachinePool) ValidateDiagnostics() error { +func validateDiagnostics(amp *infrav1exp.AzureMachinePool) error { var allErrs field.ErrorList fieldPath := field.NewPath("diagnostics") @@ -280,8 +269,7 @@ func (amp *AzureMachinePool) ValidateDiagnostics() error { return nil } -// ValidateOrchestrationMode validates requirements for the VMSS orchestration mode. -func (amp *AzureMachinePool) ValidateOrchestrationMode(c client.Client) func() error { +func validateOrchestrationMode(amp *infrav1exp.AzureMachinePool, c client.Client) func() error { return func() error { // Only Flexible orchestration mode requires validation. if amp.Spec.OrchestrationMode == infrav1.OrchestrationModeType(armcompute.OrchestrationModeFlexible) { diff --git a/exp/api/v1beta1/azuremachinepool_webhook_test.go b/internal/exp/webhooks/azuremachinepool_webhook_test.go similarity index 78% rename from exp/api/v1beta1/azuremachinepool_webhook_test.go rename to internal/exp/webhooks/azuremachinepool_webhook_test.go index 53dc0b2c720..4fa3c88e5c4 100644 --- a/exp/api/v1beta1/azuremachinepool_webhook_test.go +++ b/internal/exp/webhooks/azuremachinepool_webhook_test.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1beta1 +package webhooks import ( "context" @@ -35,6 +35,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" + infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1" "sigs.k8s.io/cluster-api-provider-azure/feature" apiinternal "sigs.k8s.io/cluster-api-provider-azure/internal/api/v1beta1" apifixtures "sigs.k8s.io/cluster-api-provider-azure/internal/test/apifixtures" @@ -52,12 +53,12 @@ type mockClient struct { ReturnError bool } -func (m mockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { +func (m mockClient) Get(_ context.Context, _ client.ObjectKey, obj client.Object, _ ...client.GetOption) error { obj.(*clusterv1.MachinePool).Spec.Template.Spec.Version = m.Version return nil } -func (m mockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { +func (m mockClient) List(_ context.Context, list client.ObjectList, _ ...client.ListOption) error { if m.ReturnError { return errors.New("MachinePool.cluster.x-k8s.io \"mock-machinepool-mp-0\" not found") } @@ -71,7 +72,7 @@ func (m mockClient) List(ctx context.Context, list client.ObjectList, opts ...cl func TestAzureMachinePool_ValidateCreate(t *testing.T) { tests := []struct { name string - amp *AzureMachinePool + amp *infrav1exp.AzureMachinePool version string ownerNotFound bool wantErr bool @@ -176,9 +177,9 @@ func TestAzureMachinePool_ValidateCreate(t *testing.T) { }, { name: "azuremachinepool with invalid MaxSurge and MaxUnavailable rolling upgrade configuration", - amp: createMachinePoolWithStrategy(AzureMachinePoolDeploymentStrategy{ - Type: RollingUpdateAzureMachinePoolDeploymentStrategyType, - RollingUpdate: &MachineRollingUpdateDeployment{ + amp: createMachinePoolWithStrategy(infrav1exp.AzureMachinePoolDeploymentStrategy{ + Type: infrav1exp.RollingUpdateAzureMachinePoolDeploymentStrategyType, + RollingUpdate: &infrav1exp.MachineRollingUpdateDeployment{ MaxSurge: &zero, MaxUnavailable: &zero, }, @@ -187,9 +188,9 @@ func TestAzureMachinePool_ValidateCreate(t *testing.T) { }, { name: "azuremachinepool with valid MaxSurge and MaxUnavailable rolling upgrade configuration", - amp: createMachinePoolWithStrategy(AzureMachinePoolDeploymentStrategy{ - Type: RollingUpdateAzureMachinePoolDeploymentStrategyType, - RollingUpdate: &MachineRollingUpdateDeployment{ + amp: createMachinePoolWithStrategy(infrav1exp.AzureMachinePoolDeploymentStrategy{ + Type: infrav1exp.RollingUpdateAzureMachinePoolDeploymentStrategyType, + RollingUpdate: &infrav1exp.MachineRollingUpdateDeployment{ MaxSurge: &zero, MaxUnavailable: &one, }, @@ -248,11 +249,11 @@ func TestAzureMachinePool_ValidateCreate(t *testing.T) { } for _, tc := range tests { - client := mockClient{Version: tc.version, ReturnError: tc.ownerNotFound} + c := mockClient{Version: tc.version, ReturnError: tc.ownerNotFound} t.Run(tc.name, func(t *testing.T) { g := NewWithT(t) - ampw := &azureMachinePoolWebhook{ - Client: client, + ampw := &AzureMachinePoolWebhook{ + Client: c, } _, err := ampw.ValidateCreate(t.Context(), tc.amp) if tc.wantErr { @@ -273,7 +274,7 @@ type mockDefaultClient struct { ReturnError bool } -func (m mockDefaultClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { +func (m mockDefaultClient) Get(_ context.Context, _ client.ObjectKey, obj client.Object, _ ...client.GetOption) error { switch obj := obj.(type) { case *infrav1.AzureCluster: obj.Spec.SubscriptionID = m.SubscriptionID @@ -288,7 +289,7 @@ func (m mockDefaultClient) Get(ctx context.Context, key client.ObjectKey, obj cl return nil } -func (m mockDefaultClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { +func (m mockDefaultClient) List(_ context.Context, list client.ObjectList, _ ...client.ListOption) error { list.(*clusterv1.MachinePoolList).Items = []clusterv1.MachinePool{ { Spec: clusterv1.MachinePoolSpec{ @@ -315,8 +316,8 @@ func TestAzureMachinePool_ValidateUpdate(t *testing.T) { tests := []struct { name string - oldAMP *AzureMachinePool - amp *AzureMachinePool + oldAMP *infrav1exp.AzureMachinePool + amp *infrav1exp.AzureMachinePool wantErr bool }{ { @@ -345,10 +346,10 @@ func TestAzureMachinePool_ValidateUpdate(t *testing.T) { }, { name: "azuremachinepool with invalid MaxSurge and MaxUnavailable rolling upgrade configuration", - oldAMP: createMachinePoolWithStrategy(AzureMachinePoolDeploymentStrategy{}), - amp: createMachinePoolWithStrategy(AzureMachinePoolDeploymentStrategy{ - Type: RollingUpdateAzureMachinePoolDeploymentStrategyType, - RollingUpdate: &MachineRollingUpdateDeployment{ + oldAMP: createMachinePoolWithStrategy(infrav1exp.AzureMachinePoolDeploymentStrategy{}), + amp: createMachinePoolWithStrategy(infrav1exp.AzureMachinePoolDeploymentStrategy{ + Type: infrav1exp.RollingUpdateAzureMachinePoolDeploymentStrategyType, + RollingUpdate: &infrav1exp.MachineRollingUpdateDeployment{ MaxSurge: &zero, MaxUnavailable: &zero, }, @@ -357,10 +358,10 @@ func TestAzureMachinePool_ValidateUpdate(t *testing.T) { }, { name: "azuremachinepool with valid MaxSurge and MaxUnavailable rolling upgrade configuration", - oldAMP: createMachinePoolWithStrategy(AzureMachinePoolDeploymentStrategy{}), - amp: createMachinePoolWithStrategy(AzureMachinePoolDeploymentStrategy{ - Type: RollingUpdateAzureMachinePoolDeploymentStrategyType, - RollingUpdate: &MachineRollingUpdateDeployment{ + oldAMP: createMachinePoolWithStrategy(infrav1exp.AzureMachinePoolDeploymentStrategy{}), + amp: createMachinePoolWithStrategy(infrav1exp.AzureMachinePoolDeploymentStrategy{ + Type: infrav1exp.RollingUpdateAzureMachinePoolDeploymentStrategyType, + RollingUpdate: &infrav1exp.MachineRollingUpdateDeployment{ MaxSurge: &zero, MaxUnavailable: &one, }, @@ -389,7 +390,7 @@ func TestAzureMachinePool_ValidateUpdate(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { g := NewWithT(t) - ampw := &azureMachinePoolWebhook{} + ampw := &AzureMachinePoolWebhook{} _, err := ampw.ValidateUpdate(t.Context(), tc.oldAMP, tc.amp) if tc.wantErr { g.Expect(err).To(HaveOccurred()) @@ -404,7 +405,7 @@ func TestAzureMachinePool_Default(t *testing.T) { g := NewWithT(t) type test struct { - amp *AzureMachinePool + amp *infrav1exp.AzureMachinePool } existingPublicKey := validSSHPublicKey @@ -416,10 +417,10 @@ func TestAzureMachinePool_Default(t *testing.T) { fakeSubscriptionID := guuid.New().String() fakeClusterName := "testcluster" fakeMachinePoolName := "testmachinepool" - mockClient := mockDefaultClient{Name: fakeMachinePoolName, ClusterName: fakeClusterName, SubscriptionID: fakeSubscriptionID} + c := mockDefaultClient{Name: fakeMachinePoolName, ClusterName: fakeClusterName, SubscriptionID: fakeSubscriptionID} - roleAssignmentExistTest := test{amp: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ + roleAssignmentExistTest := test{amp: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ Identity: "SystemAssigned", SystemAssignedIdentityRole: &infrav1.SystemAssignedIdentityRole{ Name: existingRoleAssignmentName, @@ -432,8 +433,8 @@ func TestAzureMachinePool_Default(t *testing.T) { }, }} - emptyTest := test{amp: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ + emptyTest := test{amp: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ Identity: "SystemAssigned", SystemAssignedIdentityRole: &infrav1.SystemAssignedIdentityRole{}, }, @@ -442,8 +443,8 @@ func TestAzureMachinePool_Default(t *testing.T) { }, }} - systemAssignedIdentityRoleExistTest := test{amp: &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ + systemAssignedIdentityRoleExistTest := test{amp: &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ Identity: "SystemAssigned", SystemAssignedIdentityRole: &infrav1.SystemAssignedIdentityRole{ DefinitionID: "testroledefinitionid", @@ -455,8 +456,8 @@ func TestAzureMachinePool_Default(t *testing.T) { }, }} - ampw := &azureMachinePoolWebhook{ - Client: mockClient, + ampw := &AzureMachinePoolWebhook{ + Client: c, } err := ampw.Default(t.Context(), roleAssignmentExistTest.amp) @@ -485,7 +486,7 @@ func TestAzureMachinePool_Default(t *testing.T) { g.Expect(emptyTest.amp.Spec.SystemAssignedIdentityRole.DefinitionID).To(Equal(fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Authorization/roleDefinitions/%s", fakeSubscriptionID, apiinternal.ContributorRoleID))) } -func createMachinePoolWithMarketPlaceImage(publisher, offer, sku, version string, terminateNotificationTimeout *int) *AzureMachinePool { +func createMachinePoolWithMarketPlaceImage(publisher, offer, sku, version string, terminateNotificationTimeout *int) *infrav1exp.AzureMachinePool { image := infrav1.Image{ Marketplace: &infrav1.AzureMarketplaceImage{ ImagePlan: infrav1.ImagePlan{ @@ -497,9 +498,9 @@ func createMachinePoolWithMarketPlaceImage(publisher, offer, sku, version string }, } - return &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + return &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ Image: &image, SSHPublicKey: validSSHPublicKey, TerminateNotificationTimeout: terminateNotificationTimeout, @@ -512,7 +513,7 @@ func createMachinePoolWithMarketPlaceImage(publisher, offer, sku, version string } } -func createMachinePoolWithSharedImage(subscriptionID, resourceGroup, name, gallery, version string, terminateNotificationTimeout *int) *AzureMachinePool { +func createMachinePoolWithSharedImage(subscriptionID, resourceGroup, name, gallery, version string, terminateNotificationTimeout *int) *infrav1exp.AzureMachinePool { image := infrav1.Image{ SharedGallery: &infrav1.AzureSharedGalleryImage{ SubscriptionID: subscriptionID, @@ -523,9 +524,9 @@ func createMachinePoolWithSharedImage(subscriptionID, resourceGroup, name, galle }, } - return &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + return &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ Image: &image, SSHPublicKey: validSSHPublicKey, TerminateNotificationTimeout: terminateNotificationTimeout, @@ -538,10 +539,10 @@ func createMachinePoolWithSharedImage(subscriptionID, resourceGroup, name, galle } } -func createMachinePoolWithNetworkConfig(subnetName string, interfaces []infrav1.NetworkInterface) *AzureMachinePool { - return &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ +func createMachinePoolWithNetworkConfig(subnetName string, interfaces []infrav1.NetworkInterface) *infrav1exp.AzureMachinePool { + return &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ SubnetName: subnetName, NetworkInterfaces: interfaces, OSDisk: infrav1.OSDisk{ @@ -553,14 +554,14 @@ func createMachinePoolWithNetworkConfig(subnetName string, interfaces []infrav1. } } -func createMachinePoolWithImageByID(imageID string, terminateNotificationTimeout *int) *AzureMachinePool { +func createMachinePoolWithImageByID(imageID string, terminateNotificationTimeout *int) *infrav1exp.AzureMachinePool { image := infrav1.Image{ ID: &imageID, } - return &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + return &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ Image: &image, SSHPublicKey: validSSHPublicKey, TerminateNotificationTimeout: terminateNotificationTimeout, @@ -573,16 +574,33 @@ func createMachinePoolWithImageByID(imageID string, terminateNotificationTimeout } } -func createMachinePoolWithSystemAssignedIdentity(role string) *AzureMachinePool { - return &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ +func createMachinePoolWithSSHPublicKey(sshPublicKey string) *infrav1exp.AzureMachinePool { + return &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ + SSHPublicKey: sshPublicKey, + OSDisk: infrav1.OSDisk{ + CachingType: "None", + OSType: "Linux", + }, + }, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "testmachinepool", + }, + } +} + +func createMachinePoolWithSystemAssignedIdentity(role string) *infrav1exp.AzureMachinePool { + return &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ Identity: infrav1.VMIdentitySystemAssigned, SystemAssignedIdentityRole: &infrav1.SystemAssignedIdentityRole{ Name: role, Scope: "scope", DefinitionID: "definitionID", }, - Template: AzureMachinePoolMachineTemplate{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ OSDisk: infrav1.OSDisk{ CachingType: "None", OSType: "Linux", @@ -592,7 +610,7 @@ func createMachinePoolWithSystemAssignedIdentity(role string) *AzureMachinePool } } -func createMachinePoolWithDiagnostics(diagnosticsType infrav1.BootDiagnosticsStorageAccountType, userManaged *infrav1.UserManagedBootDiagnostics) *AzureMachinePool { +func createMachinePoolWithDiagnostics(diagnosticsType infrav1.BootDiagnosticsStorageAccountType, userManaged *infrav1.UserManagedBootDiagnostics) *infrav1exp.AzureMachinePool { var diagnostics *infrav1.Diagnostics if diagnosticsType != "" { @@ -607,9 +625,9 @@ func createMachinePoolWithDiagnostics(diagnosticsType infrav1.BootDiagnosticsSto diagnostics.Boot.UserManaged = userManaged } - return &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + return &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ Diagnostics: diagnostics, OSDisk: infrav1.OSDisk{ CachingType: "None", @@ -620,7 +638,7 @@ func createMachinePoolWithDiagnostics(diagnosticsType infrav1.BootDiagnosticsSto } } -func createMachinePoolWithUserAssignedIdentity(providerIDs []string) *AzureMachinePool { +func createMachinePoolWithUserAssignedIdentity(providerIDs []string) *infrav1exp.AzureMachinePool { userAssignedIdentities := make([]infrav1.UserAssignedIdentity, len(providerIDs)) for _, providerID := range providerIDs { @@ -629,11 +647,11 @@ func createMachinePoolWithUserAssignedIdentity(providerIDs []string) *AzureMachi }) } - return &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ + return &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ Identity: infrav1.VMIdentityUserAssigned, UserAssignedIdentities: userAssignedIdentities, - Template: AzureMachinePoolMachineTemplate{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ OSDisk: infrav1.OSDisk{ CachingType: "None", OSType: "Linux", @@ -643,11 +661,11 @@ func createMachinePoolWithUserAssignedIdentity(providerIDs []string) *AzureMachi } } -func createMachinePoolWithStrategy(strategy AzureMachinePoolDeploymentStrategy) *AzureMachinePool { - return &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ +func createMachinePoolWithStrategy(strategy infrav1exp.AzureMachinePoolDeploymentStrategy) *infrav1exp.AzureMachinePool { + return &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ Strategy: strategy, - Template: AzureMachinePoolMachineTemplate{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ OSDisk: infrav1.OSDisk{ CachingType: "None", OSType: "Linux", @@ -657,11 +675,11 @@ func createMachinePoolWithStrategy(strategy AzureMachinePoolDeploymentStrategy) } } -func createMachinePoolWithOrchestrationMode(mode armcompute.OrchestrationMode) *AzureMachinePool { - return &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ +func createMachinePoolWithOrchestrationMode(mode armcompute.OrchestrationMode) *infrav1exp.AzureMachinePool { + return &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ OrchestrationMode: infrav1.OrchestrationModeType(mode), - Template: AzureMachinePoolMachineTemplate{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ OSDisk: infrav1.OSDisk{ CachingType: "None", OSType: "Linux", @@ -671,10 +689,10 @@ func createMachinePoolWithOrchestrationMode(mode armcompute.OrchestrationMode) * } } -func createMachinePoolWithDiffDiskSettings(settings infrav1.DiffDiskSettings) *AzureMachinePool { - return &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ +func createMachinePoolWithDiffDiskSettings(settings infrav1.DiffDiskSettings) *infrav1exp.AzureMachinePool { + return &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ OSDisk: infrav1.OSDisk{ DiffDiskSettings: &settings, }, @@ -688,7 +706,7 @@ func TestAzureMachinePool_ValidateCreateFailure(t *testing.T) { tests := []struct { name string - amp *AzureMachinePool + amp *infrav1exp.AzureMachinePool featureGateEnabled *bool expectError bool }{ @@ -704,7 +722,7 @@ func TestAzureMachinePool_ValidateCreateFailure(t *testing.T) { if tc.featureGateEnabled != nil { utilfeature.SetFeatureGateDuringTest(t, feature.Gates, capifeature.MachinePool, *tc.featureGateEnabled) } - ampw := &azureMachinePoolWebhook{} + ampw := &AzureMachinePoolWebhook{} _, err := ampw.ValidateCreate(t.Context(), tc.amp) if tc.expectError { g.Expect(err).To(HaveOccurred()) @@ -715,7 +733,7 @@ func TestAzureMachinePool_ValidateCreateFailure(t *testing.T) { } } -func getKnownValidAzureMachinePool() *AzureMachinePool { +func getKnownValidAzureMachinePool() *infrav1exp.AzureMachinePool { image := infrav1.Image{ Marketplace: &infrav1.AzureMarketplaceImage{ ImagePlan: infrav1.ImagePlan{ @@ -726,9 +744,9 @@ func getKnownValidAzureMachinePool() *AzureMachinePool { Version: "1.0.0", }, } - return &AzureMachinePool{ - Spec: AzureMachinePoolSpec{ - Template: AzureMachinePoolMachineTemplate{ + return &infrav1exp.AzureMachinePool{ + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ Image: &image, SSHPublicKey: validSSHPublicKey, TerminateNotificationTimeout: ptr.To(10), @@ -743,9 +761,9 @@ func getKnownValidAzureMachinePool() *AzureMachinePool { Scope: "scope", DefinitionID: "definitionID", }, - Strategy: AzureMachinePoolDeploymentStrategy{ - Type: RollingUpdateAzureMachinePoolDeploymentStrategyType, - RollingUpdate: &MachineRollingUpdateDeployment{ + Strategy: infrav1exp.AzureMachinePoolDeploymentStrategy{ + Type: infrav1exp.RollingUpdateAzureMachinePoolDeploymentStrategyType, + RollingUpdate: &infrav1exp.MachineRollingUpdateDeployment{ MaxSurge: &zero, MaxUnavailable: &one, }, diff --git a/exp/api/v1beta1/azuremachinepoolmachine_webhook.go b/internal/exp/webhooks/azuremachinepoolmachine_webhook.go similarity index 62% rename from exp/api/v1beta1/azuremachinepoolmachine_webhook.go rename to internal/exp/webhooks/azuremachinepoolmachine_webhook.go index 9ce6f003f14..6d6ece39517 100644 --- a/exp/api/v1beta1/azuremachinepoolmachine_webhook.go +++ b/internal/exp/webhooks/azuremachinepoolmachine_webhook.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1beta1 +package webhooks import ( "context" @@ -22,29 +22,31 @@ import ( "github.com/pkg/errors" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1" ) // SetupWebhookWithManager sets up and registers the webhook with the manager. -func (ampm *AzureMachinePoolMachine) SetupWebhookWithManager(mgr ctrl.Manager) error { - w := new(azureMachinePoolMachineWebhook) - return ctrl.NewWebhookManagedBy(mgr, ampm). +func (w *AzureMachinePoolMachineWebhook) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr, &infrav1exp.AzureMachinePoolMachine{}). WithValidator(w). Complete() } // +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-azuremachinepoolmachine,mutating=false,failurePolicy=fail,groups=infrastructure.cluster.x-k8s.io,resources=azuremachinepoolmachines,versions=v1beta1,name=azuremachinepoolmachine.kb.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 -type azureMachinePoolMachineWebhook struct{} +// AzureMachinePoolMachineWebhook implements a validating webhook for AzureMachinePoolMachine. +type AzureMachinePoolMachineWebhook struct{} -var _ admission.Validator[*AzureMachinePoolMachine] = &azureMachinePoolMachineWebhook{} +var _ admission.Validator[*infrav1exp.AzureMachinePoolMachine] = &AzureMachinePoolMachineWebhook{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type. -func (*azureMachinePoolMachineWebhook) ValidateCreate(_ context.Context, _ *AzureMachinePoolMachine) (admission.Warnings, error) { +func (*AzureMachinePoolMachineWebhook) ValidateCreate(_ context.Context, _ *infrav1exp.AzureMachinePoolMachine) (admission.Warnings, error) { return nil, nil } // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type. -func (*azureMachinePoolMachineWebhook) ValidateUpdate(_ context.Context, oldMachine, ampm *AzureMachinePoolMachine) (admission.Warnings, error) { +func (*AzureMachinePoolMachineWebhook) ValidateUpdate(_ context.Context, oldMachine, ampm *infrav1exp.AzureMachinePoolMachine) (admission.Warnings, error) { if oldMachine.Spec.ProviderID != "" && ampm.Spec.ProviderID != oldMachine.Spec.ProviderID { return nil, errors.New("providerID is immutable") } @@ -53,6 +55,6 @@ func (*azureMachinePoolMachineWebhook) ValidateUpdate(_ context.Context, oldMach } // ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type. -func (*azureMachinePoolMachineWebhook) ValidateDelete(_ context.Context, _ *AzureMachinePoolMachine) (admission.Warnings, error) { +func (*AzureMachinePoolMachineWebhook) ValidateDelete(_ context.Context, _ *infrav1exp.AzureMachinePoolMachine) (admission.Warnings, error) { return nil, nil } diff --git a/main.go b/main.go index 8daad0cd88d..2cefa89bd38 100644 --- a/main.go +++ b/main.go @@ -61,6 +61,7 @@ import ( infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1" infrav1controllersexp "sigs.k8s.io/cluster-api-provider-azure/exp/controllers" "sigs.k8s.io/cluster-api-provider-azure/feature" + expwebhooks "sigs.k8s.io/cluster-api-provider-azure/internal/exp/webhooks" "sigs.k8s.io/cluster-api-provider-azure/internal/webhooks" "sigs.k8s.io/cluster-api-provider-azure/pkg/coalescing" "sigs.k8s.io/cluster-api-provider-azure/pkg/ot" @@ -663,12 +664,12 @@ func registerWebhooks(mgr manager.Manager) { } if feature.Gates.Enabled(capifeature.MachinePool) { - if err := infrav1exp.SetupAzureMachinePoolWebhookWithManager(mgr); err != nil { + if err := (&expwebhooks.AzureMachinePoolWebhook{}).SetupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "AzureMachinePool") os.Exit(1) } - if err := (&infrav1exp.AzureMachinePoolMachine{}).SetupWebhookWithManager(mgr); err != nil { + if err := (&expwebhooks.AzureMachinePoolMachineWebhook{}).SetupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "AzureMachinePoolMachine") os.Exit(1) }