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 }