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 }