sigs.k8s.io/cluster-api-provider-azure@v1.17.0/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 // VMSSID returns the azure resource ID for a given VMSS. 221 func VMSSID(subscriptionID, resourceGroup, vmssName string) string { 222 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachineScaleSets/%s", subscriptionID, resourceGroup, vmssName) 223 } 224 225 // VNetID returns the azure resource ID for a given VNet. 226 func VNetID(subscriptionID, resourceGroup, vnetName string) string { 227 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s", subscriptionID, resourceGroup, vnetName) 228 } 229 230 // SubnetID returns the azure resource ID for a given subnet. 231 func SubnetID(subscriptionID, resourceGroup, vnetName, subnetName string) string { 232 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s", subscriptionID, resourceGroup, vnetName, subnetName) 233 } 234 235 // PublicIPID returns the azure resource ID for a given public IP. 236 func PublicIPID(subscriptionID, resourceGroup, ipName string) string { 237 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/publicIPAddresses/%s", subscriptionID, resourceGroup, ipName) 238 } 239 240 // PublicIPPrefixID returns the azure resource ID for a given public IP prefix. 241 func PublicIPPrefixID(subscriptionID, resourceGroup, ipName string) string { 242 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/publicipprefixes/%s", subscriptionID, resourceGroup, ipName) 243 } 244 245 // RouteTableID returns the azure resource ID for a given route table. 246 func RouteTableID(subscriptionID, resourceGroup, routeTableName string) string { 247 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/routeTables/%s", subscriptionID, resourceGroup, routeTableName) 248 } 249 250 // SecurityGroupID returns the azure resource ID for a given security group. 251 func SecurityGroupID(subscriptionID, resourceGroup, nsgName string) string { 252 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/networkSecurityGroups/%s", subscriptionID, resourceGroup, nsgName) 253 } 254 255 // NatGatewayID returns the azure resource ID for a given NAT gateway. 256 func NatGatewayID(subscriptionID, resourceGroup, natgatewayName string) string { 257 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/natGateways/%s", subscriptionID, resourceGroup, natgatewayName) 258 } 259 260 // NetworkInterfaceID returns the azure resource ID for a given network interface. 261 func NetworkInterfaceID(subscriptionID, resourceGroup, nicName string) string { 262 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/networkInterfaces/%s", subscriptionID, resourceGroup, nicName) 263 } 264 265 // FrontendIPConfigID returns the azure resource ID for a given frontend IP config. 266 func FrontendIPConfigID(subscriptionID, resourceGroup, loadBalancerName, configName string) string { 267 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/frontendIPConfigurations/%s", subscriptionID, resourceGroup, loadBalancerName, configName) 268 } 269 270 // AddressPoolID returns the azure resource ID for a given backend address pool. 271 func AddressPoolID(subscriptionID, resourceGroup, loadBalancerName, backendPoolName string) string { 272 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/backendAddressPools/%s", subscriptionID, resourceGroup, loadBalancerName, backendPoolName) 273 } 274 275 // ProbeID returns the azure resource ID for a given probe. 276 func ProbeID(subscriptionID, resourceGroup, loadBalancerName, probeName string) string { 277 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/probes/%s", subscriptionID, resourceGroup, loadBalancerName, probeName) 278 } 279 280 // NATRuleID returns the azure resource ID for a inbound NAT rule. 281 func NATRuleID(subscriptionID, resourceGroup, loadBalancerName, natRuleName string) string { 282 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/inboundNatRules/%s", subscriptionID, resourceGroup, loadBalancerName, natRuleName) 283 } 284 285 // AvailabilitySetID returns the azure resource ID for a given availability set. 286 func AvailabilitySetID(subscriptionID, resourceGroup, availabilitySetName string) string { 287 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/availabilitySets/%s", subscriptionID, resourceGroup, availabilitySetName) 288 } 289 290 // PrivateDNSZoneID returns the azure resource ID for a given private DNS zone. 291 func PrivateDNSZoneID(subscriptionID, resourceGroup, privateDNSZoneName string) string { 292 return fmt.Sprintf("subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/privateDnsZones/%s", subscriptionID, resourceGroup, privateDNSZoneName) 293 } 294 295 // VirtualNetworkLinkID returns the azure resource ID for a given virtual network link. 296 func VirtualNetworkLinkID(subscriptionID, resourceGroup, privateDNSZoneName, virtualNetworkLinkName string) string { 297 return fmt.Sprintf("subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/privateDnsZones/%s/virtualNetworkLinks/%s", subscriptionID, resourceGroup, privateDNSZoneName, virtualNetworkLinkName) 298 } 299 300 // ManagedClusterID returns the azure resource ID for a given managed cluster. 301 func ManagedClusterID(subscriptionID, resourceGroup, managedClusterName string) string { 302 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.ContainerService/managedClusters/%s", subscriptionID, resourceGroup, managedClusterName) 303 } 304 305 // FleetID returns the azure resource ID for a given fleet manager. 306 func FleetID(subscriptionID, resourceGroup, fleetName string) string { 307 return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.ContainerService/fleets/%s", subscriptionID, resourceGroup, fleetName) 308 } 309 310 // GetBootstrappingVMExtension returns the CAPZ Bootstrapping VM extension. 311 // The CAPZ Bootstrapping extension is a simple clone of https://github.com/Azure/custom-script-extension-linux for Linux or 312 // https://learn.microsoft.com/azure/virtual-machines/extensions/custom-script-windows for Windows. 313 // This extension allows running arbitrary scripts on the VM. 314 // Its role is to detect and report Kubernetes bootstrap failure or success. 315 func GetBootstrappingVMExtension(osType string, cloud string, vmName string, cpuArchitectureType string) *ExtensionSpec { 316 // currently, the bootstrap extension is only available in AzurePublicCloud. 317 if osType == LinuxOS && cloud == PublicCloudName { 318 // The command checks for the existence of the bootstrapSentinelFile on the machine, with retries and sleep between retries. 319 // 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 320 // Go on Ubuntu 20.04. The issue is being tracked here: https://github.com/golang/go/issues/58550 321 // TODO: Remove this once the issue is fixed, or when Ubuntu 20.04 is no longer supported. 322 // We are using 1.1 instead of 1.1.1 for Arm64 as AzureAPI do not allow us to specify the full version. 323 extensionVersion := "1.0" 324 if cpuArchitectureType == string(armcompute.ArchitectureTypesArm64) { 325 extensionVersion = "1.1" 326 } 327 return &ExtensionSpec{ 328 Name: BootstrappingExtensionLinux, 329 VMName: vmName, 330 Publisher: "Microsoft.Azure.ContainerUpstream", 331 Version: extensionVersion, 332 ProtectedSettings: map[string]string{ 333 "commandToExecute": LinuxBootstrapExtensionCommand, 334 }, 335 } 336 } else if osType == WindowsOS && cloud == PublicCloudName { 337 // This command for the existence of the bootstrapSentinelFile on the machine, with retries and sleep between reties. 338 // If the file is not present after the retries are exhausted the extension fails with return code '-2' - ERROR_FILE_NOT_FOUND. 339 return &ExtensionSpec{ 340 Name: BootstrappingExtensionWindows, 341 VMName: vmName, 342 Publisher: "Microsoft.Azure.ContainerUpstream", 343 Version: "1.0", 344 ProtectedSettings: map[string]string{ 345 "commandToExecute": WindowsBootstrapExtensionCommand, 346 }, 347 } 348 } 349 350 return nil 351 } 352 353 // UserAgent specifies a string to append to the agent identifier. 354 func UserAgent() string { 355 return fmt.Sprintf("cluster-api-provider-azure/%s", version.Get().String()) 356 } 357 358 // ARMClientOptions returns default ARM client options for CAPZ SDK v2 requests. 359 func ARMClientOptions(azureEnvironment string, extraPolicies ...policy.Policy) (*arm.ClientOptions, error) { 360 opts := &arm.ClientOptions{} 361 362 switch azureEnvironment { 363 case PublicCloudName: 364 opts.Cloud = cloud.AzurePublic 365 case ChinaCloudName: 366 opts.Cloud = cloud.AzureChina 367 case USGovernmentCloudName: 368 opts.Cloud = cloud.AzureGovernment 369 case "": 370 // No cloud name provided, so leave at defaults. 371 default: 372 return nil, fmt.Errorf("invalid cloud name %q", azureEnvironment) 373 } 374 opts.PerCallPolicies = []policy.Policy{ 375 correlationIDPolicy{}, 376 userAgentPolicy{}, 377 } 378 opts.PerCallPolicies = append(opts.PerCallPolicies, extraPolicies...) 379 opts.Retry.MaxRetries = -1 // Less than zero means one try and no retries. 380 381 return opts, nil 382 } 383 384 // correlationIDPolicy adds the "x-ms-correlation-request-id" header to requests. 385 // It implements the policy.Policy interface. 386 type correlationIDPolicy struct{} 387 388 // Do adds the "x-ms-correlation-request-id" header if a request has a correlation ID in its context. 389 func (p correlationIDPolicy) Do(req *policy.Request) (*http.Response, error) { 390 if corrID, ok := tele.CorrIDFromCtx(req.Raw().Context()); ok { 391 req.Raw().Header.Set(string(tele.CorrIDKeyVal), string(corrID)) 392 } 393 return req.Next() 394 } 395 396 // userAgentPolicy extends the "User-Agent" header on requests. 397 // It implements the policy.Policy interface. 398 type userAgentPolicy struct{} 399 400 // Do extends the "User-Agent" header of a request by appending CAPZ's user agent. 401 func (p userAgentPolicy) Do(req *policy.Request) (*http.Response, error) { 402 req.Raw().Header.Set("User-Agent", req.Raw().UserAgent()+" "+UserAgent()) 403 return req.Next() 404 } 405 406 // CustomPutPatchHeaderPolicy adds custom headers to a PUT or PATCH request. 407 // It implements the policy.Policy interface. 408 type CustomPutPatchHeaderPolicy struct { 409 Headers map[string]string 410 } 411 412 // Do adds any custom headers to a PUT or PATCH request. 413 func (p CustomPutPatchHeaderPolicy) Do(req *policy.Request) (*http.Response, error) { 414 if req.Raw().Method == http.MethodPut || req.Raw().Method == http.MethodPatch { 415 for key, element := range p.Headers { 416 req.Raw().Header.Set(key, element) 417 } 418 } 419 420 return req.Next() 421 } 422 423 // GetNormalizedKubernetesName returns a normalized name for a Kubernetes resource. 424 func GetNormalizedKubernetesName(name string) string { 425 // Remove non-alphanumeric characters, convert to lowercase, and replace underscores with hyphens 426 name = strings.ToLower(name) 427 re := regexp.MustCompile(`[^a-z0-9\-]+`) 428 name = re.ReplaceAllString(name, "-") 429 430 // Remove leading and trailing hyphens 431 name = strings.Trim(name, "-") 432 return name 433 }