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