Skip to content

Commit 087217a

Browse files
committed
Add DisableExtensionOperations field
This option when set to true ensures no that no VMExtension are configured on the machine by preventing users to add values to VMExtensions field in machine template, Disabling the bootstrapping VMExtension and setting AllowExtensionOperations false in the instance osProfile.
1 parent 43e72a9 commit 087217a

12 files changed

+172
-3
lines changed

api/v1beta1/azuremachine_types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ type AzureMachineSpec struct {
132132
// +optional
133133
DNSServers []string `json:"dnsServers,omitempty"`
134134

135+
// DisableExtensionOperations specifies whether extension operations should be disabled on the virtual machine.
136+
// Use this setting only if VMExtensions are not supported by your image, as it disables CAPZ bootstrapping extension used for detecting Kubernetes bootstrap failure.
137+
// This may only be set to True when no extensions are configured on the virtual machine.
138+
// +optional
139+
DisableExtensionOperations *bool `json:"disableExtensionOperations,omitempty"`
140+
135141
// VMExtensions specifies a list of extensions to be added to the virtual machine.
136142
// +optional
137143
VMExtensions []VMExtension `json:"vmExtensions,omitempty"`

api/v1beta1/azuremachine_validation.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/google/uuid"
2525
"golang.org/x/crypto/ssh"
2626
"k8s.io/apimachinery/pkg/util/validation/field"
27+
"k8s.io/utils/ptr"
2728
azureutil "sigs.k8s.io/cluster-api-provider-azure/util/azure"
2829
)
2930

@@ -71,6 +72,10 @@ func ValidateAzureMachineSpec(spec AzureMachineSpec) field.ErrorList {
7172
allErrs = append(allErrs, errs...)
7273
}
7374

75+
if errs := ValidateVMExtensions(spec.DisableExtensionOperations, spec.VMExtensions, field.NewPath("vmExtensions")); len(errs) > 0 {
76+
allErrs = append(allErrs, errs...)
77+
}
78+
7479
return allErrs
7580
}
7681

@@ -471,3 +476,14 @@ func ValidateCapacityReservationGroupID(capacityReservationGroupID *string, fldP
471476

472477
return allErrs
473478
}
479+
480+
// ValidateVMExtensions validates the VMExtensions spec.
481+
func ValidateVMExtensions(disableExtensionOperations *bool, vmExtensions []VMExtension, fldPath *field.Path) field.ErrorList {
482+
allErrs := field.ErrorList{}
483+
484+
if ptr.Deref(disableExtensionOperations, false) && len(vmExtensions) > 0 {
485+
allErrs = append(allErrs, field.Forbidden(field.NewPath("AzureMachineTemplate", "spec", "template", "spec", "VMExtensions"), "VMExtensions must be empty when DisableExtensionOperations is true"))
486+
}
487+
488+
return allErrs
489+
}

api/v1beta1/azuremachine_webhook.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,13 @@ func (mw *azureMachineWebhook) ValidateUpdate(ctx context.Context, oldObj, newOb
213213
allErrs = append(allErrs, err)
214214
}
215215

216+
if err := webhookutils.ValidateImmutable(
217+
field.NewPath("spec", "disableExtensionOperations"),
218+
old.Spec.DisableExtensionOperations,
219+
m.Spec.DisableExtensionOperations); err != nil {
220+
allErrs = append(allErrs, err)
221+
}
222+
216223
if len(allErrs) == 0 {
217224
return nil, nil
218225
}

api/v1beta1/azuremachine_webhook_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,16 @@ func TestAzureMachine_ValidateCreate(t *testing.T) {
230230
machine: createMachineWithCapacityReservaionGroupID("invalid-capacity-group-id"),
231231
wantErr: true,
232232
},
233+
{
234+
name: "azuremachine with DisableExtensionOperations true and without VMExtensions",
235+
machine: createMachineWithDisableExtenionOperations(),
236+
wantErr: false,
237+
},
238+
{
239+
name: "azuremachine with DisableExtensionOperations true and with VMExtension",
240+
machine: createMachineWithDisableExtenionOperationsAndHasExtension(),
241+
wantErr: true,
242+
},
233243
}
234244
for _, tc := range tests {
235245
t.Run(tc.name, func(t *testing.T) {
@@ -778,6 +788,34 @@ func TestAzureMachine_ValidateUpdate(t *testing.T) {
778788
},
779789
wantErr: true,
780790
},
791+
{
792+
name: "invalidTest: azuremachine.spec.disableExtensionOperations is immutable",
793+
oldMachine: &AzureMachine{
794+
Spec: AzureMachineSpec{
795+
DisableExtensionOperations: ptr.To(true),
796+
},
797+
},
798+
newMachine: &AzureMachine{
799+
Spec: AzureMachineSpec{
800+
DisableExtensionOperations: ptr.To(false),
801+
},
802+
},
803+
wantErr: true,
804+
},
805+
{
806+
name: "validTest: azuremachine.spec.disableExtensionOperations is immutable",
807+
oldMachine: &AzureMachine{
808+
Spec: AzureMachineSpec{
809+
DisableExtensionOperations: ptr.To(true),
810+
},
811+
},
812+
newMachine: &AzureMachine{
813+
Spec: AzureMachineSpec{
814+
DisableExtensionOperations: ptr.To(true),
815+
},
816+
},
817+
wantErr: false,
818+
},
781819
{
782820
name: "validTest: azuremachine.spec.networkInterfaces is immutable",
783821
oldMachine: &AzureMachine{
@@ -1154,3 +1192,28 @@ func createMachineWithCapacityReservaionGroupID(capacityReservationGroupID strin
11541192
},
11551193
}
11561194
}
1195+
1196+
func createMachineWithDisableExtenionOperationsAndHasExtension() *AzureMachine {
1197+
return &AzureMachine{
1198+
Spec: AzureMachineSpec{
1199+
SSHPublicKey: validSSHPublicKey,
1200+
OSDisk: validOSDisk,
1201+
DisableExtensionOperations: ptr.To(true),
1202+
VMExtensions: []VMExtension{{
1203+
Name: "test-extension",
1204+
Publisher: "test-publiher",
1205+
Version: "v0.0.1-test",
1206+
}},
1207+
},
1208+
}
1209+
}
1210+
1211+
func createMachineWithDisableExtenionOperations() *AzureMachine {
1212+
return &AzureMachine{
1213+
Spec: AzureMachineSpec{
1214+
SSHPublicKey: validSSHPublicKey,
1215+
OSDisk: validOSDisk,
1216+
DisableExtensionOperations: ptr.To(true),
1217+
},
1218+
}
1219+
}

api/v1beta1/azuremachinetemplate_webhook.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
apierrors "k8s.io/apimachinery/pkg/api/errors"
2525
"k8s.io/apimachinery/pkg/runtime"
2626
"k8s.io/apimachinery/pkg/util/validation/field"
27+
"k8s.io/utils/ptr"
2728
"sigs.k8s.io/cluster-api/util/topology"
2829
ctrl "sigs.k8s.io/controller-runtime"
2930
"sigs.k8s.io/controller-runtime/pkg/webhook"
@@ -85,6 +86,10 @@ func (r *AzureMachineTemplate) ValidateCreate(ctx context.Context, obj runtime.O
8586
}
8687
}
8788

89+
if ptr.Deref(r.Spec.Template.Spec.DisableExtensionOperations, false) && len(r.Spec.Template.Spec.VMExtensions) > 0 {
90+
allErrs = append(allErrs, field.Forbidden(field.NewPath("AzureMachineTemplate", "spec", "template", "spec", "VMExtensions"), "VMExtensions must be empty when DisableExtensionOperations is true"))
91+
}
92+
8893
if len(allErrs) == 0 {
8994
return nil, nil
9095
}

api/v1beta1/azuremachinetemplate_webhook_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,16 @@ func TestAzureMachineTemplate_ValidateCreate(t *testing.T) {
143143
machineTemplate: createAzureMachineTemplateFromMachine(createMachineWithRoleAssignmentName()),
144144
wantErr: true,
145145
},
146+
{
147+
name: "azuremachinetemplate with DisableExtensionOperations true and without VMExtensions",
148+
machineTemplate: createAzureMachineTemplateFromMachine(createMachineWithDisableExtenionOperations()),
149+
wantErr: false,
150+
},
151+
{
152+
name: "azuremachinetempalte with DisableExtensionOperations true and with VMExtension",
153+
machineTemplate: createAzureMachineTemplateFromMachine(createMachineWithDisableExtenionOperationsAndHasExtension()),
154+
wantErr: true,
155+
},
146156
{
147157
name: "azuremachinetemplate without RoleAssignmentName",
148158
machineTemplate: createAzureMachineTemplateFromMachine(createMachineWithoutRoleAssignmentName()),

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

azure/scope/machine.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ func (m *MachineScope) VMSpec() azure.ResourceSpecGetter {
178178
SpotVMOptions: m.AzureMachine.Spec.SpotVMOptions,
179179
SecurityProfile: m.AzureMachine.Spec.SecurityProfile,
180180
DiagnosticsProfile: m.AzureMachine.Spec.Diagnostics,
181+
DisableExtensionOperations: ptr.Deref(m.AzureMachine.Spec.DisableExtensionOperations, false),
181182
AdditionalTags: m.AdditionalTags(),
182183
AdditionalCapabilities: m.AzureMachine.Spec.AdditionalCapabilities,
183184
CapacityReservationGroupID: m.GetCapacityReservationGroupID(),
@@ -374,6 +375,10 @@ func (m *MachineScope) HasSystemAssignedIdentity() bool {
374375

375376
// VMExtensionSpecs returns the VM extension specs.
376377
func (m *MachineScope) VMExtensionSpecs() []azure.ResourceSpecGetter {
378+
if ptr.Deref(m.AzureMachine.Spec.DisableExtensionOperations, false) {
379+
return []azure.ResourceSpecGetter{}
380+
}
381+
377382
var extensionSpecs = []azure.ResourceSpecGetter{}
378383
for _, extension := range m.AzureMachine.Spec.VMExtensions {
379384
extensionSpecs = append(extensionSpecs, &vmextensions.VMExtensionSpec{

azure/scope/machine_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,44 @@ func TestMachineScope_VMExtensionSpecs(t *testing.T) {
598598
},
599599
},
600600
},
601+
{
602+
name: "If OS type is Linux and cloud is AzurePublicCloud and DisableExtensionOperations is true, it returns empty",
603+
machineScope: MachineScope{
604+
Machine: &clusterv1.Machine{},
605+
AzureMachine: &infrav1.AzureMachine{
606+
ObjectMeta: metav1.ObjectMeta{
607+
Name: "machine-name",
608+
},
609+
Spec: infrav1.AzureMachineSpec{
610+
DisableExtensionOperations: ptr.To(true),
611+
OSDisk: infrav1.OSDisk{
612+
OSType: "Linux",
613+
},
614+
},
615+
},
616+
ClusterScoper: &ClusterScope{
617+
AzureClients: AzureClients{
618+
EnvironmentSettings: auth.EnvironmentSettings{
619+
Environment: azureautorest.Environment{
620+
Name: azureautorest.PublicCloud.Name,
621+
},
622+
},
623+
},
624+
AzureCluster: &infrav1.AzureCluster{
625+
Spec: infrav1.AzureClusterSpec{
626+
ResourceGroup: "my-rg",
627+
AzureClusterClassSpec: infrav1.AzureClusterClassSpec{
628+
Location: "westus",
629+
},
630+
},
631+
},
632+
},
633+
cache: &MachineCache{
634+
VMSKU: resourceskus.SKU{},
635+
},
636+
},
637+
want: []azure.ResourceSpecGetter{},
638+
},
601639
{
602640
name: "If OS type is Linux and cloud is not AzurePublicCloud, it returns empty",
603641
machineScope: MachineScope{

azure/services/virtualmachines/spec.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type VMSpec struct {
5353
AdditionalTags infrav1.Tags
5454
AdditionalCapabilities *infrav1.AdditionalCapabilities
5555
DiagnosticsProfile *infrav1.Diagnostics
56+
DisableExtensionOperations bool
5657
CapacityReservationGroupID string
5758
SKU resourceskus.SKU
5859
Image *infrav1.Image
@@ -263,9 +264,10 @@ func (s *VMSpec) generateOSProfile() (*armcompute.OSProfile, error) {
263264
}
264265

265266
osProfile := &armcompute.OSProfile{
266-
ComputerName: ptr.To(s.Name),
267-
AdminUsername: ptr.To(azure.DefaultUserName),
268-
CustomData: ptr.To(s.BootstrapData),
267+
ComputerName: ptr.To(s.Name),
268+
AdminUsername: ptr.To(azure.DefaultUserName),
269+
CustomData: ptr.To(s.BootstrapData),
270+
AllowExtensionOperations: ptr.To(!s.DisableExtensionOperations),
269271
}
270272

271273
switch s.OSDisk.OSType {

0 commit comments

Comments
 (0)