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

     1  /*
     2  Copyright 2019 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 azure
    18  
    19  import (
    20  	"fmt"
    21  	"net/http"
    22  	"regexp"
    23  	"strings"
    24  
    25  	"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
    26  	"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
    27  	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
    28  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
    29  	"sigs.k8s.io/cluster-api-provider-azure/util/tele"
    30  	"sigs.k8s.io/cluster-api-provider-azure/version"
    31  )
    32  
    33  const (
    34  	// DefaultUserName is the default username for a created VM.
    35  	DefaultUserName = "capi"
    36  	// DefaultAKSUserName is the default username for a created AKS VM.
    37  	DefaultAKSUserName = "azureuser"
    38  	// PublicCloudName is the name of the Azure public cloud.
    39  	PublicCloudName = "AzurePublicCloud"
    40  	// ChinaCloudName is the name of the Azure China cloud.
    41  	ChinaCloudName = "AzureChinaCloud"
    42  	// USGovernmentCloudName is the name of the Azure US Government cloud.
    43  	USGovernmentCloudName = "AzureUSGovernmentCloud"
    44  )
    45  
    46  const (
    47  	// DefaultImageOfferID is the default Azure Marketplace offer ID.
    48  	DefaultImageOfferID = "capi"
    49  	// DefaultWindowsImageOfferID is the default Azure Marketplace offer ID for Windows.
    50  	DefaultWindowsImageOfferID = "capi-windows"
    51  	// DefaultImagePublisherID is the default Azure Marketplace publisher ID.
    52  	DefaultImagePublisherID = "cncf-upstream"
    53  	// LatestVersion is the image version latest.
    54  	LatestVersion = "latest"
    55  )
    56  
    57  const (
    58  	// LinuxOS is Linux OS value for OSDisk.OSType.
    59  	LinuxOS = "Linux"
    60  	// WindowsOS is Windows OS value for OSDisk.OSType.
    61  	WindowsOS = "Windows"
    62  )
    63  
    64  const (
    65  	// BootstrappingExtensionLinux is the name of the Linux CAPZ bootstrapping VM extension.
    66  	BootstrappingExtensionLinux = "CAPZ.Linux.Bootstrapping"
    67  	// BootstrappingExtensionWindows is the name of the Windows CAPZ bootstrapping VM extension.
    68  	BootstrappingExtensionWindows = "CAPZ.Windows.Bootstrapping"
    69  )
    70  
    71  const (
    72  	// DefaultWindowsOsAndVersion is the default Windows Server version to use when
    73  	// generating default images for Windows nodes.
    74  	DefaultWindowsOsAndVersion = "windows-2019"
    75  )
    76  
    77  const (
    78  	// Global is the Azure global location value.
    79  	Global = "global"
    80  )
    81  
    82  const (
    83  	// PrivateAPIServerHostname will be used as the api server hostname for private clusters.
    84  	PrivateAPIServerHostname = "apiserver"
    85  )
    86  
    87  const (
    88  	// ControlPlaneNodeGroup will be used to create availability set for control plane machines.
    89  	ControlPlaneNodeGroup = "control-plane"
    90  )
    91  
    92  const (
    93  	// bootstrapExtensionRetries is the number of retries in the BootstrapExtensionCommand.
    94  	// NOTE: the overall timeout will be number of retries * retry sleep, in this case 60 * 5s = 300s.
    95  	bootstrapExtensionRetries = 60
    96  	// bootstrapExtensionSleep is the duration in seconds to sleep before each retry in the BootstrapExtensionCommand.
    97  	bootstrapExtensionSleep = 5
    98  	// bootstrapSentinelFile is the file written by bootstrap provider on machines to indicate successful bootstrapping,
    99  	// as defined by the Cluster API Bootstrap Provider contract (https://cluster-api.sigs.k8s.io/developer/providers/bootstrap.html).
   100  	bootstrapSentinelFile = "/run/cluster-api/bootstrap-success.complete"
   101  )
   102  
   103  const (
   104  	// CustomHeaderPrefix is the prefix of annotations that enable additional cluster / node pool features.
   105  	// Whatever follows the prefix will be passed as a header to cluster/node pool creation/update requests.
   106  	// E.g. add `"infrastructure.cluster.x-k8s.io/custom-header-UseGPUDedicatedVHD": "true"` annotation to
   107  	// AzureManagedMachinePool CR to enable creating GPU nodes by the node pool.
   108  	CustomHeaderPrefix = "infrastructure.cluster.x-k8s.io/custom-header-"
   109  )
   110  
   111  var (
   112  	// LinuxBootstrapExtensionCommand is the command the VM bootstrap extension will execute to verify Linux nodes bootstrap completes successfully.
   113  	LinuxBootstrapExtensionCommand = fmt.Sprintf("for i in $(seq 1 %d); do test -f %s && break; if [ $i -eq %d ]; then exit 1; else sleep %d; fi; done", bootstrapExtensionRetries, bootstrapSentinelFile, bootstrapExtensionRetries, bootstrapExtensionSleep)
   114  	// WindowsBootstrapExtensionCommand is the command the VM bootstrap extension will execute to verify Windows nodes bootstrap completes successfully.
   115  	WindowsBootstrapExtensionCommand = fmt.Sprintf("powershell.exe -Command \"for ($i = 0; $i -lt %d; $i++) {if (Test-Path '%s') {exit 0} else {Start-Sleep -Seconds %d}} exit -2\"",
   116  		bootstrapExtensionRetries, bootstrapSentinelFile, bootstrapExtensionSleep)
   117  )
   118  
   119  // GenerateBackendAddressPoolName generates a load balancer backend address pool name.
   120  func GenerateBackendAddressPoolName(lbName string) string {
   121  	return fmt.Sprintf("%s-%s", lbName, "backendPool")
   122  }
   123  
   124  // GenerateOutboundBackendAddressPoolName generates a load balancer outbound backend address pool name.
   125  func GenerateOutboundBackendAddressPoolName(lbName string) string {
   126  	return fmt.Sprintf("%s-%s", lbName, "outboundBackendPool")
   127  }
   128  
   129  // GenerateFrontendIPConfigName generates a load balancer frontend IP config name.
   130  func GenerateFrontendIPConfigName(lbName string) string {
   131  	return fmt.Sprintf("%s-%s", lbName, "frontEnd")
   132  }
   133  
   134  // GenerateNodeOutboundIPName generates a public IP name, based on the cluster name.
   135  func GenerateNodeOutboundIPName(clusterName string) string {
   136  	return fmt.Sprintf("pip-%s-node-outbound", clusterName)
   137  }
   138  
   139  // GenerateNodePublicIPName generates a node public IP name, based on the machine name.
   140  func GenerateNodePublicIPName(machineName string) string {
   141  	return fmt.Sprintf("pip-%s", machineName)
   142  }
   143  
   144  // GenerateControlPlaneOutboundLBName generates the name of the control plane outbound LB.
   145  func GenerateControlPlaneOutboundLBName(clusterName string) string {
   146  	return fmt.Sprintf("%s-outbound-lb", clusterName)
   147  }
   148  
   149  // GenerateControlPlaneOutboundIPName generates a public IP name, based on the cluster name.
   150  func GenerateControlPlaneOutboundIPName(clusterName string) string {
   151  	return fmt.Sprintf("pip-%s-controlplane-outbound", clusterName)
   152  }
   153  
   154  // GeneratePrivateDNSZoneName generates the name of a private DNS zone based on the cluster name.
   155  func GeneratePrivateDNSZoneName(clusterName string) string {
   156  	return fmt.Sprintf("%s.capz.io", clusterName)
   157  }
   158  
   159  // GeneratePrivateFQDN generates the FQDN for a private API Server based on the private DNS zone name.
   160  func GeneratePrivateFQDN(zoneName string) string {
   161  	return fmt.Sprintf("%s.%s", PrivateAPIServerHostname, zoneName)
   162  }
   163  
   164  // GenerateVNetLinkName generates the name of a virtual network link name based on the vnet name.
   165  func GenerateVNetLinkName(vnetName string) string {
   166  	return fmt.Sprintf("%s-link", vnetName)
   167  }
   168  
   169  // GenerateNICName generates the name of a network interface based on the name of a VM.
   170  func GenerateNICName(machineName string, multiNIC bool, index int) string {
   171  	if multiNIC {
   172  		return fmt.Sprintf("%s-nic-%d", machineName, index)
   173  	}
   174  	return fmt.Sprintf("%s-nic", machineName)
   175  }
   176  
   177  // GeneratePublicNICName generates the name of a public network interface based on the name of a VM.
   178  func GeneratePublicNICName(machineName string) string {
   179  	return fmt.Sprintf("%s-public-nic", machineName)
   180  }
   181  
   182  // GenerateOSDiskName generates the name of an OS disk based on the name of a VM.
   183  func GenerateOSDiskName(machineName string) string {
   184  	return fmt.Sprintf("%s_OSDisk", machineName)
   185  }
   186  
   187  // GenerateDataDiskName generates the name of a data disk based on the name of a VM.
   188  func GenerateDataDiskName(machineName, nameSuffix string) string {
   189  	return fmt.Sprintf("%s_%s", machineName, nameSuffix)
   190  }
   191  
   192  // GenerateVnetPeeringName generates the name for a peering between two vnets.
   193  func GenerateVnetPeeringName(sourceVnetName string, remoteVnetName string) string {
   194  	return fmt.Sprintf("%s-To-%s", sourceVnetName, remoteVnetName)
   195  }
   196  
   197  // GenerateAvailabilitySetName generates the name of a availability set based on the cluster name and the node group.
   198  // node group identifies the set of nodes that belong to this availability set:
   199  // For control plane nodes, this will be `control-plane`.
   200  // For worker nodes, this will be the machine deployment name.
   201  func GenerateAvailabilitySetName(clusterName, nodeGroup string) string {
   202  	return fmt.Sprintf("%s_%s-as", clusterName, nodeGroup)
   203  }
   204  
   205  // WithIndex appends the index as suffix to a generated name.
   206  func WithIndex(name string, n int) string {
   207  	return fmt.Sprintf("%s-%d", name, n)
   208  }
   209  
   210  // ResourceGroupID returns the azure resource ID for a given resource group.
   211  func ResourceGroupID(subscriptionID, resourceGroup string) string {
   212  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", subscriptionID, resourceGroup)
   213  }
   214  
   215  // VMID returns the azure resource ID for a given VM.
   216  func VMID(subscriptionID, resourceGroup, vmName string) string {
   217  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s", subscriptionID, resourceGroup, vmName)
   218  }
   219  
   220  // VNetID returns the azure resource ID for a given VNet.
   221  func VNetID(subscriptionID, resourceGroup, vnetName string) string {
   222  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s", subscriptionID, resourceGroup, vnetName)
   223  }
   224  
   225  // SubnetID returns the azure resource ID for a given subnet.
   226  func SubnetID(subscriptionID, resourceGroup, vnetName, subnetName string) string {
   227  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s", subscriptionID, resourceGroup, vnetName, subnetName)
   228  }
   229  
   230  // PublicIPID returns the azure resource ID for a given public IP.
   231  func PublicIPID(subscriptionID, resourceGroup, ipName string) string {
   232  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/publicIPAddresses/%s", subscriptionID, resourceGroup, ipName)
   233  }
   234  
   235  // PublicIPPrefixID returns the azure resource ID for a given public IP prefix.
   236  func PublicIPPrefixID(subscriptionID, resourceGroup, ipName string) string {
   237  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/publicipprefixes/%s", subscriptionID, resourceGroup, ipName)
   238  }
   239  
   240  // RouteTableID returns the azure resource ID for a given route table.
   241  func RouteTableID(subscriptionID, resourceGroup, routeTableName string) string {
   242  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/routeTables/%s", subscriptionID, resourceGroup, routeTableName)
   243  }
   244  
   245  // SecurityGroupID returns the azure resource ID for a given security group.
   246  func SecurityGroupID(subscriptionID, resourceGroup, nsgName string) string {
   247  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/networkSecurityGroups/%s", subscriptionID, resourceGroup, nsgName)
   248  }
   249  
   250  // NatGatewayID returns the azure resource ID for a given NAT gateway.
   251  func NatGatewayID(subscriptionID, resourceGroup, natgatewayName string) string {
   252  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/natGateways/%s", subscriptionID, resourceGroup, natgatewayName)
   253  }
   254  
   255  // NetworkInterfaceID returns the azure resource ID for a given network interface.
   256  func NetworkInterfaceID(subscriptionID, resourceGroup, nicName string) string {
   257  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/networkInterfaces/%s", subscriptionID, resourceGroup, nicName)
   258  }
   259  
   260  // FrontendIPConfigID returns the azure resource ID for a given frontend IP config.
   261  func FrontendIPConfigID(subscriptionID, resourceGroup, loadBalancerName, configName string) string {
   262  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/frontendIPConfigurations/%s", subscriptionID, resourceGroup, loadBalancerName, configName)
   263  }
   264  
   265  // AddressPoolID returns the azure resource ID for a given backend address pool.
   266  func AddressPoolID(subscriptionID, resourceGroup, loadBalancerName, backendPoolName string) string {
   267  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/backendAddressPools/%s", subscriptionID, resourceGroup, loadBalancerName, backendPoolName)
   268  }
   269  
   270  // ProbeID returns the azure resource ID for a given probe.
   271  func ProbeID(subscriptionID, resourceGroup, loadBalancerName, probeName string) string {
   272  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/probes/%s", subscriptionID, resourceGroup, loadBalancerName, probeName)
   273  }
   274  
   275  // NATRuleID returns the azure resource ID for a inbound NAT rule.
   276  func NATRuleID(subscriptionID, resourceGroup, loadBalancerName, natRuleName string) string {
   277  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/inboundNatRules/%s", subscriptionID, resourceGroup, loadBalancerName, natRuleName)
   278  }
   279  
   280  // AvailabilitySetID returns the azure resource ID for a given availability set.
   281  func AvailabilitySetID(subscriptionID, resourceGroup, availabilitySetName string) string {
   282  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/availabilitySets/%s", subscriptionID, resourceGroup, availabilitySetName)
   283  }
   284  
   285  // PrivateDNSZoneID returns the azure resource ID for a given private DNS zone.
   286  func PrivateDNSZoneID(subscriptionID, resourceGroup, privateDNSZoneName string) string {
   287  	return fmt.Sprintf("subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/privateDnsZones/%s", subscriptionID, resourceGroup, privateDNSZoneName)
   288  }
   289  
   290  // VirtualNetworkLinkID returns the azure resource ID for a given virtual network link.
   291  func VirtualNetworkLinkID(subscriptionID, resourceGroup, privateDNSZoneName, virtualNetworkLinkName string) string {
   292  	return fmt.Sprintf("subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/privateDnsZones/%s/virtualNetworkLinks/%s", subscriptionID, resourceGroup, privateDNSZoneName, virtualNetworkLinkName)
   293  }
   294  
   295  // ManagedClusterID returns the azure resource ID for a given managed cluster.
   296  func ManagedClusterID(subscriptionID, resourceGroup, managedClusterName string) string {
   297  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.ContainerService/managedClusters/%s", subscriptionID, resourceGroup, managedClusterName)
   298  }
   299  
   300  // FleetID returns the azure resource ID for a given fleet manager.
   301  func FleetID(subscriptionID, resourceGroup, fleetName string) string {
   302  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.ContainerService/fleets/%s", subscriptionID, resourceGroup, fleetName)
   303  }
   304  
   305  // GetBootstrappingVMExtension returns the CAPZ Bootstrapping VM extension.
   306  // The CAPZ Bootstrapping extension is a simple clone of https://github.com/Azure/custom-script-extension-linux for Linux or
   307  // https://learn.microsoft.com/azure/virtual-machines/extensions/custom-script-windows for Windows.
   308  // This extension allows running arbitrary scripts on the VM.
   309  // Its role is to detect and report Kubernetes bootstrap failure or success.
   310  func GetBootstrappingVMExtension(osType string, cloud string, vmName string, cpuArchitectureType string) *ExtensionSpec {
   311  	// currently, the bootstrap extension is only available in AzurePublicCloud.
   312  	if osType == LinuxOS && cloud == PublicCloudName {
   313  		// The command checks for the existence of the bootstrapSentinelFile on the machine, with retries and sleep between retries.
   314  		// We set the version to 1.1 (will target 1.1.1) for arm64 machines and 1.0 for x64. This is due to a known issue with newer versions of
   315  		// Go on Ubuntu 20.04. The issue is being tracked here: https://github.com/golang/go/issues/58550
   316  		// TODO: Remove this once the issue is fixed, or when Ubuntu 20.04 is no longer supported.
   317  		// We are using 1.1 instead of 1.1.1 for Arm64 as AzureAPI do not allow us to specify the full version.
   318  		extensionVersion := "1.0"
   319  		if cpuArchitectureType == string(armcompute.ArchitectureTypesArm64) {
   320  			extensionVersion = "1.1"
   321  		}
   322  		return &ExtensionSpec{
   323  			Name:      BootstrappingExtensionLinux,
   324  			VMName:    vmName,
   325  			Publisher: "Microsoft.Azure.ContainerUpstream",
   326  			Version:   extensionVersion,
   327  			ProtectedSettings: map[string]string{
   328  				"commandToExecute": LinuxBootstrapExtensionCommand,
   329  			},
   330  		}
   331  	} else if osType == WindowsOS && cloud == PublicCloudName {
   332  		// This command for the existence of the bootstrapSentinelFile on the machine, with retries and sleep between reties.
   333  		// If the file is not present after the retries are exhausted the extension fails with return code '-2' - ERROR_FILE_NOT_FOUND.
   334  		return &ExtensionSpec{
   335  			Name:      BootstrappingExtensionWindows,
   336  			VMName:    vmName,
   337  			Publisher: "Microsoft.Azure.ContainerUpstream",
   338  			Version:   "1.0",
   339  			ProtectedSettings: map[string]string{
   340  				"commandToExecute": WindowsBootstrapExtensionCommand,
   341  			},
   342  		}
   343  	}
   344  
   345  	return nil
   346  }
   347  
   348  // UserAgent specifies a string to append to the agent identifier.
   349  func UserAgent() string {
   350  	return fmt.Sprintf("cluster-api-provider-azure/%s", version.Get().String())
   351  }
   352  
   353  // ARMClientOptions returns default ARM client options for CAPZ SDK v2 requests.
   354  func ARMClientOptions(azureEnvironment string, extraPolicies ...policy.Policy) (*arm.ClientOptions, error) {
   355  	opts := &arm.ClientOptions{}
   356  
   357  	switch azureEnvironment {
   358  	case PublicCloudName:
   359  		opts.Cloud = cloud.AzurePublic
   360  	case ChinaCloudName:
   361  		opts.Cloud = cloud.AzureChina
   362  	case USGovernmentCloudName:
   363  		opts.Cloud = cloud.AzureGovernment
   364  	case "":
   365  		// No cloud name provided, so leave at defaults.
   366  	default:
   367  		return nil, fmt.Errorf("invalid cloud name %q", azureEnvironment)
   368  	}
   369  	opts.PerCallPolicies = []policy.Policy{
   370  		correlationIDPolicy{},
   371  		userAgentPolicy{},
   372  	}
   373  	opts.PerCallPolicies = append(opts.PerCallPolicies, extraPolicies...)
   374  	opts.Retry.MaxRetries = -1 // Less than zero means one try and no retries.
   375  
   376  	return opts, nil
   377  }
   378  
   379  // correlationIDPolicy adds the "x-ms-correlation-request-id" header to requests.
   380  // It implements the policy.Policy interface.
   381  type correlationIDPolicy struct{}
   382  
   383  // Do adds the "x-ms-correlation-request-id" header if a request has a correlation ID in its context.
   384  func (p correlationIDPolicy) Do(req *policy.Request) (*http.Response, error) {
   385  	if corrID, ok := tele.CorrIDFromCtx(req.Raw().Context()); ok {
   386  		req.Raw().Header.Set(string(tele.CorrIDKeyVal), string(corrID))
   387  	}
   388  	return req.Next()
   389  }
   390  
   391  // userAgentPolicy extends the "User-Agent" header on requests.
   392  // It implements the policy.Policy interface.
   393  type userAgentPolicy struct{}
   394  
   395  // Do extends the "User-Agent" header of a request by appending CAPZ's user agent.
   396  func (p userAgentPolicy) Do(req *policy.Request) (*http.Response, error) {
   397  	req.Raw().Header.Set("User-Agent", req.Raw().UserAgent()+" "+UserAgent())
   398  	return req.Next()
   399  }
   400  
   401  // CustomPutPatchHeaderPolicy adds custom headers to a PUT or PATCH request.
   402  // It implements the policy.Policy interface.
   403  type CustomPutPatchHeaderPolicy struct {
   404  	Headers map[string]string
   405  }
   406  
   407  // Do adds any custom headers to a PUT or PATCH request.
   408  func (p CustomPutPatchHeaderPolicy) Do(req *policy.Request) (*http.Response, error) {
   409  	if req.Raw().Method == http.MethodPut || req.Raw().Method == http.MethodPatch {
   410  		for key, element := range p.Headers {
   411  			req.Raw().Header.Set(key, element)
   412  		}
   413  	}
   414  
   415  	return req.Next()
   416  }
   417  
   418  // GetNormalizedKubernetesName returns a normalized name for a Kubernetes resource.
   419  func GetNormalizedKubernetesName(name string) string {
   420  	// Remove non-alphanumeric characters, convert to lowercase, and replace underscores with hyphens
   421  	name = strings.ToLower(name)
   422  	re := regexp.MustCompile(`[^a-z0-9\-]+`)
   423  	name = re.ReplaceAllString(name, "-")
   424  
   425  	// Remove leading and trailing hyphens
   426  	name = strings.Trim(name, "-")
   427  	return name
   428  }