sigs.k8s.io/cluster-api-provider-azure@v1.17.0/azure/services/scalesets/spec.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package scalesets
    18  
    19  import (
    20  	"context"
    21  	"encoding/base64"
    22  	"fmt"
    23  	"strconv"
    24  
    25  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
    26  	"github.com/pkg/errors"
    27  	"k8s.io/utils/ptr"
    28  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    29  	"sigs.k8s.io/cluster-api-provider-azure/azure"
    30  	"sigs.k8s.io/cluster-api-provider-azure/azure/converters"
    31  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/resourceskus"
    32  	"sigs.k8s.io/cluster-api-provider-azure/util/generators"
    33  	"sigs.k8s.io/cluster-api-provider-azure/util/tele"
    34  )
    35  
    36  // ScaleSetSpec defines the specification for a Scale Set.
    37  type ScaleSetSpec struct {
    38  	Name                         string
    39  	ResourceGroup                string
    40  	Size                         string
    41  	Capacity                     int64
    42  	SSHKeyData                   string
    43  	OSDisk                       infrav1.OSDisk
    44  	DataDisks                    []infrav1.DataDisk
    45  	SubnetName                   string
    46  	VNetName                     string
    47  	VNetResourceGroup            string
    48  	PublicLBName                 string
    49  	PublicLBAddressPoolName      string
    50  	AcceleratedNetworking        *bool
    51  	TerminateNotificationTimeout *int
    52  	Identity                     infrav1.VMIdentity
    53  	UserAssignedIdentities       []infrav1.UserAssignedIdentity
    54  	SecurityProfile              *infrav1.SecurityProfile
    55  	SpotVMOptions                *infrav1.SpotVMOptions
    56  	AdditionalCapabilities       *infrav1.AdditionalCapabilities
    57  	DiagnosticsProfile           *infrav1.Diagnostics
    58  	FailureDomains               []string
    59  	VMExtensions                 []infrav1.VMExtension
    60  	NetworkInterfaces            []infrav1.NetworkInterface
    61  	IPv6Enabled                  bool
    62  	OrchestrationMode            infrav1.OrchestrationModeType
    63  	Location                     string
    64  	SubscriptionID               string
    65  	SKU                          resourceskus.SKU
    66  	VMSSExtensionSpecs           []azure.ResourceSpecGetter
    67  	VMImage                      *infrav1.Image
    68  	BootstrapData                string
    69  	VMSSInstances                []armcompute.VirtualMachineScaleSetVM
    70  	MaxSurge                     int
    71  	ClusterName                  string
    72  	ShouldPatchCustomData        bool
    73  	HasReplicasExternallyManaged bool
    74  	AdditionalTags               infrav1.Tags
    75  	PlatformFaultDomainCount     *int32
    76  	ZoneBalance                  *bool
    77  }
    78  
    79  // ResourceName returns the name of the Scale Set.
    80  func (s *ScaleSetSpec) ResourceName() string {
    81  	return s.Name
    82  }
    83  
    84  // ResourceGroupName returns the name of the resource group for this Scale Set.
    85  func (s *ScaleSetSpec) ResourceGroupName() string {
    86  	return s.ResourceGroup
    87  }
    88  
    89  // OwnerResourceName is a no-op for Scale Sets.
    90  func (s *ScaleSetSpec) OwnerResourceName() string {
    91  	return ""
    92  }
    93  
    94  func (s *ScaleSetSpec) existingParameters(ctx context.Context, existing interface{}) (parameters interface{}, err error) {
    95  	existingVMSS, ok := existing.(armcompute.VirtualMachineScaleSet)
    96  	if !ok {
    97  		return nil, errors.Errorf("%T is not an armcompute.VirtualMachineScaleSet", existing)
    98  	}
    99  
   100  	existingInfraVMSS := converters.SDKToVMSS(existingVMSS, s.VMSSInstances)
   101  
   102  	params, err := s.Parameters(ctx, nil)
   103  	if err != nil {
   104  		return nil, errors.Wrapf(err, "failed to generate scale set update parameters for %s", s.Name)
   105  	}
   106  
   107  	vmss, ok := params.(armcompute.VirtualMachineScaleSet)
   108  	if !ok {
   109  		return nil, errors.Errorf("%T is not an armcompute.VirtualMachineScaleSet", existing)
   110  	}
   111  
   112  	vmss.Properties.VirtualMachineProfile.NetworkProfile = nil
   113  	vmss.ID = existingVMSS.ID
   114  
   115  	hasModelChanges := hasModelModifyingDifferences(&existingInfraVMSS, vmss)
   116  	isFlex := s.OrchestrationMode == infrav1.FlexibleOrchestrationMode
   117  	updated := true
   118  	if !isFlex {
   119  		updated = existingInfraVMSS.HasEnoughLatestModelOrNotMixedModel()
   120  	}
   121  	if s.MaxSurge > 0 && (hasModelChanges || !updated) && !s.HasReplicasExternallyManaged {
   122  		// surge capacity with the intention of lowering during instance reconciliation
   123  		surge := s.Capacity + int64(s.MaxSurge)
   124  		vmss.SKU.Capacity = ptr.To[int64](surge)
   125  	}
   126  
   127  	// If there are no model changes and no increase in the replica count, do not update the VMSS.
   128  	// Decreases in replica count is handled by deleting AzureMachinePoolMachine instances in the MachinePoolScope
   129  	if *vmss.SKU.Capacity <= existingInfraVMSS.Capacity && !hasModelChanges && !s.ShouldPatchCustomData {
   130  		// up to date, nothing to do
   131  		return nil, nil
   132  	}
   133  
   134  	return vmss, nil
   135  }
   136  
   137  // Parameters returns the parameters for the Scale Set.
   138  func (s *ScaleSetSpec) Parameters(ctx context.Context, existing interface{}) (parameters interface{}, err error) {
   139  	if existing != nil {
   140  		return s.existingParameters(ctx, existing)
   141  	}
   142  
   143  	if s.AcceleratedNetworking == nil {
   144  		// set accelerated networking to the capability of the VMSize
   145  		accelNet := s.SKU.HasCapability(resourceskus.AcceleratedNetworking)
   146  		s.AcceleratedNetworking = &accelNet
   147  	}
   148  
   149  	extensions, err := s.generateExtensions(ctx)
   150  	if err != nil {
   151  		return armcompute.VirtualMachineScaleSet{}, err
   152  	}
   153  
   154  	storageProfile, err := s.generateStorageProfile(ctx)
   155  	if err != nil {
   156  		return armcompute.VirtualMachineScaleSet{}, err
   157  	}
   158  
   159  	securityProfile, err := s.getSecurityProfile()
   160  	if err != nil {
   161  		return armcompute.VirtualMachineScaleSet{}, err
   162  	}
   163  
   164  	priority, evictionPolicy, billingProfile, err := converters.GetSpotVMOptions(s.SpotVMOptions, s.OSDisk.DiffDiskSettings)
   165  	if err != nil {
   166  		return armcompute.VirtualMachineScaleSet{}, errors.Wrapf(err, "failed to get Spot VM options")
   167  	}
   168  
   169  	diagnosticsProfile := converters.GetDiagnosticsProfile(s.DiagnosticsProfile)
   170  
   171  	osProfile, err := s.generateOSProfile(ctx)
   172  	if err != nil {
   173  		return armcompute.VirtualMachineScaleSet{}, err
   174  	}
   175  
   176  	orchestrationMode := converters.GetOrchestrationMode(s.OrchestrationMode)
   177  
   178  	vmss := armcompute.VirtualMachineScaleSet{
   179  		Location: ptr.To(s.Location),
   180  		SKU: &armcompute.SKU{
   181  			Name:     ptr.To(s.Size),
   182  			Tier:     ptr.To("Standard"),
   183  			Capacity: ptr.To[int64](s.Capacity),
   184  		},
   185  		Zones: azure.PtrSlice(&s.FailureDomains),
   186  		Plan:  s.generateImagePlan(ctx),
   187  		Properties: &armcompute.VirtualMachineScaleSetProperties{
   188  			OrchestrationMode:    ptr.To(orchestrationMode),
   189  			SinglePlacementGroup: ptr.To(false),
   190  			VirtualMachineProfile: &armcompute.VirtualMachineScaleSetVMProfile{
   191  				OSProfile:          osProfile,
   192  				StorageProfile:     storageProfile,
   193  				SecurityProfile:    securityProfile,
   194  				DiagnosticsProfile: diagnosticsProfile,
   195  				NetworkProfile: &armcompute.VirtualMachineScaleSetNetworkProfile{
   196  					NetworkInterfaceConfigurations: azure.PtrSlice(s.getVirtualMachineScaleSetNetworkConfiguration()),
   197  				},
   198  				Priority:       priority,
   199  				EvictionPolicy: evictionPolicy,
   200  				BillingProfile: billingProfile,
   201  				ExtensionProfile: &armcompute.VirtualMachineScaleSetExtensionProfile{
   202  					Extensions: azure.PtrSlice(&extensions),
   203  				},
   204  			},
   205  		},
   206  	}
   207  
   208  	// Set properties specific to VMSS orchestration mode
   209  	// See https://learn.microsoft.com/en-us/azure/virtual-machine-scale-sets/virtual-machine-scale-sets-orchestration-modes for more details
   210  	switch orchestrationMode {
   211  	case armcompute.OrchestrationModeUniform: // Uniform VMSS
   212  		vmss.Properties.Overprovision = ptr.To(false)
   213  		vmss.Properties.UpgradePolicy = &armcompute.UpgradePolicy{Mode: ptr.To(armcompute.UpgradeModeManual)}
   214  	case armcompute.OrchestrationModeFlexible: // VMSS Flex, VMs are treated as individual virtual machines
   215  		vmss.Properties.VirtualMachineProfile.NetworkProfile.NetworkAPIVersion =
   216  			ptr.To(armcompute.NetworkAPIVersionTwoThousandTwenty1101)
   217  		vmss.Properties.PlatformFaultDomainCount = ptr.To[int32](1)
   218  	}
   219  
   220  	if s.PlatformFaultDomainCount != nil {
   221  		vmss.Properties.PlatformFaultDomainCount = ptr.To[int32](*s.PlatformFaultDomainCount)
   222  	}
   223  
   224  	if s.ZoneBalance != nil {
   225  		vmss.Properties.ZoneBalance = s.ZoneBalance
   226  	}
   227  
   228  	// Assign Identity to VMSS
   229  	if s.Identity == infrav1.VMIdentitySystemAssigned {
   230  		vmss.Identity = &armcompute.VirtualMachineScaleSetIdentity{
   231  			Type: ptr.To(armcompute.ResourceIdentityTypeSystemAssigned),
   232  		}
   233  	} else if s.Identity == infrav1.VMIdentityUserAssigned {
   234  		userIdentitiesMap, err := converters.UserAssignedIdentitiesToVMSSSDK(s.UserAssignedIdentities)
   235  		if err != nil {
   236  			return vmss, errors.Wrapf(err, "failed to assign identity %q", s.Name)
   237  		}
   238  		vmss.Identity = &armcompute.VirtualMachineScaleSetIdentity{
   239  			Type:                   ptr.To(armcompute.ResourceIdentityTypeUserAssigned),
   240  			UserAssignedIdentities: userIdentitiesMap,
   241  		}
   242  	}
   243  
   244  	// Provisionally detect whether there is any Data Disk defined which uses UltraSSDs.
   245  	// If that's the case, enable the UltraSSD capability.
   246  	for _, dataDisk := range s.DataDisks {
   247  		if dataDisk.ManagedDisk != nil && dataDisk.ManagedDisk.StorageAccountType == string(armcompute.StorageAccountTypesUltraSSDLRS) {
   248  			vmss.Properties.AdditionalCapabilities = &armcompute.AdditionalCapabilities{
   249  				UltraSSDEnabled: ptr.To(true),
   250  			}
   251  		}
   252  	}
   253  
   254  	// Set Additional Capabilities if any is present on the spec.
   255  	if s.AdditionalCapabilities != nil {
   256  		// Set UltraSSDEnabled if a specific value is set on the spec for it.
   257  		if s.AdditionalCapabilities.UltraSSDEnabled != nil {
   258  			vmss.Properties.AdditionalCapabilities.UltraSSDEnabled = s.AdditionalCapabilities.UltraSSDEnabled
   259  		}
   260  	}
   261  
   262  	if s.TerminateNotificationTimeout != nil {
   263  		vmss.Properties.VirtualMachineProfile.ScheduledEventsProfile = &armcompute.ScheduledEventsProfile{
   264  			TerminateNotificationProfile: &armcompute.TerminateNotificationProfile{
   265  				NotBeforeTimeout: ptr.To(fmt.Sprintf("PT%dM", *s.TerminateNotificationTimeout)),
   266  				Enable:           ptr.To(true),
   267  			},
   268  		}
   269  	}
   270  
   271  	tags := infrav1.Build(infrav1.BuildParams{
   272  		ClusterName: s.ClusterName,
   273  		Lifecycle:   infrav1.ResourceLifecycleOwned,
   274  		Name:        ptr.To(s.Name),
   275  		Role:        ptr.To(infrav1.Node),
   276  		Additional:  s.AdditionalTags,
   277  	})
   278  
   279  	vmss.Tags = converters.TagsToMap(tags)
   280  	return vmss, nil
   281  }
   282  
   283  func hasModelModifyingDifferences(infraVMSS *azure.VMSS, vmss armcompute.VirtualMachineScaleSet) bool {
   284  	other := converters.SDKToVMSS(vmss, []armcompute.VirtualMachineScaleSetVM{})
   285  	return infraVMSS.HasModelChanges(other)
   286  }
   287  
   288  func (s *ScaleSetSpec) generateExtensions(ctx context.Context) ([]armcompute.VirtualMachineScaleSetExtension, error) {
   289  	extensions := make([]armcompute.VirtualMachineScaleSetExtension, len(s.VMSSExtensionSpecs))
   290  	for i, extensionSpec := range s.VMSSExtensionSpecs {
   291  		extensionSpec := extensionSpec
   292  		parameters, err := extensionSpec.Parameters(ctx, nil)
   293  		if err != nil {
   294  			return nil, err
   295  		}
   296  		vmssextension, ok := parameters.(armcompute.VirtualMachineScaleSetExtension)
   297  		if !ok {
   298  			return nil, errors.Errorf("%T is not an armcompute.VirtualMachineScaleSetExtension", parameters)
   299  		}
   300  		extensions[i] = vmssextension
   301  	}
   302  
   303  	return extensions, nil
   304  }
   305  
   306  func (s *ScaleSetSpec) getVirtualMachineScaleSetNetworkConfiguration() *[]armcompute.VirtualMachineScaleSetNetworkConfiguration {
   307  	var backendAddressPools []armcompute.SubResource
   308  	if s.PublicLBName != "" {
   309  		if s.PublicLBAddressPoolName != "" {
   310  			backendAddressPools = append(backendAddressPools,
   311  				armcompute.SubResource{
   312  					ID: ptr.To(azure.AddressPoolID(s.SubscriptionID, s.ResourceGroup, s.PublicLBName, s.PublicLBAddressPoolName)),
   313  				})
   314  		}
   315  	}
   316  	nicConfigs := []armcompute.VirtualMachineScaleSetNetworkConfiguration{}
   317  	for i, n := range s.NetworkInterfaces {
   318  		nicConfig := armcompute.VirtualMachineScaleSetNetworkConfiguration{}
   319  		nicConfig.Properties = &armcompute.VirtualMachineScaleSetNetworkConfigurationProperties{}
   320  		nicConfig.Name = ptr.To(s.Name + "-nic-" + strconv.Itoa(i))
   321  		nicConfig.Properties.EnableIPForwarding = ptr.To(true)
   322  		if n.AcceleratedNetworking != nil {
   323  			nicConfig.Properties.EnableAcceleratedNetworking = n.AcceleratedNetworking
   324  		} else {
   325  			// If AcceleratedNetworking is not specified, use the value from the VMSS spec.
   326  			// It will be set to true if the VMSS SKU supports it.
   327  			nicConfig.Properties.EnableAcceleratedNetworking = s.AcceleratedNetworking
   328  		}
   329  
   330  		// Create IPConfigs
   331  		ipconfigs := []armcompute.VirtualMachineScaleSetIPConfiguration{}
   332  		for j := 0; j < n.PrivateIPConfigs; j++ {
   333  			ipconfig := armcompute.VirtualMachineScaleSetIPConfiguration{
   334  				Name: ptr.To(fmt.Sprintf("ipConfig" + strconv.Itoa(j))),
   335  				Properties: &armcompute.VirtualMachineScaleSetIPConfigurationProperties{
   336  					PrivateIPAddressVersion: ptr.To(armcompute.IPVersionIPv4),
   337  					Subnet: &armcompute.APIEntityReference{
   338  						ID: ptr.To(azure.SubnetID(s.SubscriptionID, s.VNetResourceGroup, s.VNetName, n.SubnetName)),
   339  					},
   340  				},
   341  			}
   342  
   343  			if j == 0 {
   344  				// Always use the first IPConfig as the Primary
   345  				ipconfig.Properties.Primary = ptr.To(true)
   346  			}
   347  			ipconfigs = append(ipconfigs, ipconfig)
   348  		}
   349  		if s.IPv6Enabled {
   350  			ipv6Config := armcompute.VirtualMachineScaleSetIPConfiguration{
   351  				Name: ptr.To("ipConfigv6"),
   352  				Properties: &armcompute.VirtualMachineScaleSetIPConfigurationProperties{
   353  					PrivateIPAddressVersion: ptr.To(armcompute.IPVersionIPv6),
   354  					Primary:                 ptr.To(false),
   355  					Subnet: &armcompute.APIEntityReference{
   356  						ID: ptr.To(azure.SubnetID(s.SubscriptionID, s.VNetResourceGroup, s.VNetName, n.SubnetName)),
   357  					},
   358  				},
   359  			}
   360  			ipconfigs = append(ipconfigs, ipv6Config)
   361  		}
   362  		if i == 0 {
   363  			ipconfigs[0].Properties.LoadBalancerBackendAddressPools = azure.PtrSlice(&backendAddressPools)
   364  			nicConfig.Properties.Primary = ptr.To(true)
   365  		}
   366  		nicConfig.Properties.IPConfigurations = azure.PtrSlice(&ipconfigs)
   367  		nicConfigs = append(nicConfigs, nicConfig)
   368  	}
   369  	return &nicConfigs
   370  }
   371  
   372  // generateStorageProfile generates a pointer to an armcompute.VirtualMachineScaleSetStorageProfile which can utilized for VM creation.
   373  func (s *ScaleSetSpec) generateStorageProfile(ctx context.Context) (*armcompute.VirtualMachineScaleSetStorageProfile, error) {
   374  	_, _, done := tele.StartSpanWithLogger(ctx, "scalesets.ScaleSetSpec.generateStorageProfile")
   375  	defer done()
   376  
   377  	storageProfile := &armcompute.VirtualMachineScaleSetStorageProfile{
   378  		OSDisk: &armcompute.VirtualMachineScaleSetOSDisk{
   379  			OSType:       ptr.To(armcompute.OperatingSystemTypes(s.OSDisk.OSType)),
   380  			CreateOption: ptr.To(armcompute.DiskCreateOptionTypesFromImage),
   381  			DiskSizeGB:   s.OSDisk.DiskSizeGB,
   382  		},
   383  	}
   384  
   385  	// enable ephemeral OS
   386  	if s.OSDisk.DiffDiskSettings != nil {
   387  		if !s.SKU.HasCapability(resourceskus.EphemeralOSDisk) {
   388  			return nil, fmt.Errorf("vm size %s does not support ephemeral os. select a different vm size or disable ephemeral os", s.Size)
   389  		}
   390  
   391  		storageProfile.OSDisk.DiffDiskSettings = &armcompute.DiffDiskSettings{
   392  			Option: ptr.To(armcompute.DiffDiskOptions(s.OSDisk.DiffDiskSettings.Option)),
   393  		}
   394  
   395  		if s.OSDisk.DiffDiskSettings.Placement != nil {
   396  			storageProfile.OSDisk.DiffDiskSettings.Placement = ptr.To(armcompute.DiffDiskPlacement(*s.OSDisk.DiffDiskSettings.Placement))
   397  		}
   398  	}
   399  
   400  	if s.OSDisk.ManagedDisk != nil {
   401  		storageProfile.OSDisk.ManagedDisk = &armcompute.VirtualMachineScaleSetManagedDiskParameters{}
   402  		if s.OSDisk.ManagedDisk.StorageAccountType != "" {
   403  			storageProfile.OSDisk.ManagedDisk.StorageAccountType = ptr.To(armcompute.StorageAccountTypes(s.OSDisk.ManagedDisk.StorageAccountType))
   404  		}
   405  		if s.OSDisk.ManagedDisk.DiskEncryptionSet != nil {
   406  			storageProfile.OSDisk.ManagedDisk.DiskEncryptionSet = &armcompute.DiskEncryptionSetParameters{ID: ptr.To(s.OSDisk.ManagedDisk.DiskEncryptionSet.ID)}
   407  		}
   408  	}
   409  
   410  	if s.OSDisk.CachingType != "" {
   411  		storageProfile.OSDisk.Caching = ptr.To(armcompute.CachingTypes(s.OSDisk.CachingType))
   412  	}
   413  
   414  	dataDisks := make([]armcompute.VirtualMachineScaleSetDataDisk, len(s.DataDisks))
   415  	for i, disk := range s.DataDisks {
   416  		dataDisks[i] = armcompute.VirtualMachineScaleSetDataDisk{
   417  			CreateOption: ptr.To(armcompute.DiskCreateOptionTypesEmpty),
   418  			DiskSizeGB:   ptr.To[int32](disk.DiskSizeGB),
   419  			Lun:          disk.Lun,
   420  			Name:         ptr.To(azure.GenerateDataDiskName(s.Name, disk.NameSuffix)),
   421  		}
   422  
   423  		if disk.ManagedDisk != nil {
   424  			dataDisks[i].ManagedDisk = &armcompute.VirtualMachineScaleSetManagedDiskParameters{
   425  				StorageAccountType: ptr.To(armcompute.StorageAccountTypes(disk.ManagedDisk.StorageAccountType)),
   426  			}
   427  
   428  			if disk.ManagedDisk.DiskEncryptionSet != nil {
   429  				dataDisks[i].ManagedDisk.DiskEncryptionSet = &armcompute.DiskEncryptionSetParameters{ID: ptr.To(disk.ManagedDisk.DiskEncryptionSet.ID)}
   430  			}
   431  		}
   432  	}
   433  	storageProfile.DataDisks = azure.PtrSlice(&dataDisks)
   434  
   435  	if s.VMImage == nil {
   436  		return nil, errors.Errorf("vm image is nil")
   437  	}
   438  	imageRef, err := converters.ImageToSDK(s.VMImage)
   439  	if err != nil {
   440  		return nil, err
   441  	}
   442  
   443  	storageProfile.ImageReference = imageRef
   444  
   445  	return storageProfile, nil
   446  }
   447  
   448  func (s *ScaleSetSpec) generateOSProfile(_ context.Context) (*armcompute.VirtualMachineScaleSetOSProfile, error) {
   449  	sshKey, err := base64.StdEncoding.DecodeString(s.SSHKeyData)
   450  	if err != nil {
   451  		return nil, errors.Wrap(err, "failed to decode ssh public key")
   452  	}
   453  
   454  	osProfile := &armcompute.VirtualMachineScaleSetOSProfile{
   455  		ComputerNamePrefix: ptr.To(s.Name),
   456  		AdminUsername:      ptr.To(azure.DefaultUserName),
   457  		CustomData:         ptr.To(s.BootstrapData),
   458  	}
   459  
   460  	switch s.OSDisk.OSType {
   461  	case string(armcompute.OperatingSystemTypesWindows):
   462  		// Cloudbase-init is used to generate a password.
   463  		// https://cloudbase-init.readthedocs.io/en/latest/plugins.html#setting-password-main
   464  		//
   465  		// We generate a random password here in case of failure
   466  		// but the password on the VM will NOT be the same as created here.
   467  		// Access is provided via SSH public key that is set during deployment
   468  		// Azure also provides a way to reset user passwords in the case of need.
   469  		osProfile.AdminPassword = ptr.To(generators.SudoRandomPassword(123))
   470  		osProfile.WindowsConfiguration = &armcompute.WindowsConfiguration{
   471  			EnableAutomaticUpdates: ptr.To(false),
   472  		}
   473  	default:
   474  		osProfile.LinuxConfiguration = &armcompute.LinuxConfiguration{
   475  			DisablePasswordAuthentication: ptr.To(true),
   476  			SSH: &armcompute.SSHConfiguration{
   477  				PublicKeys: []*armcompute.SSHPublicKey{
   478  					{
   479  						Path:    ptr.To(fmt.Sprintf("/home/%s/.ssh/authorized_keys", azure.DefaultUserName)),
   480  						KeyData: ptr.To(string(sshKey)),
   481  					},
   482  				},
   483  			},
   484  		}
   485  	}
   486  
   487  	return osProfile, nil
   488  }
   489  
   490  func (s *ScaleSetSpec) generateImagePlan(ctx context.Context) *armcompute.Plan {
   491  	_, log, done := tele.StartSpanWithLogger(ctx, "scalesets.ScaleSetSpec.generateImagePlan")
   492  	defer done()
   493  
   494  	if s.VMImage == nil {
   495  		log.V(2).Info("no vm image found, disabling plan")
   496  		return nil
   497  	}
   498  
   499  	if s.VMImage.SharedGallery != nil && s.VMImage.SharedGallery.Publisher != nil && s.VMImage.SharedGallery.SKU != nil && s.VMImage.SharedGallery.Offer != nil {
   500  		return &armcompute.Plan{
   501  			Publisher: s.VMImage.SharedGallery.Publisher,
   502  			Name:      s.VMImage.SharedGallery.SKU,
   503  			Product:   s.VMImage.SharedGallery.Offer,
   504  		}
   505  	}
   506  
   507  	if s.VMImage.Marketplace == nil || !s.VMImage.Marketplace.ThirdPartyImage {
   508  		return nil
   509  	}
   510  
   511  	if s.VMImage.Marketplace.Publisher == "" || s.VMImage.Marketplace.SKU == "" || s.VMImage.Marketplace.Offer == "" {
   512  		return nil
   513  	}
   514  
   515  	return &armcompute.Plan{
   516  		Publisher: ptr.To(s.VMImage.Marketplace.Publisher),
   517  		Name:      ptr.To(s.VMImage.Marketplace.SKU),
   518  		Product:   ptr.To(s.VMImage.Marketplace.Offer),
   519  	}
   520  }
   521  
   522  func (s *ScaleSetSpec) getSecurityProfile() (*armcompute.SecurityProfile, error) {
   523  	if s.SecurityProfile == nil {
   524  		return nil, nil
   525  	}
   526  
   527  	if !s.SKU.HasCapability(resourceskus.EncryptionAtHost) {
   528  		return nil, azure.WithTerminalError(errors.Errorf("encryption at host is not supported for VM type %s", s.Size))
   529  	}
   530  
   531  	return &armcompute.SecurityProfile{
   532  		EncryptionAtHost: ptr.To(*s.SecurityProfile.EncryptionAtHost),
   533  	}, nil
   534  }