sigs.k8s.io/cluster-api-provider-azure@v1.14.3/api/v1beta1/azuremachine_default.go (about)

     1  /*
     2  Copyright 2021 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 v1beta1
    18  
    19  import (
    20  	"context"
    21  	"encoding/base64"
    22  	"fmt"
    23  	"time"
    24  
    25  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
    26  	"github.com/pkg/errors"
    27  	"golang.org/x/crypto/ssh"
    28  	kerrors "k8s.io/apimachinery/pkg/util/errors"
    29  	"k8s.io/apimachinery/pkg/util/uuid"
    30  	utilSSH "sigs.k8s.io/cluster-api-provider-azure/util/ssh"
    31  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  )
    34  
    35  // ContributorRoleID is the ID of the built-in "Contributor" role.
    36  const ContributorRoleID = "b24988ac-6180-42a0-ab88-20f7382dd24c"
    37  
    38  // SetDefaultSSHPublicKey sets the default SSHPublicKey for an AzureMachine.
    39  func (s *AzureMachineSpec) SetDefaultSSHPublicKey() error {
    40  	if sshKeyData := s.SSHPublicKey; sshKeyData == "" {
    41  		_, publicRsaKey, err := utilSSH.GenerateSSHKey()
    42  		if err != nil {
    43  			return err
    44  		}
    45  
    46  		s.SSHPublicKey = base64.StdEncoding.EncodeToString(ssh.MarshalAuthorizedKey(publicRsaKey))
    47  	}
    48  	return nil
    49  }
    50  
    51  // SetDefaultCachingType sets the default cache type for an AzureMachine.
    52  func (s *AzureMachineSpec) SetDefaultCachingType() {
    53  	if s.OSDisk.CachingType == "" {
    54  		s.OSDisk.CachingType = "None"
    55  	}
    56  }
    57  
    58  // SetDataDisksDefaults sets the data disk defaults for an AzureMachine.
    59  func (s *AzureMachineSpec) SetDataDisksDefaults() {
    60  	set := make(map[int32]struct{})
    61  	// populate all the existing values in the set
    62  	for _, disk := range s.DataDisks {
    63  		if disk.Lun != nil {
    64  			set[*disk.Lun] = struct{}{}
    65  		}
    66  	}
    67  	// Look for unique values for unassigned LUNs
    68  	for i, disk := range s.DataDisks {
    69  		if disk.Lun == nil {
    70  			for l := range s.DataDisks {
    71  				lun := int32(l)
    72  				if _, ok := set[lun]; !ok {
    73  					s.DataDisks[i].Lun = &lun
    74  					set[lun] = struct{}{}
    75  					break
    76  				}
    77  			}
    78  		}
    79  		if disk.CachingType == "" {
    80  			if s.DataDisks[i].ManagedDisk != nil &&
    81  				s.DataDisks[i].ManagedDisk.StorageAccountType == string(armcompute.StorageAccountTypesUltraSSDLRS) {
    82  				s.DataDisks[i].CachingType = string(armcompute.CachingTypesNone)
    83  			} else {
    84  				s.DataDisks[i].CachingType = string(armcompute.CachingTypesReadWrite)
    85  			}
    86  		}
    87  	}
    88  }
    89  
    90  // SetIdentityDefaults sets the defaults for VM Identity.
    91  func (s *AzureMachineSpec) SetIdentityDefaults(subscriptionID string) {
    92  	// Ensure the deprecated fields and new fields are not populated simultaneously
    93  	if s.RoleAssignmentName != "" && s.SystemAssignedIdentityRole != nil && s.SystemAssignedIdentityRole.Name != "" {
    94  		// Both the deprecated and the new fields are both set, return without changes
    95  		// and reject the request in the validating webhook which runs later.
    96  		return
    97  	}
    98  	if s.Identity == VMIdentitySystemAssigned {
    99  		if s.SystemAssignedIdentityRole == nil {
   100  			s.SystemAssignedIdentityRole = &SystemAssignedIdentityRole{}
   101  		}
   102  		if s.RoleAssignmentName != "" {
   103  			// Move the existing value from the deprecated RoleAssignmentName field.
   104  			s.SystemAssignedIdentityRole.Name = s.RoleAssignmentName
   105  			s.RoleAssignmentName = ""
   106  		} else if s.SystemAssignedIdentityRole.Name == "" {
   107  			// Default role name to a generated UUID.
   108  			s.SystemAssignedIdentityRole.Name = string(uuid.NewUUID())
   109  		}
   110  		if s.SystemAssignedIdentityRole.Scope == "" && subscriptionID != "" {
   111  			// Default scope to the subscription.
   112  			s.SystemAssignedIdentityRole.Scope = fmt.Sprintf("/subscriptions/%s/", subscriptionID)
   113  		}
   114  		if s.SystemAssignedIdentityRole.DefinitionID == "" && subscriptionID != "" {
   115  			// Default role definition ID to Contributor role.
   116  			s.SystemAssignedIdentityRole.DefinitionID = fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Authorization/roleDefinitions/%s", subscriptionID, ContributorRoleID)
   117  		}
   118  	}
   119  }
   120  
   121  // SetSpotEvictionPolicyDefaults sets the defaults for the spot VM eviction policy.
   122  func (s *AzureMachineSpec) SetSpotEvictionPolicyDefaults() {
   123  	if s.SpotVMOptions != nil && s.SpotVMOptions.EvictionPolicy == nil {
   124  		defaultPolicy := SpotEvictionPolicyDeallocate
   125  		if s.OSDisk.DiffDiskSettings != nil && s.OSDisk.DiffDiskSettings.Option == "Local" {
   126  			defaultPolicy = SpotEvictionPolicyDelete
   127  		}
   128  		s.SpotVMOptions.EvictionPolicy = &defaultPolicy
   129  	}
   130  }
   131  
   132  // SetDiagnosticsDefaults sets the defaults for Diagnostic settings for an AzureMachinePool.
   133  func (s *AzureMachineSpec) SetDiagnosticsDefaults() {
   134  	bootDiagnosticsDefault := &BootDiagnostics{
   135  		StorageAccountType: ManagedDiagnosticsStorage,
   136  	}
   137  
   138  	diagnosticsDefault := &Diagnostics{Boot: bootDiagnosticsDefault}
   139  
   140  	if s.Diagnostics == nil {
   141  		s.Diagnostics = diagnosticsDefault
   142  	}
   143  
   144  	if s.Diagnostics.Boot == nil {
   145  		s.Diagnostics.Boot = bootDiagnosticsDefault
   146  	}
   147  }
   148  
   149  // SetNetworkInterfacesDefaults sets the defaults for the network interfaces.
   150  func (s *AzureMachineSpec) SetNetworkInterfacesDefaults() {
   151  	// Ensure the deprecated fields and new fields are not populated simultaneously
   152  	if (s.SubnetName != "" || s.AcceleratedNetworking != nil) && len(s.NetworkInterfaces) > 0 {
   153  		// Both the deprecated and the new fields are both set, return without changes
   154  		// and reject the request in the validating webhook which runs later.
   155  		return
   156  	}
   157  
   158  	if len(s.NetworkInterfaces) == 0 {
   159  		s.NetworkInterfaces = []NetworkInterface{
   160  			{
   161  				SubnetName:            s.SubnetName,
   162  				AcceleratedNetworking: s.AcceleratedNetworking,
   163  			},
   164  		}
   165  		s.SubnetName = ""
   166  		s.AcceleratedNetworking = nil
   167  	}
   168  
   169  	// Ensure that PrivateIPConfigs defaults to 1 if not specified.
   170  	for i := 0; i < len(s.NetworkInterfaces); i++ {
   171  		if s.NetworkInterfaces[i].PrivateIPConfigs == 0 {
   172  			s.NetworkInterfaces[i].PrivateIPConfigs = 1
   173  		}
   174  	}
   175  }
   176  
   177  // GetOwnerAzureClusterNameAndNamespace returns the owner azure cluster's name and namespace for the given cluster name and namespace.
   178  func GetOwnerAzureClusterNameAndNamespace(cli client.Client, clusterName string, namespace string, maxAttempts int) (azureClusterName string, azureClusterNamespace string, err error) {
   179  	ctx := context.Background()
   180  
   181  	ownerCluster := &clusterv1.Cluster{}
   182  	key := client.ObjectKey{
   183  		Namespace: namespace,
   184  		Name:      clusterName,
   185  	}
   186  
   187  	for i := 1; ; i++ {
   188  		if err := cli.Get(ctx, key, ownerCluster); err != nil {
   189  			if i > maxAttempts {
   190  				return "", "", errors.Wrapf(err, "failed to find owner cluster for %s/%s", namespace, clusterName)
   191  			}
   192  			time.Sleep(1 * time.Second)
   193  			continue
   194  		}
   195  		break
   196  	}
   197  
   198  	return ownerCluster.Spec.InfrastructureRef.Name, ownerCluster.Spec.InfrastructureRef.Namespace, nil
   199  }
   200  
   201  // GetSubscriptionID returns the subscription ID for the AzureCluster given the cluster name and namespace.
   202  func GetSubscriptionID(cli client.Client, ownerAzureClusterName string, ownerAzureClusterNamespace string, maxAttempts int) (string, error) {
   203  	ctx := context.Background()
   204  
   205  	ownerAzureCluster := &AzureCluster{}
   206  	key := client.ObjectKey{
   207  		Namespace: ownerAzureClusterNamespace,
   208  		Name:      ownerAzureClusterName,
   209  	}
   210  	for i := 1; ; i++ {
   211  		if err := cli.Get(ctx, key, ownerAzureCluster); err != nil {
   212  			if i >= maxAttempts {
   213  				return "", errors.Wrapf(err, "failed to find AzureCluster for owner cluster %s/%s", ownerAzureClusterNamespace, ownerAzureClusterName)
   214  			}
   215  			time.Sleep(1 * time.Second)
   216  			continue
   217  		}
   218  		break
   219  	}
   220  
   221  	return ownerAzureCluster.Spec.SubscriptionID, nil
   222  }
   223  
   224  // SetDefaults sets to the defaults for the AzureMachineSpec.
   225  func (m *AzureMachine) SetDefaults(client client.Client) error {
   226  	var errs []error
   227  	if err := m.Spec.SetDefaultSSHPublicKey(); err != nil {
   228  		errs = append(errs, errors.Wrap(err, "failed to set default SSH public key"))
   229  	}
   230  
   231  	// Fetch the Cluster.
   232  	clusterName, ok := m.Labels[clusterv1.ClusterNameLabel]
   233  	if !ok {
   234  		errs = append(errs, errors.Errorf("failed to fetch ClusterName for AzureMachine %s/%s", m.Namespace, m.Name))
   235  	}
   236  
   237  	ownerAzureClusterName, ownerAzureClusterNamespace, err := GetOwnerAzureClusterNameAndNamespace(client, clusterName, m.Namespace, 5)
   238  	if err != nil {
   239  		errs = append(errs, errors.Wrapf(err, "failed to fetch owner cluster for AzureMachine %s/%s", m.Namespace, m.Name))
   240  	}
   241  
   242  	subscriptionID, err := GetSubscriptionID(client, ownerAzureClusterName, ownerAzureClusterNamespace, 5)
   243  	if err != nil {
   244  		errs = append(errs, errors.Wrapf(err, "failed to fetch subscription ID for AzureMachine %s/%s", m.Namespace, m.Name))
   245  	}
   246  
   247  	m.Spec.SetDefaultCachingType()
   248  	m.Spec.SetDataDisksDefaults()
   249  	m.Spec.SetIdentityDefaults(subscriptionID)
   250  	m.Spec.SetSpotEvictionPolicyDefaults()
   251  	m.Spec.SetDiagnosticsDefaults()
   252  	m.Spec.SetNetworkInterfacesDefaults()
   253  
   254  	return kerrors.NewAggregate(errs)
   255  }