diff --git a/api/v1alpha3/azuremachine_conversion.go b/api/v1alpha3/azuremachine_conversion.go index dcf10192e42..e5994f1836c 100644 --- a/api/v1alpha3/azuremachine_conversion.go +++ b/api/v1alpha3/azuremachine_conversion.go @@ -62,6 +62,10 @@ func (src *AzureMachine) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.DNSServers = restored.Spec.DNSServers } + if len(restored.Spec.VMExtensions) > 0 { + dst.Spec.VMExtensions = restored.Spec.VMExtensions + } + dst.Spec.SubnetName = restored.Spec.SubnetName dst.Status.LongRunningOperationStates = restored.Status.LongRunningOperationStates diff --git a/api/v1alpha3/azuremachinetemplate_conversion.go b/api/v1alpha3/azuremachinetemplate_conversion.go index c3e80912b90..870d42c470f 100644 --- a/api/v1alpha3/azuremachinetemplate_conversion.go +++ b/api/v1alpha3/azuremachinetemplate_conversion.go @@ -65,6 +65,10 @@ func (src *AzureMachineTemplate) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.Template.Spec.DNSServers = restored.Spec.Template.Spec.DNSServers } + if len(restored.Spec.Template.Spec.VMExtensions) > 0 { + dst.Spec.Template.Spec.VMExtensions = restored.Spec.Template.Spec.VMExtensions + } + return nil } diff --git a/api/v1alpha4/azuremachine_conversion.go b/api/v1alpha4/azuremachine_conversion.go index cf582ada834..85bb35638a3 100644 --- a/api/v1alpha4/azuremachine_conversion.go +++ b/api/v1alpha4/azuremachine_conversion.go @@ -48,6 +48,10 @@ func (src *AzureMachine) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.DNSServers = restored.Spec.DNSServers } + if len(restored.Spec.VMExtensions) > 0 { + dst.Spec.VMExtensions = restored.Spec.VMExtensions + } + return nil } diff --git a/api/v1alpha4/azuremachinetemplate_conversion.go b/api/v1alpha4/azuremachinetemplate_conversion.go index 4abf57698bd..4b67e7695aa 100644 --- a/api/v1alpha4/azuremachinetemplate_conversion.go +++ b/api/v1alpha4/azuremachinetemplate_conversion.go @@ -50,6 +50,10 @@ func (src *AzureMachineTemplate) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.Template.Spec.DNSServers = restored.Spec.Template.Spec.DNSServers } + if len(restored.Spec.Template.Spec.VMExtensions) > 0 { + dst.Spec.Template.Spec.VMExtensions = restored.Spec.Template.Spec.VMExtensions + } + return nil } diff --git a/api/v1beta1/azuremachine_types.go b/api/v1beta1/azuremachine_types.go index c804823969d..3ba84e784b3 100644 --- a/api/v1beta1/azuremachine_types.go +++ b/api/v1beta1/azuremachine_types.go @@ -122,6 +122,10 @@ type AzureMachineSpec struct { // DNSServers adds a list of DNS Server IP addresses to the VM NICs. // +optional DNSServers []string `json:"dnsServers,omitempty"` + + // VMExtensions specifies a list of extensions to be added to the virtual machine. + // +optional + VMExtensions []VMExtension `json:"vmExtensions,omitempty"` } // SpotVMOptions defines the options relevant to running the Machine on Spot VMs. diff --git a/api/v1beta1/types.go b/api/v1beta1/types.go index d60fa3f47a5..17dd6fe2660 100644 --- a/api/v1beta1/types.go +++ b/api/v1beta1/types.go @@ -530,6 +530,22 @@ type DataDisk struct { CachingType string `json:"cachingType,omitempty"` } +// VMExtension specifies the parameters for a custom VM extension +type VMExtension struct { + // Name is the name of the extension. + Name string `json:"name"` + // Publisher is the name of the extension handler publisher. + Publisher string `json:"publisher"` + // Version specifies the version of the script handler. + Version string `json:"version"` + // Settings is a JSON formatted public settings for the extension. + // +optional + Settings Tags `json:"settings,omitempty"` + // ProtectedSettings is a JSON formatted protected settings for the extension. + // +optional + ProtectedSettings Tags `json:"protectedSettings,omitempty"` +} + // ManagedDiskParameters defines the parameters of a managed disk. type ManagedDiskParameters struct { // +optional diff --git a/azure/scope/machine.go b/azure/scope/machine.go index 46830c0d3c1..78d6b9a9a3f 100644 --- a/azure/scope/machine.go +++ b/azure/scope/machine.go @@ -333,6 +333,21 @@ func (m *MachineScope) HasSystemAssignedIdentity() bool { // VMExtensionSpecs returns the VM extension specs. func (m *MachineScope) VMExtensionSpecs() []azure.ResourceSpecGetter { var extensionSpecs = []azure.ResourceSpecGetter{} + for _, extension := range m.AzureMachine.Spec.VMExtensions { + extensionSpecs = append(extensionSpecs, &vmextensions.VMExtensionSpec{ + ExtensionSpec: azure.ExtensionSpec{ + Name: extension.Name, + VMName: m.Name(), + Publisher: extension.Publisher, + Version: extension.Version, + Settings: extension.Settings, + ProtectedSettings: extension.ProtectedSettings, + }, + ResourceGroup: m.ResourceGroup(), + Location: m.Location(), + }) + } + bootstrapExtensionSpec := azure.GetBootstrappingVMExtension(m.AzureMachine.Spec.OSDisk.OSType, m.CloudEnvironment(), m.Name()) if bootstrapExtensionSpec != nil { @@ -359,9 +374,9 @@ func (m *MachineScope) Subnet() infrav1.SubnetSpec { // AvailabilityZone returns the AzureMachine Availability Zone. // Priority for selecting the AZ is -// 1) Machine.Spec.FailureDomain -// 2) AzureMachine.Spec.FailureDomain (This is to support deprecated AZ) -// 3) No AZ +// 1. Machine.Spec.FailureDomain +// 2. AzureMachine.Spec.FailureDomain (This is to support deprecated AZ) +// 3. No AZ func (m *MachineScope) AvailabilityZone() string { if m.Machine.Spec.FailureDomain != nil { return *m.Machine.Spec.FailureDomain diff --git a/azure/scope/machine_test.go b/azure/scope/machine_test.go index 3ce296756ed..64194c05527 100644 --- a/azure/scope/machine_test.go +++ b/azure/scope/machine_test.go @@ -728,6 +728,83 @@ func TestMachineScope_VMExtensionSpecs(t *testing.T) { }, want: []azure.ResourceSpecGetter{}, }, + { + name: "If a custom VM extension is specified, it returns the custom VM extension", + machineScope: MachineScope{ + Machine: &clusterv1.Machine{}, + AzureMachine: &infrav1.AzureMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "machine-name", + }, + Spec: infrav1.AzureMachineSpec{ + OSDisk: infrav1.OSDisk{ + OSType: "Linux", + }, + VMExtensions: []infrav1.VMExtension{ + { + Name: "custom-vm-extension", + Publisher: "Microsoft.Azure.Extensions", + Version: "2.0", + Settings: map[string]string{ + "timestamp": "1234567890", + }, + ProtectedSettings: map[string]string{ + "commandToExecute": "echo hello world", + }, + }, + }, + }, + }, + ClusterScoper: &ClusterScope{ + AzureClients: AzureClients{ + EnvironmentSettings: auth.EnvironmentSettings{ + Environment: autorestazure.Environment{ + Name: autorestazure.PublicCloud.Name, + }, + }, + }, + AzureCluster: &infrav1.AzureCluster{ + Spec: infrav1.AzureClusterSpec{ + ResourceGroup: "my-rg", + AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ + Location: "westus", + }, + }, + }, + }, + }, + want: []azure.ResourceSpecGetter{ + &vmextensions.VMExtensionSpec{ + ExtensionSpec: azure.ExtensionSpec{ + Name: "custom-vm-extension", + VMName: "machine-name", + Publisher: "Microsoft.Azure.Extensions", + Version: "2.0", + Settings: map[string]string{ + "timestamp": "1234567890", + }, + ProtectedSettings: map[string]string{ + "commandToExecute": "echo hello world", + }, + }, + ResourceGroup: "my-rg", + Location: "westus", + }, + &vmextensions.VMExtensionSpec{ + ExtensionSpec: azure.ExtensionSpec{ + Name: "CAPZ.Linux.Bootstrapping", + VMName: "machine-name", + Publisher: "Microsoft.Azure.ContainerUpstream", + Version: "1.0", + ProtectedSettings: map[string]string{ + "commandToExecute": azure.LinuxBootstrapExtensionCommand, + }, + }, + ResourceGroup: "my-rg", + Location: "westus", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/azure/scope/machinepool.go b/azure/scope/machinepool.go index 6286b9c753f..9140862920e 100644 --- a/azure/scope/machinepool.go +++ b/azure/scope/machinepool.go @@ -127,6 +127,7 @@ func (m *MachinePoolScope) ScaleSetSpec() azure.ScaleSetSpec { SpotVMOptions: m.AzureMachinePool.Spec.Template.SpotVMOptions, FailureDomains: m.MachinePool.Spec.FailureDomains, TerminateNotificationTimeout: m.AzureMachinePool.Spec.Template.TerminateNotificationTimeout, + VMExtensions: m.AzureMachinePool.Spec.Template.VMExtensions, } } @@ -613,6 +614,21 @@ func (m *MachinePoolScope) HasSystemAssignedIdentity() bool { // VMSSExtensionSpecs returns the VMSS extension specs. func (m *MachinePoolScope) VMSSExtensionSpecs() []azure.ResourceSpecGetter { var extensionSpecs = []azure.ResourceSpecGetter{} + + for _, extension := range m.AzureMachinePool.Spec.Template.VMExtensions { + extensionSpecs = append(extensionSpecs, &scalesets.VMSSExtensionSpec{ + ExtensionSpec: azure.ExtensionSpec{ + Name: extension.Name, + VMName: m.Name(), + Publisher: extension.Publisher, + Version: extension.Version, + Settings: extension.Settings, + ProtectedSettings: extension.ProtectedSettings, + }, + ResourceGroup: m.ResourceGroup(), + }) + } + bootstrapExtensionSpec := azure.GetBootstrappingVMExtension(m.AzureMachinePool.Spec.Template.OSDisk.OSType, m.CloudEnvironment(), m.Name()) if bootstrapExtensionSpec != nil { diff --git a/azure/scope/machinepool_test.go b/azure/scope/machinepool_test.go index cd82f910349..fbc6ead0291 100644 --- a/azure/scope/machinepool_test.go +++ b/azure/scope/machinepool_test.go @@ -872,6 +872,83 @@ func TestMachinePoolScope_VMSSExtensionSpecs(t *testing.T) { }, want: []azure.ResourceSpecGetter{}, }, + { + name: "If a custom VM extension is specified, it returns the custom VM extension", + machinePoolScope: MachinePoolScope{ + MachinePool: &expv1.MachinePool{}, + AzureMachinePool: &infrav1exp.AzureMachinePool{ + ObjectMeta: metav1.ObjectMeta{ + Name: "machinepool-name", + }, + Spec: infrav1exp.AzureMachinePoolSpec{ + Template: infrav1exp.AzureMachinePoolMachineTemplate{ + OSDisk: infrav1.OSDisk{ + OSType: "Linux", + }, + VMExtensions: []infrav1.VMExtension{ + { + Name: "custom-vm-extension", + Publisher: "Microsoft.Azure.Extensions", + Version: "2.0", + Settings: map[string]string{ + "timestamp": "1234567890", + }, + ProtectedSettings: map[string]string{ + "commandToExecute": "echo hello world", + }, + }, + }, + }, + }, + }, + ClusterScoper: &ClusterScope{ + AzureClients: AzureClients{ + EnvironmentSettings: auth.EnvironmentSettings{ + Environment: autorestazure.Environment{ + Name: autorestazure.PublicCloud.Name, + }, + }, + }, + AzureCluster: &infrav1.AzureCluster{ + Spec: infrav1.AzureClusterSpec{ + ResourceGroup: "my-rg", + AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ + Location: "westus", + }, + }, + }, + }, + }, + want: []azure.ResourceSpecGetter{ + &scalesets.VMSSExtensionSpec{ + ExtensionSpec: azure.ExtensionSpec{ + Name: "custom-vm-extension", + VMName: "machinepool-name", + Publisher: "Microsoft.Azure.Extensions", + Version: "2.0", + Settings: map[string]string{ + "timestamp": "1234567890", + }, + ProtectedSettings: map[string]string{ + "commandToExecute": "echo hello world", + }, + }, + ResourceGroup: "my-rg", + }, + &scalesets.VMSSExtensionSpec{ + ExtensionSpec: azure.ExtensionSpec{ + Name: "CAPZ.Linux.Bootstrapping", + VMName: "machinepool-name", + Publisher: "Microsoft.Azure.ContainerUpstream", + Version: "1.0", + ProtectedSettings: map[string]string{ + "commandToExecute": azure.LinuxBootstrapExtensionCommand, + }, + }, + ResourceGroup: "my-rg", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/azure/services/scalesets/vmssextension_spec.go b/azure/services/scalesets/vmssextension_spec.go index 27f663c8153..0c77c5c25d8 100644 --- a/azure/services/scalesets/vmssextension_spec.go +++ b/azure/services/scalesets/vmssextension_spec.go @@ -62,7 +62,7 @@ func (s *VMSSExtensionSpec) Parameters(existing interface{}) (interface{}, error Publisher: to.StringPtr(s.Publisher), Type: to.StringPtr(s.Name), TypeHandlerVersion: to.StringPtr(s.Version), - Settings: nil, + Settings: s.Settings, ProtectedSettings: s.ProtectedSettings, }, }, nil diff --git a/azure/services/scalesets/vmssextension_spec_test.go b/azure/services/scalesets/vmssextension_spec_test.go new file mode 100644 index 00000000000..8671e19766b --- /dev/null +++ b/azure/services/scalesets/vmssextension_spec_test.go @@ -0,0 +1,96 @@ +/* +Copyright 2022 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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scalesets + +import ( + "testing" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-11-01/compute" + "github.com/Azure/go-autorest/autorest/to" + . "github.com/onsi/gomega" + "sigs.k8s.io/cluster-api-provider-azure/azure" +) + +var ( + fakeVMSSExtensionSpec = VMSSExtensionSpec{ + azure.ExtensionSpec{ + Name: "my-vm-extension", + VMName: "my-vm", + Publisher: "my-publisher", + Version: "1.0", + Settings: map[string]string{"my-setting": "my-value"}, + ProtectedSettings: map[string]string{"my-protected-setting": "my-protected-value"}, + }, + "my-rg", + } + + fakeVMSSExtensionParams = compute.VirtualMachineScaleSetExtension{ + Name: to.StringPtr("my-vm-extension"), + VirtualMachineScaleSetExtensionProperties: &compute.VirtualMachineScaleSetExtensionProperties{ + Publisher: to.StringPtr("my-publisher"), + Type: to.StringPtr("my-vm-extension"), + TypeHandlerVersion: to.StringPtr("1.0"), + Settings: map[string]string{"my-setting": "my-value"}, + ProtectedSettings: map[string]string{"my-protected-setting": "my-protected-value"}, + }, + } +) + +func TestParameters(t *testing.T) { + testcases := []struct { + name string + spec *VMSSExtensionSpec + existing interface{} + expect func(g *WithT, result interface{}) + expectedError string + }{ + { + name: "get parameters for vmextension", + spec: &fakeVMSSExtensionSpec, + existing: nil, + expect: func(g *WithT, result interface{}) { + g.Expect(result).To(Equal(fakeVMSSExtensionParams)) + }, + expectedError: "", + }, + { + name: "vmextension that already exists", + spec: &fakeVMSSExtensionSpec, + existing: fakeVMSSExtensionParams, + expect: func(g *WithT, result interface{}) { + g.Expect(result).To(BeNil()) + }, + expectedError: "", + }, + } + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + t.Parallel() + + result, err := tc.spec.Parameters(tc.existing) + if tc.expectedError != "" { + g.Expect(err).To(HaveOccurred()) + g.Expect(err).To(MatchError(tc.expectedError)) + } else { + g.Expect(err).NotTo(HaveOccurred()) + } + tc.expect(g, result) + }) + } +} diff --git a/azure/services/virtualmachines/spec.go b/azure/services/virtualmachines/spec.go index e77dab9c343..0e7d7bcc1c0 100644 --- a/azure/services/virtualmachines/spec.go +++ b/azure/services/virtualmachines/spec.go @@ -54,6 +54,7 @@ type VMSpec struct { Image *infrav1.Image BootstrapData string ProviderID string + VMExtensions []infrav1.VMExtension } // ResourceName returns the name of the virtual machine. diff --git a/azure/services/vmextensions/spec.go b/azure/services/vmextensions/spec.go index 89d491b37cd..e99cf16ac0e 100644 --- a/azure/services/vmextensions/spec.go +++ b/azure/services/vmextensions/spec.go @@ -62,7 +62,7 @@ func (s *VMExtensionSpec) Parameters(existing interface{}) (interface{}, error) Publisher: to.StringPtr(s.Publisher), Type: to.StringPtr(s.Name), TypeHandlerVersion: to.StringPtr(s.Version), - Settings: nil, + Settings: s.Settings, ProtectedSettings: s.ProtectedSettings, }, Location: to.StringPtr(s.Location), diff --git a/azure/services/vmextensions/spec_test.go b/azure/services/vmextensions/spec_test.go new file mode 100644 index 00000000000..67d0bce0334 --- /dev/null +++ b/azure/services/vmextensions/spec_test.go @@ -0,0 +1,97 @@ +/* +Copyright 2022 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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vmextensions + +import ( + "testing" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-11-01/compute" + "github.com/Azure/go-autorest/autorest/to" + . "github.com/onsi/gomega" + "sigs.k8s.io/cluster-api-provider-azure/azure" +) + +var ( + fakeVMExtensionSpec = VMExtensionSpec{ + azure.ExtensionSpec{ + Name: "my-vm-extension", + VMName: "my-vm", + Publisher: "my-publisher", + Version: "1.0", + Settings: map[string]string{"my-setting": "my-value"}, + ProtectedSettings: map[string]string{"my-protected-setting": "my-protected-value"}, + }, + "my-rg", + "my-location", + } + + fakeVMExtensionParams = compute.VirtualMachineExtension{ + VirtualMachineExtensionProperties: &compute.VirtualMachineExtensionProperties{ + Publisher: to.StringPtr("my-publisher"), + Type: to.StringPtr("my-vm-extension"), + TypeHandlerVersion: to.StringPtr("1.0"), + Settings: map[string]string{"my-setting": "my-value"}, + ProtectedSettings: map[string]string{"my-protected-setting": "my-protected-value"}, + }, + Location: to.StringPtr("my-location"), + } +) + +func TestParameters(t *testing.T) { + testcases := []struct { + name string + spec *VMExtensionSpec + existing interface{} + expect func(g *WithT, result interface{}) + expectedError string + }{ + { + name: "get parameters for vmextension", + spec: &fakeVMExtensionSpec, + existing: nil, + expect: func(g *WithT, result interface{}) { + g.Expect(result).To(Equal(fakeVMExtensionParams)) + }, + expectedError: "", + }, + { + name: "vmextension that already exists", + spec: &fakeVMExtensionSpec, + existing: fakeVMExtensionParams, + expect: func(g *WithT, result interface{}) { + g.Expect(result).To(BeNil()) + }, + expectedError: "", + }, + } + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + t.Parallel() + + result, err := tc.spec.Parameters(tc.existing) + if tc.expectedError != "" { + g.Expect(err).To(HaveOccurred()) + g.Expect(err).To(MatchError(tc.expectedError)) + } else { + g.Expect(err).NotTo(HaveOccurred()) + } + tc.expect(g, result) + }) + } +} diff --git a/azure/types.go b/azure/types.go index df20d352657..81756ab4e69 100644 --- a/azure/types.go +++ b/azure/types.go @@ -64,6 +64,7 @@ type ScaleSetSpec struct { SpotVMOptions *infrav1.SpotVMOptions AdditionalCapabilities *infrav1.AdditionalCapabilities FailureDomains []string + VMExtensions []infrav1.VMExtension } // TagsSpec defines the specification for a set of tags. @@ -82,6 +83,7 @@ type ExtensionSpec struct { VMName string Publisher string Version string + Settings map[string]string ProtectedSettings map[string]string } diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinepools.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinepools.yaml index a45716ba497..8f65f29887c 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinepools.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinepools.yaml @@ -1792,6 +1792,35 @@ spec: VMSS scheduled events termination notification with specified timeout allowed values are between 5 and 15 (mins) type: integer + vmExtensions: + description: VMExtensions specifies a list of extensions to be attached + to the Virtual Machine + items: + description: VMExtension defines an extension for a Virtual Machine + properties: + name: + description: Name is the name of the extension + type: string + publisher: + description: Publisher is the name of the extension publisher + type: string + version: + description: Version is the version of the extension + type: string + settings: + description: Settings is a JSON string containing the extension + settings + type: object + protectedSettings: + description: ProtectedSettings is a JSON string containing the + extension protected settings + type: object + required: + - name + - publisher + - version + type: object + type: array vmSize: description: VMSize is the size of the Virtual Machine to build. See https://docs.microsoft.com/en-us/rest/api/compute/virtualmachines/createorupdate#virtualmachinesizetypes diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachines.yaml index ddf1a5928f7..3beae5a8fec 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachines.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachines.yaml @@ -1434,6 +1434,35 @@ spec: - providerID type: object type: array + vmExtensions: + description: VMExtensions specifies a list of extensions to be attached + to the Virtual Machine + items: + description: VMExtension defines an extension for a Virtual Machine + properties: + name: + description: Name is the name of the extension + type: string + publisher: + description: Publisher is the name of the extension publisher + type: string + version: + description: Version is the version of the extension + type: string + settings: + description: Settings is a JSON string containing the extension + settings + type: object + protectedSettings: + description: ProtectedSettings is a JSON string containing the + extension protected settings + type: object + required: + - name + - publisher + - version + type: object + type: array vmSize: type: string required: diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinetemplates.yaml index 1a246a2e66b..8b1fc93bacd 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinetemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinetemplates.yaml @@ -1219,6 +1219,35 @@ spec: - providerID type: object type: array + vmExtensions: + description: VMExtensions specifies a list of extensions to be attached + to the Virtual Machine + items: + description: VMExtension defines an extension for a Virtual Machine + properties: + name: + description: Name is the name of the extension + type: string + publisher: + description: Publisher is the name of the extension publisher + type: string + version: + description: Version is the version of the extension + type: string + settings: + description: Settings is a JSON string containing the extension + settings + type: object + protectedSettings: + description: ProtectedSettings is a JSON string containing the + extension protected settings + type: object + required: + - name + - publisher + - version + type: object + type: array vmSize: type: string required: diff --git a/exp/api/v1alpha3/azuremachinepool_conversion.go b/exp/api/v1alpha3/azuremachinepool_conversion.go index 86574c7663f..1bf5fec349d 100644 --- a/exp/api/v1alpha3/azuremachinepool_conversion.go +++ b/exp/api/v1alpha3/azuremachinepool_conversion.go @@ -85,6 +85,10 @@ func (src *AzureMachinePool) ConvertTo(dstRaw conversion.Hub) error { } } + if len(restored.Spec.Template.VMExtensions) > 0 { + dst.Spec.Template.VMExtensions = restored.Spec.Template.VMExtensions + } + return nil } diff --git a/exp/api/v1alpha4/azuremachinepool_conversion.go b/exp/api/v1alpha4/azuremachinepool_conversion.go index 90c19148545..1f190c79516 100644 --- a/exp/api/v1alpha4/azuremachinepool_conversion.go +++ b/exp/api/v1alpha4/azuremachinepool_conversion.go @@ -43,6 +43,10 @@ func (src *AzureMachinePool) ConvertTo(dstRaw conversion.Hub) error { dst.Status.Image.ComputeGallery = restored.Status.Image.ComputeGallery } + if len(restored.Spec.Template.VMExtensions) > 0 { + dst.Spec.Template.VMExtensions = restored.Spec.Template.VMExtensions + } + return nil } diff --git a/exp/api/v1beta1/azuremachinepool_types.go b/exp/api/v1beta1/azuremachinepool_types.go index 7f3b6a50546..54cbb140796 100644 --- a/exp/api/v1beta1/azuremachinepool_types.go +++ b/exp/api/v1beta1/azuremachinepool_types.go @@ -87,6 +87,10 @@ type ( // SubnetName selects the Subnet where the VMSS will be placed // +optional SubnetName string `json:"subnetName,omitempty"` + + // VMExtensions specifies a list of extensions to be added to the virtual machine. + // +optional + VMExtensions []infrav1.VMExtension `json:"vmExtensions,omitempty"` } // AzureMachinePoolSpec defines the desired state of AzureMachinePool.