sigs.k8s.io/cluster-api-provider-azure@v1.17.0/azure/scope/machine.go (about)

     1  /*
     2  Copyright 2018 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 scope
    18  
    19  import (
    20  	"context"
    21  	"encoding/base64"
    22  	"encoding/json"
    23  	"strings"
    24  
    25  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2"
    26  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
    27  	"github.com/pkg/errors"
    28  	corev1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	"k8s.io/utils/ptr"
    31  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    32  	"sigs.k8s.io/cluster-api-provider-azure/azure"
    33  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/availabilitysets"
    34  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/disks"
    35  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/inboundnatrules"
    36  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/networkinterfaces"
    37  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/publicips"
    38  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/resourceskus"
    39  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/roleassignments"
    40  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/virtualmachineimages"
    41  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/virtualmachines"
    42  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/vmextensions"
    43  	azureutil "sigs.k8s.io/cluster-api-provider-azure/util/azure"
    44  	"sigs.k8s.io/cluster-api-provider-azure/util/futures"
    45  	"sigs.k8s.io/cluster-api-provider-azure/util/tele"
    46  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    47  	capierrors "sigs.k8s.io/cluster-api/errors"
    48  	"sigs.k8s.io/cluster-api/util"
    49  	"sigs.k8s.io/cluster-api/util/conditions"
    50  	"sigs.k8s.io/cluster-api/util/patch"
    51  	"sigs.k8s.io/controller-runtime/pkg/client"
    52  )
    53  
    54  // MachineScopeParams defines the input parameters used to create a new MachineScope.
    55  type MachineScopeParams struct {
    56  	Client       client.Client
    57  	ClusterScope azure.ClusterScoper
    58  	Machine      *clusterv1.Machine
    59  	AzureMachine *infrav1.AzureMachine
    60  	Cache        *MachineCache
    61  	SKUCache     SKUCacher
    62  }
    63  
    64  // NewMachineScope creates a new MachineScope from the supplied parameters.
    65  // This is meant to be called for each reconcile iteration.
    66  func NewMachineScope(params MachineScopeParams) (*MachineScope, error) {
    67  	if params.Client == nil {
    68  		return nil, errors.New("client is required when creating a MachineScope")
    69  	}
    70  	if params.Machine == nil {
    71  		return nil, errors.New("machine is required when creating a MachineScope")
    72  	}
    73  	if params.AzureMachine == nil {
    74  		return nil, errors.New("azure machine is required when creating a MachineScope")
    75  	}
    76  
    77  	helper, err := patch.NewHelper(params.AzureMachine, params.Client)
    78  	if err != nil {
    79  		return nil, errors.Wrap(err, "failed to init patch helper")
    80  	}
    81  
    82  	return &MachineScope{
    83  		client:        params.Client,
    84  		Machine:       params.Machine,
    85  		AzureMachine:  params.AzureMachine,
    86  		patchHelper:   helper,
    87  		ClusterScoper: params.ClusterScope,
    88  		cache:         params.Cache,
    89  		skuCache:      params.SKUCache,
    90  	}, nil
    91  }
    92  
    93  // MachineScope defines a scope defined around a machine and its cluster.
    94  type MachineScope struct {
    95  	client      client.Client
    96  	patchHelper *patch.Helper
    97  
    98  	azure.ClusterScoper
    99  	Machine      *clusterv1.Machine
   100  	AzureMachine *infrav1.AzureMachine
   101  	cache        *MachineCache
   102  	skuCache     SKUCacher
   103  }
   104  
   105  // SKUCacher fetches a SKU from its cache.
   106  type SKUCacher interface {
   107  	Get(context.Context, string, resourceskus.ResourceType) (resourceskus.SKU, error)
   108  }
   109  
   110  // MachineCache stores common machine information so we don't have to hit the API multiple times within the same reconcile loop.
   111  type MachineCache struct {
   112  	BootstrapData      string
   113  	VMImage            *infrav1.Image
   114  	VMSKU              resourceskus.SKU
   115  	availabilitySetSKU resourceskus.SKU
   116  }
   117  
   118  // InitMachineCache sets cached information about the machine to be used in the scope.
   119  func (m *MachineScope) InitMachineCache(ctx context.Context) error {
   120  	ctx, _, done := tele.StartSpanWithLogger(ctx, "azure.MachineScope.InitMachineCache")
   121  	defer done()
   122  
   123  	if m.cache == nil {
   124  		var err error
   125  		m.cache = &MachineCache{}
   126  
   127  		m.cache.BootstrapData, err = m.GetBootstrapData(ctx)
   128  		if err != nil {
   129  			return err
   130  		}
   131  
   132  		m.cache.VMImage, err = m.GetVMImage(ctx)
   133  		if err != nil {
   134  			return err
   135  		}
   136  
   137  		skuCache := m.skuCache
   138  		if skuCache == nil {
   139  			cache, err := resourceskus.GetCache(m, m.Location())
   140  			if err != nil {
   141  				return err
   142  			}
   143  			skuCache = cache
   144  		}
   145  
   146  		m.cache.VMSKU, err = skuCache.Get(ctx, m.AzureMachine.Spec.VMSize, resourceskus.VirtualMachines)
   147  		if err != nil {
   148  			return errors.Wrapf(err, "failed to get VM SKU %s in compute api", m.AzureMachine.Spec.VMSize)
   149  		}
   150  
   151  		m.cache.availabilitySetSKU, err = skuCache.Get(ctx, string(armcompute.AvailabilitySetSKUTypesAligned), resourceskus.AvailabilitySets)
   152  		if err != nil {
   153  			return errors.Wrapf(err, "failed to get availability set SKU %s in compute api", string(armcompute.AvailabilitySetSKUTypesAligned))
   154  		}
   155  	}
   156  
   157  	return nil
   158  }
   159  
   160  // VMSpec returns the VM spec.
   161  func (m *MachineScope) VMSpec() azure.ResourceSpecGetter {
   162  	spec := &virtualmachines.VMSpec{
   163  		Name:                       m.Name(),
   164  		Location:                   m.Location(),
   165  		ExtendedLocation:           m.ExtendedLocation(),
   166  		ResourceGroup:              m.NodeResourceGroup(),
   167  		ClusterName:                m.ClusterName(),
   168  		Role:                       m.Role(),
   169  		NICIDs:                     m.NICIDs(),
   170  		SSHKeyData:                 m.AzureMachine.Spec.SSHPublicKey,
   171  		Size:                       m.AzureMachine.Spec.VMSize,
   172  		OSDisk:                     m.AzureMachine.Spec.OSDisk,
   173  		DataDisks:                  m.AzureMachine.Spec.DataDisks,
   174  		AvailabilitySetID:          m.AvailabilitySetID(),
   175  		Zone:                       m.AvailabilityZone(),
   176  		Identity:                   m.AzureMachine.Spec.Identity,
   177  		UserAssignedIdentities:     m.AzureMachine.Spec.UserAssignedIdentities,
   178  		SpotVMOptions:              m.AzureMachine.Spec.SpotVMOptions,
   179  		SecurityProfile:            m.AzureMachine.Spec.SecurityProfile,
   180  		DiagnosticsProfile:         m.AzureMachine.Spec.Diagnostics,
   181  		DisableExtensionOperations: ptr.Deref(m.AzureMachine.Spec.DisableExtensionOperations, false),
   182  		AdditionalTags:             m.AdditionalTags(),
   183  		AdditionalCapabilities:     m.AzureMachine.Spec.AdditionalCapabilities,
   184  		CapacityReservationGroupID: m.GetCapacityReservationGroupID(),
   185  		ProviderID:                 m.ProviderID(),
   186  	}
   187  	if m.cache != nil {
   188  		spec.SKU = m.cache.VMSKU
   189  		spec.Image = m.cache.VMImage
   190  		spec.BootstrapData = m.cache.BootstrapData
   191  	}
   192  	return spec
   193  }
   194  
   195  // TagsSpecs returns the tags for the AzureMachine.
   196  func (m *MachineScope) TagsSpecs() []azure.TagsSpec {
   197  	return []azure.TagsSpec{
   198  		{
   199  			Scope:      azure.VMID(m.SubscriptionID(), m.NodeResourceGroup(), m.Name()),
   200  			Tags:       m.AdditionalTags(),
   201  			Annotation: azure.VMTagsLastAppliedAnnotation,
   202  		},
   203  	}
   204  }
   205  
   206  // PublicIPSpecs returns the public IP specs.
   207  func (m *MachineScope) PublicIPSpecs() []azure.ResourceSpecGetter {
   208  	var specs []azure.ResourceSpecGetter
   209  	if m.AzureMachine.Spec.AllocatePublicIP {
   210  		specs = append(specs, &publicips.PublicIPSpec{
   211  			Name:             azure.GenerateNodePublicIPName(m.Name()),
   212  			ResourceGroup:    m.NodeResourceGroup(),
   213  			ClusterName:      m.ClusterName(),
   214  			DNSName:          "",    // Set to default value
   215  			IsIPv6:           false, // Set to default value
   216  			Location:         m.Location(),
   217  			ExtendedLocation: m.ExtendedLocation(),
   218  			FailureDomains:   m.FailureDomains(),
   219  			AdditionalTags:   m.ClusterScoper.AdditionalTags(),
   220  		})
   221  	}
   222  	return specs
   223  }
   224  
   225  // InboundNatSpecs returns the inbound NAT specs.
   226  func (m *MachineScope) InboundNatSpecs() []azure.ResourceSpecGetter {
   227  	// The existing inbound NAT rules are needed in order to find an available SSH port for each new inbound NAT rule.
   228  	if m.Role() == infrav1.ControlPlane {
   229  		spec := &inboundnatrules.InboundNatSpec{
   230  			Name:                      m.Name(),
   231  			ResourceGroup:             m.NodeResourceGroup(),
   232  			LoadBalancerName:          m.APIServerLBName(),
   233  			FrontendIPConfigurationID: nil,
   234  		}
   235  		if frontEndIPs := m.APIServerLB().FrontendIPs; len(frontEndIPs) > 0 {
   236  			ipConfig := frontEndIPs[0].Name
   237  			id := azure.FrontendIPConfigID(m.SubscriptionID(), m.NodeResourceGroup(), m.APIServerLBName(), ipConfig)
   238  			spec.FrontendIPConfigurationID = ptr.To(id)
   239  		}
   240  
   241  		return []azure.ResourceSpecGetter{spec}
   242  	}
   243  	return []azure.ResourceSpecGetter{}
   244  }
   245  
   246  // NICSpecs returns the network interface specs.
   247  func (m *MachineScope) NICSpecs() []azure.ResourceSpecGetter {
   248  	nicSpecs := []azure.ResourceSpecGetter{}
   249  
   250  	// For backwards compatibility we need to ensure the NIC Name does not change on existing machines
   251  	// created prior to multiple NIC support
   252  	isMultiNIC := len(m.AzureMachine.Spec.NetworkInterfaces) > 1
   253  
   254  	for i := 0; i < len(m.AzureMachine.Spec.NetworkInterfaces); i++ {
   255  		isPrimary := i == 0
   256  		nicName := azure.GenerateNICName(m.Name(), isMultiNIC, i)
   257  		nicSpecs = append(nicSpecs, m.BuildNICSpec(nicName, m.AzureMachine.Spec.NetworkInterfaces[i], isPrimary))
   258  	}
   259  	return nicSpecs
   260  }
   261  
   262  // BuildNICSpec takes a NetworkInterface from the AzureMachineSpec and returns a NICSpec for use by the networkinterfaces service.
   263  func (m *MachineScope) BuildNICSpec(nicName string, infrav1NetworkInterface infrav1.NetworkInterface, primaryNetworkInterface bool) *networkinterfaces.NICSpec {
   264  	spec := &networkinterfaces.NICSpec{
   265  		Name:                  nicName,
   266  		ResourceGroup:         m.NodeResourceGroup(),
   267  		Location:              m.Location(),
   268  		ExtendedLocation:      m.ExtendedLocation(),
   269  		SubscriptionID:        m.SubscriptionID(),
   270  		MachineName:           m.Name(),
   271  		VNetName:              m.Vnet().Name,
   272  		VNetResourceGroup:     m.Vnet().ResourceGroup,
   273  		AcceleratedNetworking: infrav1NetworkInterface.AcceleratedNetworking,
   274  		IPv6Enabled:           m.IsIPv6Enabled(),
   275  		EnableIPForwarding:    m.AzureMachine.Spec.EnableIPForwarding,
   276  		SubnetName:            infrav1NetworkInterface.SubnetName,
   277  		AdditionalTags:        m.AdditionalTags(),
   278  		ClusterName:           m.ClusterName(),
   279  		IPConfigs:             []networkinterfaces.IPConfig{},
   280  	}
   281  
   282  	if m.cache != nil {
   283  		spec.SKU = &m.cache.VMSKU
   284  	}
   285  
   286  	for i := 0; i < infrav1NetworkInterface.PrivateIPConfigs; i++ {
   287  		spec.IPConfigs = append(spec.IPConfigs, networkinterfaces.IPConfig{})
   288  	}
   289  
   290  	if primaryNetworkInterface {
   291  		spec.DNSServers = m.AzureMachine.Spec.DNSServers
   292  
   293  		if m.Role() == infrav1.ControlPlane {
   294  			spec.PublicLBName = m.OutboundLBName(m.Role())
   295  			spec.PublicLBAddressPoolName = m.OutboundPoolName(m.Role())
   296  			if m.IsAPIServerPrivate() {
   297  				spec.InternalLBName = m.APIServerLBName()
   298  				spec.InternalLBAddressPoolName = m.APIServerLBPoolName()
   299  			} else {
   300  				spec.PublicLBNATRuleName = m.Name()
   301  				spec.PublicLBAddressPoolName = m.APIServerLBPoolName()
   302  			}
   303  		}
   304  
   305  		if m.Role() == infrav1.Node && m.AzureMachine.Spec.AllocatePublicIP {
   306  			spec.PublicIPName = azure.GenerateNodePublicIPName(m.Name())
   307  		}
   308  		// If the NAT gateway is not enabled and node has no public IP, then the NIC needs to reference the LB to get outbound traffic.
   309  		if m.Role() == infrav1.Node && !m.Subnet().IsNatGatewayEnabled() && !m.AzureMachine.Spec.AllocatePublicIP {
   310  			spec.PublicLBName = m.OutboundLBName(m.Role())
   311  			spec.PublicLBAddressPoolName = m.OutboundPoolName(m.Role())
   312  		}
   313  	}
   314  
   315  	return spec
   316  }
   317  
   318  // NICIDs returns the NIC resource IDs.
   319  func (m *MachineScope) NICIDs() []string {
   320  	nicspecs := m.NICSpecs()
   321  	nicIDs := make([]string, len(nicspecs))
   322  	for i, nic := range nicspecs {
   323  		nicIDs[i] = azure.NetworkInterfaceID(m.SubscriptionID(), nic.ResourceGroupName(), nic.ResourceName())
   324  	}
   325  
   326  	return nicIDs
   327  }
   328  
   329  // DiskSpecs returns the disk specs.
   330  func (m *MachineScope) DiskSpecs() []azure.ResourceSpecGetter {
   331  	diskSpecs := make([]azure.ResourceSpecGetter, 1+len(m.AzureMachine.Spec.DataDisks))
   332  	diskSpecs[0] = &disks.DiskSpec{
   333  		Name:          azure.GenerateOSDiskName(m.Name()),
   334  		ResourceGroup: m.NodeResourceGroup(),
   335  	}
   336  
   337  	for i, dd := range m.AzureMachine.Spec.DataDisks {
   338  		diskSpecs[i+1] = &disks.DiskSpec{
   339  			Name:          azure.GenerateDataDiskName(m.Name(), dd.NameSuffix),
   340  			ResourceGroup: m.NodeResourceGroup(),
   341  		}
   342  	}
   343  	return diskSpecs
   344  }
   345  
   346  // RoleAssignmentSpecs returns the role assignment specs.
   347  func (m *MachineScope) RoleAssignmentSpecs(principalID *string) []azure.ResourceSpecGetter {
   348  	roles := make([]azure.ResourceSpecGetter, 1)
   349  	if m.HasSystemAssignedIdentity() {
   350  		roles[0] = &roleassignments.RoleAssignmentSpec{
   351  			Name:             m.SystemAssignedIdentityName(),
   352  			MachineName:      m.Name(),
   353  			ResourceType:     azure.VirtualMachine,
   354  			ResourceGroup:    m.NodeResourceGroup(),
   355  			Scope:            m.SystemAssignedIdentityScope(),
   356  			RoleDefinitionID: m.SystemAssignedIdentityDefinitionID(),
   357  			PrincipalID:      principalID,
   358  			PrincipalType:    armauthorization.PrincipalTypeServicePrincipal,
   359  		}
   360  		return roles
   361  	}
   362  	return []azure.ResourceSpecGetter{}
   363  }
   364  
   365  // RoleAssignmentResourceType returns the role assignment resource type.
   366  func (m *MachineScope) RoleAssignmentResourceType() string {
   367  	return azure.VirtualMachine
   368  }
   369  
   370  // HasSystemAssignedIdentity returns true if the azure machine has
   371  // system assigned identity.
   372  func (m *MachineScope) HasSystemAssignedIdentity() bool {
   373  	return m.AzureMachine.Spec.Identity == infrav1.VMIdentitySystemAssigned
   374  }
   375  
   376  // VMExtensionSpecs returns the VM extension specs.
   377  func (m *MachineScope) VMExtensionSpecs() []azure.ResourceSpecGetter {
   378  	if ptr.Deref(m.AzureMachine.Spec.DisableExtensionOperations, false) {
   379  		return []azure.ResourceSpecGetter{}
   380  	}
   381  
   382  	var extensionSpecs = []azure.ResourceSpecGetter{}
   383  	for _, extension := range m.AzureMachine.Spec.VMExtensions {
   384  		extensionSpecs = append(extensionSpecs, &vmextensions.VMExtensionSpec{
   385  			ExtensionSpec: azure.ExtensionSpec{
   386  				Name:              extension.Name,
   387  				VMName:            m.Name(),
   388  				Publisher:         extension.Publisher,
   389  				Version:           extension.Version,
   390  				Settings:          extension.Settings,
   391  				ProtectedSettings: extension.ProtectedSettings,
   392  			},
   393  			ResourceGroup: m.NodeResourceGroup(),
   394  			Location:      m.Location(),
   395  		})
   396  	}
   397  
   398  	cpuArchitectureType, _ := m.cache.VMSKU.GetCapability(resourceskus.CPUArchitectureType)
   399  	bootstrapExtensionSpec := azure.GetBootstrappingVMExtension(m.AzureMachine.Spec.OSDisk.OSType, m.CloudEnvironment(), m.Name(), cpuArchitectureType)
   400  
   401  	if bootstrapExtensionSpec != nil {
   402  		extensionSpecs = append(extensionSpecs, &vmextensions.VMExtensionSpec{
   403  			ExtensionSpec: *bootstrapExtensionSpec,
   404  			ResourceGroup: m.NodeResourceGroup(),
   405  			Location:      m.Location(),
   406  		})
   407  	}
   408  
   409  	return extensionSpecs
   410  }
   411  
   412  // Subnet returns the machine's subnet.
   413  func (m *MachineScope) Subnet() infrav1.SubnetSpec {
   414  	for _, subnet := range m.Subnets() {
   415  		if subnet.Name == m.AzureMachine.Spec.NetworkInterfaces[0].SubnetName {
   416  			return subnet
   417  		}
   418  	}
   419  
   420  	return infrav1.SubnetSpec{}
   421  }
   422  
   423  // AvailabilityZone returns the AzureMachine Availability Zone.
   424  // Priority for selecting the AZ is
   425  //  1. Machine.Spec.FailureDomain
   426  //  2. AzureMachine.Spec.FailureDomain (This is to support deprecated AZ)
   427  //  3. No AZ
   428  func (m *MachineScope) AvailabilityZone() string {
   429  	if m.Machine.Spec.FailureDomain != nil {
   430  		return *m.Machine.Spec.FailureDomain
   431  	}
   432  	// Deprecated: to support old clients
   433  	if m.AzureMachine.Spec.FailureDomain != nil {
   434  		return *m.AzureMachine.Spec.FailureDomain
   435  	}
   436  
   437  	return ""
   438  }
   439  
   440  // Name returns the AzureMachine name.
   441  func (m *MachineScope) Name() string {
   442  	if id := m.GetVMID(); id != "" {
   443  		return id
   444  	}
   445  	// Windows Machine names cannot be longer than 15 chars
   446  	if m.AzureMachine.Spec.OSDisk.OSType == azure.WindowsOS && len(m.AzureMachine.Name) > 15 {
   447  		return strings.TrimSuffix(m.AzureMachine.Name[0:9], "-") + "-" + m.AzureMachine.Name[len(m.AzureMachine.Name)-5:]
   448  	}
   449  	return m.AzureMachine.Name
   450  }
   451  
   452  // Namespace returns the namespace name.
   453  func (m *MachineScope) Namespace() string {
   454  	return m.AzureMachine.Namespace
   455  }
   456  
   457  // IsControlPlane returns true if the machine is a control plane.
   458  func (m *MachineScope) IsControlPlane() bool {
   459  	return util.IsControlPlaneMachine(m.Machine)
   460  }
   461  
   462  // Role returns the machine role from the labels.
   463  func (m *MachineScope) Role() string {
   464  	if util.IsControlPlaneMachine(m.Machine) {
   465  		return infrav1.ControlPlane
   466  	}
   467  	return infrav1.Node
   468  }
   469  
   470  // GetVMID returns the AzureMachine instance id by parsing the scope's providerID.
   471  func (m *MachineScope) GetVMID() string {
   472  	resourceID, err := azureutil.ParseResourceID(m.ProviderID())
   473  	if err != nil {
   474  		return ""
   475  	}
   476  	return resourceID.Name
   477  }
   478  
   479  // ProviderID returns the AzureMachine providerID from the spec.
   480  func (m *MachineScope) ProviderID() string {
   481  	return ptr.Deref(m.AzureMachine.Spec.ProviderID, "")
   482  }
   483  
   484  // AvailabilitySetSpec returns the availability set spec for this machine if available.
   485  func (m *MachineScope) AvailabilitySetSpec() azure.ResourceSpecGetter {
   486  	availabilitySetName, ok := m.AvailabilitySet()
   487  	if !ok {
   488  		return nil
   489  	}
   490  
   491  	spec := &availabilitysets.AvailabilitySetSpec{
   492  		Name:           availabilitySetName,
   493  		ResourceGroup:  m.NodeResourceGroup(),
   494  		ClusterName:    m.ClusterName(),
   495  		Location:       m.Location(),
   496  		SKU:            nil,
   497  		AdditionalTags: m.AdditionalTags(),
   498  	}
   499  
   500  	if m.cache != nil {
   501  		spec.SKU = &m.cache.availabilitySetSKU
   502  	}
   503  
   504  	return spec
   505  }
   506  
   507  // AvailabilitySet returns the availability set for this machine if available.
   508  func (m *MachineScope) AvailabilitySet() (string, bool) {
   509  	// AvailabilitySet service is not supported on EdgeZone currently.
   510  	// AvailabilitySet cannot be used with Spot instances.
   511  	if !m.AvailabilitySetEnabled() || m.AzureMachine.Spec.SpotVMOptions != nil || m.ExtendedLocation() != nil ||
   512  		m.AzureMachine.Spec.FailureDomain != nil || m.Machine.Spec.FailureDomain != nil {
   513  		return "", false
   514  	}
   515  
   516  	if m.IsControlPlane() {
   517  		return azure.GenerateAvailabilitySetName(m.ClusterName(), azure.ControlPlaneNodeGroup), true
   518  	}
   519  
   520  	// get machine deployment name from labels for machines that maybe part of a machine deployment.
   521  	if mdName, ok := m.Machine.Labels[clusterv1.MachineDeploymentNameLabel]; ok {
   522  		return azure.GenerateAvailabilitySetName(m.ClusterName(), mdName), true
   523  	}
   524  
   525  	// if machine deployment name label is not available, use machine set name.
   526  	if msName, ok := m.Machine.Labels[clusterv1.MachineSetNameLabel]; ok {
   527  		return azure.GenerateAvailabilitySetName(m.ClusterName(), msName), true
   528  	}
   529  
   530  	return "", false
   531  }
   532  
   533  // AvailabilitySetID returns the availability set for this machine, or "" if there is no availability set.
   534  func (m *MachineScope) AvailabilitySetID() string {
   535  	var asID string
   536  	if asName, ok := m.AvailabilitySet(); ok {
   537  		asID = azure.AvailabilitySetID(m.SubscriptionID(), m.NodeResourceGroup(), asName)
   538  	}
   539  	return asID
   540  }
   541  
   542  // SystemAssignedIdentityName returns the role assignment name for the system assigned identity.
   543  func (m *MachineScope) SystemAssignedIdentityName() string {
   544  	if m.AzureMachine.Spec.SystemAssignedIdentityRole != nil {
   545  		return m.AzureMachine.Spec.SystemAssignedIdentityRole.Name
   546  	}
   547  	return ""
   548  }
   549  
   550  // SystemAssignedIdentityScope returns the scope for the system assigned identity.
   551  func (m *MachineScope) SystemAssignedIdentityScope() string {
   552  	if m.AzureMachine.Spec.SystemAssignedIdentityRole != nil {
   553  		return m.AzureMachine.Spec.SystemAssignedIdentityRole.Scope
   554  	}
   555  	return ""
   556  }
   557  
   558  // SystemAssignedIdentityDefinitionID returns the role definition id for the system assigned identity.
   559  func (m *MachineScope) SystemAssignedIdentityDefinitionID() string {
   560  	if m.AzureMachine.Spec.SystemAssignedIdentityRole != nil {
   561  		return m.AzureMachine.Spec.SystemAssignedIdentityRole.DefinitionID
   562  	}
   563  	return ""
   564  }
   565  
   566  // SetProviderID sets the AzureMachine providerID in spec.
   567  func (m *MachineScope) SetProviderID(v string) {
   568  	m.AzureMachine.Spec.ProviderID = ptr.To(v)
   569  }
   570  
   571  // VMState returns the AzureMachine VM state.
   572  func (m *MachineScope) VMState() infrav1.ProvisioningState {
   573  	if m.AzureMachine.Status.VMState != nil {
   574  		return *m.AzureMachine.Status.VMState
   575  	}
   576  	return ""
   577  }
   578  
   579  // SetVMState sets the AzureMachine VM state.
   580  func (m *MachineScope) SetVMState(v infrav1.ProvisioningState) {
   581  	m.AzureMachine.Status.VMState = &v
   582  }
   583  
   584  // SetReady sets the AzureMachine Ready Status to true.
   585  func (m *MachineScope) SetReady() {
   586  	m.AzureMachine.Status.Ready = true
   587  }
   588  
   589  // SetNotReady sets the AzureMachine Ready Status to false.
   590  func (m *MachineScope) SetNotReady() {
   591  	m.AzureMachine.Status.Ready = false
   592  }
   593  
   594  // SetFailureMessage sets the AzureMachine status failure message.
   595  func (m *MachineScope) SetFailureMessage(v error) {
   596  	m.AzureMachine.Status.FailureMessage = ptr.To(v.Error())
   597  }
   598  
   599  // SetFailureReason sets the AzureMachine status failure reason.
   600  func (m *MachineScope) SetFailureReason(v capierrors.MachineStatusError) {
   601  	m.AzureMachine.Status.FailureReason = &v
   602  }
   603  
   604  // SetConditionFalse sets the specified AzureMachine condition to false.
   605  func (m *MachineScope) SetConditionFalse(conditionType clusterv1.ConditionType, reason string, severity clusterv1.ConditionSeverity, message string) {
   606  	conditions.MarkFalse(m.AzureMachine, conditionType, reason, severity, message)
   607  }
   608  
   609  // SetAnnotation sets a key value annotation on the AzureMachine.
   610  func (m *MachineScope) SetAnnotation(key, value string) {
   611  	if m.AzureMachine.Annotations == nil {
   612  		m.AzureMachine.Annotations = map[string]string{}
   613  	}
   614  	m.AzureMachine.Annotations[key] = value
   615  }
   616  
   617  // AnnotationJSON returns a map[string]interface from a JSON annotation.
   618  func (m *MachineScope) AnnotationJSON(annotation string) (map[string]interface{}, error) {
   619  	out := map[string]interface{}{}
   620  	jsonAnnotation := m.AzureMachine.GetAnnotations()[annotation]
   621  	if jsonAnnotation == "" {
   622  		return out, nil
   623  	}
   624  	err := json.Unmarshal([]byte(jsonAnnotation), &out)
   625  	if err != nil {
   626  		return out, err
   627  	}
   628  	return out, nil
   629  }
   630  
   631  // UpdateAnnotationJSON updates the `annotation` with
   632  // `content`. `content` in this case should be a `map[string]interface{}`
   633  // suitable for turning into JSON. This `content` map will be marshalled into a
   634  // JSON string before being set as the given `annotation`.
   635  func (m *MachineScope) UpdateAnnotationJSON(annotation string, content map[string]interface{}) error {
   636  	b, err := json.Marshal(content)
   637  	if err != nil {
   638  		return err
   639  	}
   640  	m.SetAnnotation(annotation, string(b))
   641  	return nil
   642  }
   643  
   644  // SetAddresses sets the Azure address status.
   645  func (m *MachineScope) SetAddresses(addrs []corev1.NodeAddress) {
   646  	m.AzureMachine.Status.Addresses = addrs
   647  }
   648  
   649  // PatchObject persists the machine spec and status.
   650  func (m *MachineScope) PatchObject(ctx context.Context) error {
   651  	conditions.SetSummary(m.AzureMachine)
   652  
   653  	return m.patchHelper.Patch(
   654  		ctx,
   655  		m.AzureMachine,
   656  		patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{
   657  			clusterv1.ReadyCondition,
   658  			infrav1.VMRunningCondition,
   659  			infrav1.AvailabilitySetReadyCondition,
   660  			infrav1.NetworkInterfaceReadyCondition,
   661  		}})
   662  }
   663  
   664  // Close the MachineScope by updating the machine spec, machine status.
   665  func (m *MachineScope) Close(ctx context.Context) error {
   666  	return m.PatchObject(ctx)
   667  }
   668  
   669  // AdditionalTags merges AdditionalTags from the scope's AzureCluster and AzureMachine. If the same key is present in both,
   670  // the value from AzureMachine takes precedence.
   671  func (m *MachineScope) AdditionalTags() infrav1.Tags {
   672  	tags := make(infrav1.Tags)
   673  	// Start with the cluster-wide tags...
   674  	tags.Merge(m.ClusterScoper.AdditionalTags())
   675  	// ... and merge in the Machine's
   676  	tags.Merge(m.AzureMachine.Spec.AdditionalTags)
   677  	// Set the cloud provider tag
   678  	tags[infrav1.ClusterAzureCloudProviderTagKey(m.ClusterName())] = string(infrav1.ResourceLifecycleOwned)
   679  
   680  	return tags
   681  }
   682  
   683  // GetBootstrapData returns the bootstrap data from the secret in the Machine's bootstrap.dataSecretName.
   684  func (m *MachineScope) GetBootstrapData(ctx context.Context) (string, error) {
   685  	ctx, _, done := tele.StartSpanWithLogger(ctx, "scope.MachineScope.GetBootstrapData")
   686  	defer done()
   687  
   688  	if m.Machine.Spec.Bootstrap.DataSecretName == nil {
   689  		return "", errors.New("error retrieving bootstrap data: linked Machine's bootstrap.dataSecretName is nil")
   690  	}
   691  	secret := &corev1.Secret{}
   692  	key := types.NamespacedName{Namespace: m.Namespace(), Name: *m.Machine.Spec.Bootstrap.DataSecretName}
   693  	if err := m.client.Get(ctx, key, secret); err != nil {
   694  		return "", errors.Wrapf(err, "failed to retrieve bootstrap data secret for AzureMachine %s/%s", m.Namespace(), m.Name())
   695  	}
   696  
   697  	value, ok := secret.Data["value"]
   698  	if !ok {
   699  		return "", errors.New("error retrieving bootstrap data: secret value key is missing")
   700  	}
   701  	return base64.StdEncoding.EncodeToString(value), nil
   702  }
   703  
   704  // GetVMImage returns the image from the machine configuration, or a default one.
   705  func (m *MachineScope) GetVMImage(ctx context.Context) (*infrav1.Image, error) {
   706  	ctx, log, done := tele.StartSpanWithLogger(ctx, "scope.MachineScope.GetVMImage")
   707  	defer done()
   708  
   709  	// Use custom Marketplace image, Image ID or a Shared Image Gallery image if provided
   710  	if m.AzureMachine.Spec.Image != nil {
   711  		return m.AzureMachine.Spec.Image, nil
   712  	}
   713  
   714  	svc, err := virtualmachineimages.New(m)
   715  	if err != nil {
   716  		return nil, errors.Wrap(err, "failed to create virtualmachineimages service")
   717  	}
   718  
   719  	if m.AzureMachine.Spec.OSDisk.OSType == azure.WindowsOS {
   720  		runtime := m.AzureMachine.Annotations["runtime"]
   721  		windowsServerVersion := m.AzureMachine.Annotations["windowsServerVersion"]
   722  		log.Info("No image specified for machine, using default Windows Image", "machine", m.AzureMachine.GetName(), "runtime", runtime, "windowsServerVersion", windowsServerVersion)
   723  		return svc.GetDefaultWindowsImage(ctx, m.Location(), ptr.Deref(m.Machine.Spec.Version, ""), runtime, windowsServerVersion)
   724  	}
   725  
   726  	log.Info("No image specified for machine, using default Linux Image", "machine", m.AzureMachine.GetName())
   727  	return svc.GetDefaultUbuntuImage(ctx, m.Location(), ptr.Deref(m.Machine.Spec.Version, ""))
   728  }
   729  
   730  // SetSubnetName defaults the AzureMachine subnet name to the name of one the subnets with the machine role when there is only one of them.
   731  // Note: this logic exists only for purposes of ensuring backwards compatibility for old clusters created without the `subnetName` field being
   732  // set, and should be removed in the future when this field is no longer optional.
   733  func (m *MachineScope) SetSubnetName() error {
   734  	if m.AzureMachine.Spec.NetworkInterfaces[0].SubnetName == "" {
   735  		subnetName := ""
   736  		subnets := m.Subnets()
   737  		var subnetCount int
   738  		clusterSubnetName := ""
   739  		for _, subnet := range subnets {
   740  			if string(subnet.Role) == m.Role() {
   741  				subnetCount++
   742  				subnetName = subnet.Name
   743  			}
   744  			if subnet.Role == infrav1.SubnetCluster {
   745  				clusterSubnetName = subnet.Name
   746  			}
   747  		}
   748  
   749  		if subnetName == "" && clusterSubnetName != "" {
   750  			subnetName = clusterSubnetName
   751  			subnetCount = 1
   752  		}
   753  
   754  		if subnetCount == 0 || subnetCount > 1 || subnetName == "" {
   755  			return errors.New("a subnet name must be specified when no subnets are specified or more than 1 subnet of the same role exist")
   756  		}
   757  
   758  		m.AzureMachine.Spec.NetworkInterfaces[0].SubnetName = subnetName
   759  	}
   760  
   761  	return nil
   762  }
   763  
   764  // SetLongRunningOperationState will set the future on the AzureMachine status to allow the resource to continue
   765  // in the next reconciliation.
   766  func (m *MachineScope) SetLongRunningOperationState(future *infrav1.Future) {
   767  	futures.Set(m.AzureMachine, future)
   768  }
   769  
   770  // GetLongRunningOperationState will get the future on the AzureMachine status.
   771  func (m *MachineScope) GetLongRunningOperationState(name, service, futureType string) *infrav1.Future {
   772  	return futures.Get(m.AzureMachine, name, service, futureType)
   773  }
   774  
   775  // DeleteLongRunningOperationState will delete the future from the AzureMachine status.
   776  func (m *MachineScope) DeleteLongRunningOperationState(name, service, futureType string) {
   777  	futures.Delete(m.AzureMachine, name, service, futureType)
   778  }
   779  
   780  // UpdateDeleteStatus updates a condition on the AzureMachine status after a DELETE operation.
   781  func (m *MachineScope) UpdateDeleteStatus(condition clusterv1.ConditionType, service string, err error) {
   782  	switch {
   783  	case err == nil:
   784  		conditions.MarkFalse(m.AzureMachine, condition, infrav1.DeletedReason, clusterv1.ConditionSeverityInfo, "%s successfully deleted", service)
   785  	case azure.IsOperationNotDoneError(err):
   786  		conditions.MarkFalse(m.AzureMachine, condition, infrav1.DeletingReason, clusterv1.ConditionSeverityInfo, "%s deleting", service)
   787  	default:
   788  		conditions.MarkFalse(m.AzureMachine, condition, infrav1.DeletionFailedReason, clusterv1.ConditionSeverityError, "%s failed to delete. err: %s", service, err.Error())
   789  	}
   790  }
   791  
   792  // UpdatePutStatus updates a condition on the AzureMachine status after a PUT operation.
   793  func (m *MachineScope) UpdatePutStatus(condition clusterv1.ConditionType, service string, err error) {
   794  	switch {
   795  	case err == nil:
   796  		conditions.MarkTrue(m.AzureMachine, condition)
   797  	case azure.IsOperationNotDoneError(err):
   798  		conditions.MarkFalse(m.AzureMachine, condition, infrav1.CreatingReason, clusterv1.ConditionSeverityInfo, "%s creating or updating", service)
   799  	default:
   800  		conditions.MarkFalse(m.AzureMachine, condition, infrav1.FailedReason, clusterv1.ConditionSeverityError, "%s failed to create or update. err: %s", service, err.Error())
   801  	}
   802  }
   803  
   804  // UpdatePatchStatus updates a condition on the AzureMachine status after a PATCH operation.
   805  func (m *MachineScope) UpdatePatchStatus(condition clusterv1.ConditionType, service string, err error) {
   806  	switch {
   807  	case err == nil:
   808  		conditions.MarkTrue(m.AzureMachine, condition)
   809  	case azure.IsOperationNotDoneError(err):
   810  		conditions.MarkFalse(m.AzureMachine, condition, infrav1.UpdatingReason, clusterv1.ConditionSeverityInfo, "%s updating", service)
   811  	default:
   812  		conditions.MarkFalse(m.AzureMachine, condition, infrav1.FailedReason, clusterv1.ConditionSeverityError, "%s failed to update. err: %s", service, err.Error())
   813  	}
   814  }
   815  
   816  // GetCapacityReservationGroupID returns the CapacityReservationGroupID from the spec if the
   817  // value is assigned, or else returns an empty string.
   818  func (m *MachineScope) GetCapacityReservationGroupID() string {
   819  	return ptr.Deref(m.AzureMachine.Spec.CapacityReservationGroupID, "")
   820  }