sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/scope/cluster.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/json" 22 "fmt" 23 "hash/fnv" 24 "sort" 25 "strconv" 26 "strings" 27 28 asonetworkv1api20201101 "github.com/Azure/azure-service-operator/v2/api/network/v1api20201101" 29 asonetworkv1api20220701 "github.com/Azure/azure-service-operator/v2/api/network/v1api20220701" 30 asoresourcesv1 "github.com/Azure/azure-service-operator/v2/api/resources/v1api20200601" 31 "github.com/pkg/errors" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/utils/net" 34 "k8s.io/utils/ptr" 35 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 36 "sigs.k8s.io/cluster-api-provider-azure/azure" 37 "sigs.k8s.io/cluster-api-provider-azure/azure/services/bastionhosts" 38 "sigs.k8s.io/cluster-api-provider-azure/azure/services/groups" 39 "sigs.k8s.io/cluster-api-provider-azure/azure/services/loadbalancers" 40 "sigs.k8s.io/cluster-api-provider-azure/azure/services/natgateways" 41 "sigs.k8s.io/cluster-api-provider-azure/azure/services/privatedns" 42 "sigs.k8s.io/cluster-api-provider-azure/azure/services/privateendpoints" 43 "sigs.k8s.io/cluster-api-provider-azure/azure/services/publicips" 44 "sigs.k8s.io/cluster-api-provider-azure/azure/services/routetables" 45 "sigs.k8s.io/cluster-api-provider-azure/azure/services/securitygroups" 46 "sigs.k8s.io/cluster-api-provider-azure/azure/services/subnets" 47 "sigs.k8s.io/cluster-api-provider-azure/azure/services/virtualnetworks" 48 "sigs.k8s.io/cluster-api-provider-azure/azure/services/vnetpeerings" 49 "sigs.k8s.io/cluster-api-provider-azure/util/futures" 50 "sigs.k8s.io/cluster-api-provider-azure/util/tele" 51 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 52 "sigs.k8s.io/cluster-api/util/conditions" 53 "sigs.k8s.io/cluster-api/util/patch" 54 "sigs.k8s.io/controller-runtime/pkg/client" 55 ) 56 57 // ClusterScopeParams defines the input parameters used to create a new Scope. 58 type ClusterScopeParams struct { 59 AzureClients 60 Client client.Client 61 Cluster *clusterv1.Cluster 62 AzureCluster *infrav1.AzureCluster 63 Cache *ClusterCache 64 Timeouts azure.AsyncReconciler 65 } 66 67 // NewClusterScope creates a new Scope from the supplied parameters. 68 // This is meant to be called for each reconcile iteration. 69 func NewClusterScope(ctx context.Context, params ClusterScopeParams) (*ClusterScope, error) { 70 ctx, _, done := tele.StartSpanWithLogger(ctx, "azure.clusterScope.NewClusterScope") 71 defer done() 72 73 if params.Cluster == nil { 74 return nil, errors.New("failed to generate new scope from nil Cluster") 75 } 76 if params.AzureCluster == nil { 77 return nil, errors.New("failed to generate new scope from nil AzureCluster") 78 } 79 80 credentialsProvider, err := NewAzureClusterCredentialsProvider(ctx, params.Client, params.AzureCluster) 81 if err != nil { 82 return nil, errors.Wrap(err, "failed to init credentials provider") 83 } 84 err = params.AzureClients.setCredentialsWithProvider(ctx, params.AzureCluster.Spec.SubscriptionID, params.AzureCluster.Spec.AzureEnvironment, credentialsProvider) 85 if err != nil { 86 return nil, errors.Wrap(err, "failed to configure azure settings and credentials for Identity") 87 } 88 89 if params.Cache == nil { 90 params.Cache = &ClusterCache{} 91 } 92 93 helper, err := patch.NewHelper(params.AzureCluster, params.Client) 94 if err != nil { 95 return nil, errors.Errorf("failed to init patch helper: %v", err) 96 } 97 98 return &ClusterScope{ 99 Client: params.Client, 100 AzureClients: params.AzureClients, 101 Cluster: params.Cluster, 102 AzureCluster: params.AzureCluster, 103 patchHelper: helper, 104 cache: params.Cache, 105 AsyncReconciler: params.Timeouts, 106 }, nil 107 } 108 109 // ClusterScope defines the basic context for an actuator to operate upon. 110 type ClusterScope struct { 111 Client client.Client 112 patchHelper *patch.Helper 113 cache *ClusterCache 114 115 AzureClients 116 Cluster *clusterv1.Cluster 117 AzureCluster *infrav1.AzureCluster 118 azure.AsyncReconciler 119 } 120 121 // ClusterCache stores ClusterCache data locally so we don't have to hit the API multiple times within the same reconcile loop. 122 type ClusterCache struct { 123 isVnetManaged *bool 124 } 125 126 // BaseURI returns the Azure ResourceManagerEndpoint. 127 func (s *ClusterScope) BaseURI() string { 128 return s.ResourceManagerEndpoint 129 } 130 131 // GetClient returns the controller-runtime client. 132 func (s *ClusterScope) GetClient() client.Client { 133 return s.Client 134 } 135 136 // GetDeletionTimestamp returns the deletion timestamp of the Cluster. 137 func (s *ClusterScope) GetDeletionTimestamp() *metav1.Time { 138 return s.Cluster.DeletionTimestamp 139 } 140 141 // ASOOwner implements aso.Scope. 142 func (s *ClusterScope) ASOOwner() client.Object { 143 return s.AzureCluster 144 } 145 146 // PublicIPSpecs returns the public IP specs. 147 func (s *ClusterScope) PublicIPSpecs() []azure.ResourceSpecGetter { 148 var publicIPSpecs []azure.ResourceSpecGetter 149 150 // Public IP specs for control plane lb 151 var controlPlaneOutboundIPSpecs []azure.ResourceSpecGetter 152 if s.IsAPIServerPrivate() { 153 // Public IP specs for control plane outbound lb 154 if s.ControlPlaneOutboundLB() != nil { 155 for _, ip := range s.ControlPlaneOutboundLB().FrontendIPs { 156 controlPlaneOutboundIPSpecs = append(controlPlaneOutboundIPSpecs, &publicips.PublicIPSpec{ 157 Name: ip.PublicIP.Name, 158 ResourceGroup: s.ResourceGroup(), 159 ClusterName: s.ClusterName(), 160 DNSName: "", // Set to default value 161 IsIPv6: false, // Set to default value 162 Location: s.Location(), 163 ExtendedLocation: s.ExtendedLocation(), 164 FailureDomains: s.FailureDomains(), 165 AdditionalTags: s.AdditionalTags(), 166 }) 167 } 168 } 169 } else { 170 controlPlaneOutboundIPSpecs = []azure.ResourceSpecGetter{ 171 &publicips.PublicIPSpec{ 172 Name: s.APIServerPublicIP().Name, 173 ResourceGroup: s.ResourceGroup(), 174 DNSName: s.APIServerPublicIP().DNSName, 175 IsIPv6: false, // Currently azure requires an IPv4 lb rule to enable IPv6 176 ClusterName: s.ClusterName(), 177 Location: s.Location(), 178 ExtendedLocation: s.ExtendedLocation(), 179 FailureDomains: s.FailureDomains(), 180 AdditionalTags: s.AdditionalTags(), 181 IPTags: s.APIServerPublicIP().IPTags, 182 }, 183 } 184 } 185 publicIPSpecs = append(publicIPSpecs, controlPlaneOutboundIPSpecs...) 186 187 // Public IP specs for node outbound lb 188 if s.NodeOutboundLB() != nil { 189 for _, ip := range s.NodeOutboundLB().FrontendIPs { 190 publicIPSpecs = append(publicIPSpecs, &publicips.PublicIPSpec{ 191 Name: ip.PublicIP.Name, 192 ResourceGroup: s.ResourceGroup(), 193 ClusterName: s.ClusterName(), 194 DNSName: "", // Set to default value 195 IsIPv6: false, // Set to default value 196 Location: s.Location(), 197 ExtendedLocation: s.ExtendedLocation(), 198 FailureDomains: s.FailureDomains(), 199 AdditionalTags: s.AdditionalTags(), 200 }) 201 } 202 } 203 204 // Public IP specs for node NAT gateways 205 var nodeNatGatewayIPSpecs []azure.ResourceSpecGetter 206 for _, subnet := range s.NodeSubnets() { 207 if subnet.IsNatGatewayEnabled() { 208 nodeNatGatewayIPSpecs = append(nodeNatGatewayIPSpecs, &publicips.PublicIPSpec{ 209 Name: subnet.NatGateway.NatGatewayIP.Name, 210 ResourceGroup: s.ResourceGroup(), 211 DNSName: subnet.NatGateway.NatGatewayIP.DNSName, 212 IsIPv6: false, // Public IP is IPv4 by default 213 ClusterName: s.ClusterName(), 214 Location: s.Location(), 215 FailureDomains: s.FailureDomains(), 216 AdditionalTags: s.AdditionalTags(), 217 IPTags: subnet.NatGateway.NatGatewayIP.IPTags, 218 }) 219 } 220 publicIPSpecs = append(publicIPSpecs, nodeNatGatewayIPSpecs...) 221 } 222 223 if azureBastion := s.AzureBastion(); azureBastion != nil { 224 // public IP for Azure Bastion. 225 azureBastionPublicIP := &publicips.PublicIPSpec{ 226 Name: azureBastion.PublicIP.Name, 227 ResourceGroup: s.ResourceGroup(), 228 DNSName: azureBastion.PublicIP.DNSName, 229 IsIPv6: false, // Public IP is IPv4 by default 230 ClusterName: s.ClusterName(), 231 Location: s.Location(), 232 FailureDomains: s.FailureDomains(), 233 AdditionalTags: s.AdditionalTags(), 234 IPTags: azureBastion.PublicIP.IPTags, 235 } 236 publicIPSpecs = append(publicIPSpecs, azureBastionPublicIP) 237 } 238 239 return publicIPSpecs 240 } 241 242 // LBSpecs returns the load balancer specs. 243 func (s *ClusterScope) LBSpecs() []azure.ResourceSpecGetter { 244 specs := []azure.ResourceSpecGetter{ 245 &loadbalancers.LBSpec{ 246 // API Server LB 247 Name: s.APIServerLB().Name, 248 ResourceGroup: s.ResourceGroup(), 249 SubscriptionID: s.SubscriptionID(), 250 ClusterName: s.ClusterName(), 251 Location: s.Location(), 252 ExtendedLocation: s.ExtendedLocation(), 253 VNetName: s.Vnet().Name, 254 VNetResourceGroup: s.Vnet().ResourceGroup, 255 SubnetName: s.ControlPlaneSubnet().Name, 256 FrontendIPConfigs: s.APIServerLB().FrontendIPs, 257 APIServerPort: s.APIServerPort(), 258 Type: s.APIServerLB().Type, 259 SKU: s.APIServerLB().SKU, 260 Role: infrav1.APIServerRole, 261 BackendPoolName: s.APIServerLB().BackendPool.Name, 262 IdleTimeoutInMinutes: s.APIServerLB().IdleTimeoutInMinutes, 263 AdditionalTags: s.AdditionalTags(), 264 }, 265 } 266 267 // Node outbound LB 268 if s.NodeOutboundLB() != nil { 269 specs = append(specs, &loadbalancers.LBSpec{ 270 Name: s.NodeOutboundLB().Name, 271 ResourceGroup: s.ResourceGroup(), 272 SubscriptionID: s.SubscriptionID(), 273 ClusterName: s.ClusterName(), 274 Location: s.Location(), 275 ExtendedLocation: s.ExtendedLocation(), 276 VNetName: s.Vnet().Name, 277 VNetResourceGroup: s.Vnet().ResourceGroup, 278 FrontendIPConfigs: s.NodeOutboundLB().FrontendIPs, 279 Type: s.NodeOutboundLB().Type, 280 SKU: s.NodeOutboundLB().SKU, 281 BackendPoolName: s.NodeOutboundLB().BackendPool.Name, 282 IdleTimeoutInMinutes: s.NodeOutboundLB().IdleTimeoutInMinutes, 283 Role: infrav1.NodeOutboundRole, 284 AdditionalTags: s.AdditionalTags(), 285 }) 286 } 287 288 // Control Plane Outbound LB 289 if s.ControlPlaneOutboundLB() != nil { 290 specs = append(specs, &loadbalancers.LBSpec{ 291 Name: s.ControlPlaneOutboundLB().Name, 292 ResourceGroup: s.ResourceGroup(), 293 SubscriptionID: s.SubscriptionID(), 294 ClusterName: s.ClusterName(), 295 Location: s.Location(), 296 ExtendedLocation: s.ExtendedLocation(), 297 VNetName: s.Vnet().Name, 298 VNetResourceGroup: s.Vnet().ResourceGroup, 299 FrontendIPConfigs: s.ControlPlaneOutboundLB().FrontendIPs, 300 Type: s.ControlPlaneOutboundLB().Type, 301 SKU: s.ControlPlaneOutboundLB().SKU, 302 BackendPoolName: s.ControlPlaneOutboundLB().BackendPool.Name, 303 IdleTimeoutInMinutes: s.ControlPlaneOutboundLB().IdleTimeoutInMinutes, 304 Role: infrav1.ControlPlaneOutboundRole, 305 AdditionalTags: s.AdditionalTags(), 306 }) 307 } 308 309 return specs 310 } 311 312 // RouteTableSpecs returns the subnet route tables. 313 func (s *ClusterScope) RouteTableSpecs() []azure.ResourceSpecGetter { 314 var specs []azure.ResourceSpecGetter 315 for _, subnet := range s.AzureCluster.Spec.NetworkSpec.Subnets { 316 if subnet.RouteTable.Name != "" { 317 specs = append(specs, &routetables.RouteTableSpec{ 318 Name: subnet.RouteTable.Name, 319 Location: s.Location(), 320 ResourceGroup: s.Vnet().ResourceGroup, 321 ClusterName: s.ClusterName(), 322 AdditionalTags: s.AdditionalTags(), 323 }) 324 } 325 } 326 327 return specs 328 } 329 330 // NatGatewaySpecs returns the node NAT gateway. 331 func (s *ClusterScope) NatGatewaySpecs() []azure.ASOResourceSpecGetter[*asonetworkv1api20220701.NatGateway] { 332 natGatewaySet := make(map[string]struct{}) 333 var natGateways []azure.ASOResourceSpecGetter[*asonetworkv1api20220701.NatGateway] 334 335 // We ignore the control plane NAT gateway, as we will always use a LB to enable egress on the control plane. 336 for _, subnet := range s.NodeSubnets() { 337 if subnet.IsNatGatewayEnabled() { 338 if _, ok := natGatewaySet[subnet.NatGateway.Name]; !ok { 339 natGatewaySet[subnet.NatGateway.Name] = struct{}{} // empty struct to represent hash set 340 natGateways = append(natGateways, &natgateways.NatGatewaySpec{ 341 Name: subnet.NatGateway.Name, 342 ResourceGroup: s.ResourceGroup(), 343 SubscriptionID: s.SubscriptionID(), 344 Location: s.Location(), 345 ClusterName: s.ClusterName(), 346 NatGatewayIP: infrav1.PublicIPSpec{ 347 Name: subnet.NatGateway.NatGatewayIP.Name, 348 }, 349 AdditionalTags: s.AdditionalTags(), 350 // We need to know if the VNet is managed to decide if this NAT Gateway was-managed or not. 351 IsVnetManaged: s.IsVnetManaged(), 352 }) 353 } 354 } 355 } 356 357 return natGateways 358 } 359 360 // NSGSpecs returns the security group specs. 361 func (s *ClusterScope) NSGSpecs() []azure.ResourceSpecGetter { 362 nsgspecs := make([]azure.ResourceSpecGetter, len(s.AzureCluster.Spec.NetworkSpec.Subnets)) 363 for i, subnet := range s.AzureCluster.Spec.NetworkSpec.Subnets { 364 nsgspecs[i] = &securitygroups.NSGSpec{ 365 Name: subnet.SecurityGroup.Name, 366 SecurityRules: subnet.SecurityGroup.SecurityRules, 367 ResourceGroup: s.Vnet().ResourceGroup, 368 Location: s.Location(), 369 ClusterName: s.ClusterName(), 370 AdditionalTags: s.AdditionalTags(), 371 LastAppliedSecurityRules: s.getLastAppliedSecurityRules(subnet.SecurityGroup.Name), 372 } 373 } 374 375 return nsgspecs 376 } 377 378 // SubnetSpecs returns the subnets specs. 379 func (s *ClusterScope) SubnetSpecs() []azure.ASOResourceSpecGetter[*asonetworkv1api20201101.VirtualNetworksSubnet] { 380 numberOfSubnets := len(s.AzureCluster.Spec.NetworkSpec.Subnets) 381 if s.IsAzureBastionEnabled() { 382 numberOfSubnets++ 383 } 384 385 subnetSpecs := make([]azure.ASOResourceSpecGetter[*asonetworkv1api20201101.VirtualNetworksSubnet], 0, numberOfSubnets) 386 387 for _, subnet := range s.AzureCluster.Spec.NetworkSpec.Subnets { 388 subnetSpec := &subnets.SubnetSpec{ 389 Name: subnet.Name, 390 ResourceGroup: s.ResourceGroup(), 391 SubscriptionID: s.SubscriptionID(), 392 CIDRs: subnet.CIDRBlocks, 393 VNetName: s.Vnet().Name, 394 VNetResourceGroup: s.Vnet().ResourceGroup, 395 IsVNetManaged: s.IsVnetManaged(), 396 RouteTableName: subnet.RouteTable.Name, 397 SecurityGroupName: subnet.SecurityGroup.Name, 398 NatGatewayName: subnet.NatGateway.Name, 399 ServiceEndpoints: subnet.ServiceEndpoints, 400 } 401 subnetSpecs = append(subnetSpecs, subnetSpec) 402 } 403 404 if s.IsAzureBastionEnabled() { 405 azureBastionSubnet := s.AzureCluster.Spec.BastionSpec.AzureBastion.Subnet 406 subnetSpecs = append(subnetSpecs, &subnets.SubnetSpec{ 407 Name: azureBastionSubnet.Name, 408 ResourceGroup: s.ResourceGroup(), 409 SubscriptionID: s.SubscriptionID(), 410 CIDRs: azureBastionSubnet.CIDRBlocks, 411 VNetName: s.Vnet().Name, 412 VNetResourceGroup: s.Vnet().ResourceGroup, 413 IsVNetManaged: s.IsVnetManaged(), 414 SecurityGroupName: azureBastionSubnet.SecurityGroup.Name, 415 RouteTableName: azureBastionSubnet.RouteTable.Name, 416 ServiceEndpoints: azureBastionSubnet.ServiceEndpoints, 417 }) 418 } 419 420 return subnetSpecs 421 } 422 423 // GroupSpecs returns the resource group spec. 424 func (s *ClusterScope) GroupSpecs() []azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup] { 425 specs := []azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup]{ 426 &groups.GroupSpec{ 427 Name: s.ResourceGroup(), 428 AzureName: s.ResourceGroup(), 429 Location: s.Location(), 430 ClusterName: s.ClusterName(), 431 AdditionalTags: s.AdditionalTags(), 432 }, 433 } 434 if s.Vnet().ResourceGroup != "" && s.Vnet().ResourceGroup != s.ResourceGroup() { 435 specs = append(specs, &groups.GroupSpec{ 436 Name: azure.GetNormalizedKubernetesName(s.Vnet().ResourceGroup), 437 AzureName: s.Vnet().ResourceGroup, 438 Location: s.Location(), 439 ClusterName: s.ClusterName(), 440 AdditionalTags: s.AdditionalTags(), 441 }) 442 } 443 return specs 444 } 445 446 // VnetPeeringSpecs returns the virtual network peering specs. 447 func (s *ClusterScope) VnetPeeringSpecs() []azure.ResourceSpecGetter { 448 peeringSpecs := make([]azure.ResourceSpecGetter, 2*len(s.Vnet().Peerings)) 449 for i, peering := range s.Vnet().Peerings { 450 forwardPeering := &vnetpeerings.VnetPeeringSpec{ 451 PeeringName: azure.GenerateVnetPeeringName(s.Vnet().Name, peering.RemoteVnetName), 452 SourceVnetName: s.Vnet().Name, 453 SourceResourceGroup: s.Vnet().ResourceGroup, 454 RemoteVnetName: peering.RemoteVnetName, 455 RemoteResourceGroup: peering.ResourceGroup, 456 SubscriptionID: s.SubscriptionID(), 457 AllowForwardedTraffic: peering.ForwardPeeringProperties.AllowForwardedTraffic, 458 AllowGatewayTransit: peering.ForwardPeeringProperties.AllowGatewayTransit, 459 AllowVirtualNetworkAccess: peering.ForwardPeeringProperties.AllowVirtualNetworkAccess, 460 UseRemoteGateways: peering.ForwardPeeringProperties.UseRemoteGateways, 461 } 462 reversePeering := &vnetpeerings.VnetPeeringSpec{ 463 PeeringName: azure.GenerateVnetPeeringName(peering.RemoteVnetName, s.Vnet().Name), 464 SourceVnetName: peering.RemoteVnetName, 465 SourceResourceGroup: peering.ResourceGroup, 466 RemoteVnetName: s.Vnet().Name, 467 RemoteResourceGroup: s.Vnet().ResourceGroup, 468 SubscriptionID: s.SubscriptionID(), 469 AllowForwardedTraffic: peering.ReversePeeringProperties.AllowForwardedTraffic, 470 AllowGatewayTransit: peering.ReversePeeringProperties.AllowGatewayTransit, 471 AllowVirtualNetworkAccess: peering.ReversePeeringProperties.AllowVirtualNetworkAccess, 472 UseRemoteGateways: peering.ReversePeeringProperties.UseRemoteGateways, 473 } 474 peeringSpecs[i*2] = forwardPeering 475 peeringSpecs[i*2+1] = reversePeering 476 } 477 478 return peeringSpecs 479 } 480 481 // VNetSpec returns the virtual network spec. 482 func (s *ClusterScope) VNetSpec() azure.ASOResourceSpecGetter[*asonetworkv1api20201101.VirtualNetwork] { 483 return &virtualnetworks.VNetSpec{ 484 ResourceGroup: s.Vnet().ResourceGroup, 485 Name: s.Vnet().Name, 486 CIDRs: s.Vnet().CIDRBlocks, 487 ExtendedLocation: s.ExtendedLocation(), 488 Location: s.Location(), 489 ClusterName: s.ClusterName(), 490 AdditionalTags: s.AdditionalTags(), 491 } 492 } 493 494 // PrivateDNSSpec returns the private dns zone spec. 495 func (s *ClusterScope) PrivateDNSSpec() (zoneSpec azure.ResourceSpecGetter, linkSpec, recordSpec []azure.ResourceSpecGetter) { 496 if s.IsAPIServerPrivate() { 497 zone := privatedns.ZoneSpec{ 498 Name: s.GetPrivateDNSZoneName(), 499 ResourceGroup: s.ResourceGroup(), 500 ClusterName: s.ClusterName(), 501 AdditionalTags: s.AdditionalTags(), 502 } 503 504 links := make([]azure.ResourceSpecGetter, 1+len(s.Vnet().Peerings)) 505 links[0] = privatedns.LinkSpec{ 506 Name: azure.GenerateVNetLinkName(s.Vnet().Name), 507 ZoneName: s.GetPrivateDNSZoneName(), 508 SubscriptionID: s.SubscriptionID(), 509 VNetResourceGroup: s.Vnet().ResourceGroup, 510 VNetName: s.Vnet().Name, 511 ResourceGroup: s.ResourceGroup(), 512 ClusterName: s.ClusterName(), 513 AdditionalTags: s.AdditionalTags(), 514 } 515 for i, peering := range s.Vnet().Peerings { 516 links[i+1] = privatedns.LinkSpec{ 517 Name: azure.GenerateVNetLinkName(peering.RemoteVnetName), 518 ZoneName: s.GetPrivateDNSZoneName(), 519 SubscriptionID: s.SubscriptionID(), 520 VNetResourceGroup: peering.ResourceGroup, 521 VNetName: peering.RemoteVnetName, 522 ResourceGroup: s.ResourceGroup(), 523 ClusterName: s.ClusterName(), 524 AdditionalTags: s.AdditionalTags(), 525 } 526 } 527 528 records := make([]azure.ResourceSpecGetter, 1) 529 records[0] = privatedns.RecordSpec{ 530 Record: infrav1.AddressRecord{ 531 Hostname: azure.PrivateAPIServerHostname, 532 IP: s.APIServerPrivateIP(), 533 }, 534 ZoneName: s.GetPrivateDNSZoneName(), 535 ResourceGroup: s.ResourceGroup(), 536 } 537 538 return zone, links, records 539 } 540 541 return nil, nil, nil 542 } 543 544 // IsAzureBastionEnabled returns true if the azure bastion is enabled. 545 func (s *ClusterScope) IsAzureBastionEnabled() bool { 546 return s.AzureCluster.Spec.BastionSpec.AzureBastion != nil 547 } 548 549 // AzureBastion returns the cluster AzureBastion. 550 func (s *ClusterScope) AzureBastion() *infrav1.AzureBastion { 551 return s.AzureCluster.Spec.BastionSpec.AzureBastion 552 } 553 554 // AzureBastionSpec returns the bastion spec. 555 func (s *ClusterScope) AzureBastionSpec() azure.ASOResourceSpecGetter[*asonetworkv1api20220701.BastionHost] { 556 if s.IsAzureBastionEnabled() { 557 subnetID := azure.SubnetID(s.SubscriptionID(), s.ResourceGroup(), s.Vnet().Name, s.AzureBastion().Subnet.Name) 558 publicIPID := azure.PublicIPID(s.SubscriptionID(), s.ResourceGroup(), s.AzureBastion().PublicIP.Name) 559 560 return &bastionhosts.AzureBastionSpec{ 561 Name: s.AzureBastion().Name, 562 ResourceGroup: s.ResourceGroup(), 563 Location: s.Location(), 564 ClusterName: s.ClusterName(), 565 SubnetID: subnetID, 566 PublicIPID: publicIPID, 567 Sku: s.AzureBastion().Sku, 568 EnableTunneling: s.AzureBastion().EnableTunneling, 569 } 570 } 571 572 return nil 573 } 574 575 // Vnet returns the cluster Vnet. 576 func (s *ClusterScope) Vnet() *infrav1.VnetSpec { 577 return &s.AzureCluster.Spec.NetworkSpec.Vnet 578 } 579 580 // IsVnetManaged returns true if the vnet is managed. 581 func (s *ClusterScope) IsVnetManaged() bool { 582 if s.cache.isVnetManaged != nil { 583 return ptr.Deref(s.cache.isVnetManaged, false) 584 } 585 isVnetManaged := s.Vnet().ID == "" || s.Vnet().Tags.HasOwned(s.ClusterName()) 586 s.cache.isVnetManaged = ptr.To(isVnetManaged) 587 return isVnetManaged 588 } 589 590 // IsIPv6Enabled returns true if IPv6 is enabled. 591 func (s *ClusterScope) IsIPv6Enabled() bool { 592 for _, cidr := range s.AzureCluster.Spec.NetworkSpec.Vnet.CIDRBlocks { 593 if net.IsIPv6CIDRString(cidr) { 594 return true 595 } 596 } 597 return false 598 } 599 600 // Subnets returns the cluster subnets. 601 func (s *ClusterScope) Subnets() infrav1.Subnets { 602 return s.AzureCluster.Spec.NetworkSpec.Subnets 603 } 604 605 // ControlPlaneSubnet returns the cluster control plane subnet. 606 func (s *ClusterScope) ControlPlaneSubnet() infrav1.SubnetSpec { 607 subnet, _ := s.AzureCluster.Spec.NetworkSpec.GetControlPlaneSubnet() 608 return subnet 609 } 610 611 // NodeSubnets returns the subnets with the node role. 612 func (s *ClusterScope) NodeSubnets() []infrav1.SubnetSpec { 613 subnets := []infrav1.SubnetSpec{} 614 for _, subnet := range s.AzureCluster.Spec.NetworkSpec.Subnets { 615 if subnet.Role == infrav1.SubnetNode || subnet.Role == infrav1.SubnetCluster { 616 subnets = append(subnets, subnet) 617 } 618 } 619 620 return subnets 621 } 622 623 // Subnet returns the subnet with the provided name. 624 func (s *ClusterScope) Subnet(name string) infrav1.SubnetSpec { 625 for _, sn := range s.AzureCluster.Spec.NetworkSpec.Subnets { 626 if sn.Name == name { 627 return sn 628 } 629 } 630 631 return infrav1.SubnetSpec{} 632 } 633 634 // SetSubnet sets the subnet spec for the subnet with the same name. 635 func (s *ClusterScope) SetSubnet(subnetSpec infrav1.SubnetSpec) { 636 for i, sn := range s.AzureCluster.Spec.NetworkSpec.Subnets { 637 if sn.Name == subnetSpec.Name { 638 s.AzureCluster.Spec.NetworkSpec.Subnets[i] = subnetSpec 639 return 640 } 641 } 642 } 643 644 // SetNatGatewayIDInSubnets sets the NAT Gateway ID in the subnets with the same name. 645 func (s *ClusterScope) SetNatGatewayIDInSubnets(name string, id string) { 646 for _, subnet := range s.Subnets() { 647 if subnet.NatGateway.Name == name { 648 subnet.NatGateway.ID = id 649 s.SetSubnet(subnet) 650 } 651 } 652 } 653 654 // UpdateSubnetCIDRs updates the subnet CIDRs for the subnet with the same name. 655 func (s *ClusterScope) UpdateSubnetCIDRs(name string, cidrBlocks []string) { 656 subnetSpecInfra := s.Subnet(name) 657 subnetSpecInfra.CIDRBlocks = cidrBlocks 658 s.SetSubnet(subnetSpecInfra) 659 } 660 661 // UpdateSubnetID updates the subnet ID for the subnet with the same name. 662 func (s *ClusterScope) UpdateSubnetID(name string, id string) { 663 subnetSpecInfra := s.Subnet(name) 664 subnetSpecInfra.ID = id 665 s.SetSubnet(subnetSpecInfra) 666 } 667 668 // ControlPlaneRouteTable returns the cluster controlplane routetable. 669 func (s *ClusterScope) ControlPlaneRouteTable() infrav1.RouteTable { 670 subnet, _ := s.AzureCluster.Spec.NetworkSpec.GetControlPlaneSubnet() 671 return subnet.RouteTable 672 } 673 674 // APIServerLB returns the cluster API Server load balancer. 675 func (s *ClusterScope) APIServerLB() *infrav1.LoadBalancerSpec { 676 return &s.AzureCluster.Spec.NetworkSpec.APIServerLB 677 } 678 679 // NodeOutboundLB returns the cluster node outbound load balancer. 680 func (s *ClusterScope) NodeOutboundLB() *infrav1.LoadBalancerSpec { 681 return s.AzureCluster.Spec.NetworkSpec.NodeOutboundLB 682 } 683 684 // ControlPlaneOutboundLB returns the cluster control plane outbound load balancer. 685 func (s *ClusterScope) ControlPlaneOutboundLB() *infrav1.LoadBalancerSpec { 686 return s.AzureCluster.Spec.NetworkSpec.ControlPlaneOutboundLB 687 } 688 689 // APIServerLBName returns the API Server LB name. 690 func (s *ClusterScope) APIServerLBName() string { 691 return s.APIServerLB().Name 692 } 693 694 // IsAPIServerPrivate returns true if the API Server LB is of type Internal. 695 func (s *ClusterScope) IsAPIServerPrivate() bool { 696 return s.APIServerLB().Type == infrav1.Internal 697 } 698 699 // APIServerPublicIP returns the API Server public IP. 700 func (s *ClusterScope) APIServerPublicIP() *infrav1.PublicIPSpec { 701 return s.APIServerLB().FrontendIPs[0].PublicIP 702 } 703 704 // APIServerPrivateIP returns the API Server private IP. 705 func (s *ClusterScope) APIServerPrivateIP() string { 706 return s.APIServerLB().FrontendIPs[0].PrivateIPAddress 707 } 708 709 // GetPrivateDNSZoneName returns the Private DNS Zone from the spec or generate it from cluster name. 710 func (s *ClusterScope) GetPrivateDNSZoneName() string { 711 if len(s.AzureCluster.Spec.NetworkSpec.PrivateDNSZoneName) > 0 { 712 return s.AzureCluster.Spec.NetworkSpec.PrivateDNSZoneName 713 } 714 return azure.GeneratePrivateDNSZoneName(s.ClusterName()) 715 } 716 717 // APIServerLBPoolName returns the API Server LB backend pool name. 718 func (s *ClusterScope) APIServerLBPoolName() string { 719 return s.APIServerLB().BackendPool.Name 720 } 721 722 // OutboundLB returns the outbound LB. 723 func (s *ClusterScope) outboundLB(role string) *infrav1.LoadBalancerSpec { 724 if role == infrav1.Node { 725 return s.NodeOutboundLB() 726 } 727 if s.IsAPIServerPrivate() { 728 return s.ControlPlaneOutboundLB() 729 } 730 return s.APIServerLB() 731 } 732 733 // OutboundLBName returns the name of the outbound LB. 734 func (s *ClusterScope) OutboundLBName(role string) string { 735 lb := s.outboundLB(role) 736 if lb == nil { 737 return "" 738 } 739 return lb.Name 740 } 741 742 // OutboundPoolName returns the outbound LB backend pool name. 743 func (s *ClusterScope) OutboundPoolName(role string) string { 744 lb := s.outboundLB(role) 745 if lb == nil { 746 return "" 747 } 748 return lb.BackendPool.Name 749 } 750 751 // ResourceGroup returns the cluster resource group. 752 func (s *ClusterScope) ResourceGroup() string { 753 return s.AzureCluster.Spec.ResourceGroup 754 } 755 756 // NodeResourceGroup returns the resource group where nodes live. 757 // For AzureClusters this is the same as the cluster RG. 758 func (s *ClusterScope) NodeResourceGroup() string { 759 return s.ResourceGroup() 760 } 761 762 // ClusterName returns the cluster name. 763 func (s *ClusterScope) ClusterName() string { 764 return s.Cluster.Name 765 } 766 767 // Namespace returns the cluster namespace. 768 func (s *ClusterScope) Namespace() string { 769 return s.Cluster.Namespace 770 } 771 772 // Location returns the cluster location. 773 func (s *ClusterScope) Location() string { 774 return s.AzureCluster.Spec.Location 775 } 776 777 // AvailabilitySetEnabled informs machines that they should be part of an Availability Set. 778 func (s *ClusterScope) AvailabilitySetEnabled() bool { 779 return len(s.AzureCluster.Status.FailureDomains) == 0 780 } 781 782 // CloudProviderConfigOverrides returns the cloud provider config overrides for the cluster. 783 func (s *ClusterScope) CloudProviderConfigOverrides() *infrav1.CloudProviderConfigOverrides { 784 return s.AzureCluster.Spec.CloudProviderConfigOverrides 785 } 786 787 // ExtendedLocationName returns ExtendedLocation name for the cluster. 788 func (s *ClusterScope) ExtendedLocationName() string { 789 if s.ExtendedLocation() == nil { 790 return "" 791 } 792 return s.ExtendedLocation().Name 793 } 794 795 // ExtendedLocationType returns ExtendedLocation type for the cluster. 796 func (s *ClusterScope) ExtendedLocationType() string { 797 if s.ExtendedLocation() == nil { 798 return "" 799 } 800 return s.ExtendedLocation().Type 801 } 802 803 // ExtendedLocation returns the cluster extendedLocation. 804 func (s *ClusterScope) ExtendedLocation() *infrav1.ExtendedLocationSpec { 805 return s.AzureCluster.Spec.ExtendedLocation 806 } 807 808 // GenerateFQDN generates a fully qualified domain name, based on a hash, cluster name and cluster location. 809 func (s *ClusterScope) GenerateFQDN(ipName string) string { 810 h := fnv.New32a() 811 if _, err := fmt.Fprintf(h, "%s/%s/%s", s.SubscriptionID(), s.ResourceGroup(), ipName); err != nil { 812 return "" 813 } 814 hash := fmt.Sprintf("%x", h.Sum32()) 815 return strings.ToLower(fmt.Sprintf("%s-%s.%s.%s", s.ClusterName(), hash, s.Location(), s.AzureClients.ResourceManagerVMDNSSuffix)) 816 } 817 818 // GenerateLegacyFQDN generates an IP name and a fully qualified domain name, based on a hash, cluster name and cluster location. 819 // Deprecated: use GenerateFQDN instead. 820 func (s *ClusterScope) GenerateLegacyFQDN() (ip string, domain string) { 821 h := fnv.New32a() 822 if _, err := fmt.Fprintf(h, "%s/%s/%s", s.SubscriptionID(), s.ResourceGroup(), s.ClusterName()); err != nil { 823 return "", "" 824 } 825 ipName := fmt.Sprintf("%s-%x", s.ClusterName(), h.Sum32()) 826 fqdn := fmt.Sprintf("%s.%s.%s", ipName, s.Location(), s.AzureClients.ResourceManagerVMDNSSuffix) 827 return ipName, fqdn 828 } 829 830 // ListOptionsLabelSelector returns a ListOptions with a label selector for clusterName. 831 func (s *ClusterScope) ListOptionsLabelSelector() client.ListOption { 832 return client.MatchingLabels(map[string]string{ 833 clusterv1.ClusterNameLabel: s.Cluster.Name, 834 }) 835 } 836 837 // PatchObject persists the cluster configuration and status. 838 func (s *ClusterScope) PatchObject(ctx context.Context) error { 839 ctx, _, done := tele.StartSpanWithLogger(ctx, "scope.ClusterScope.PatchObject") 840 defer done() 841 842 conditions.SetSummary(s.AzureCluster) 843 844 return s.patchHelper.Patch( 845 ctx, 846 s.AzureCluster, 847 patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{ 848 clusterv1.ReadyCondition, 849 infrav1.ResourceGroupReadyCondition, 850 infrav1.RouteTablesReadyCondition, 851 infrav1.NetworkInfrastructureReadyCondition, 852 infrav1.VnetPeeringReadyCondition, 853 infrav1.DisksReadyCondition, 854 infrav1.NATGatewaysReadyCondition, 855 infrav1.LoadBalancersReadyCondition, 856 infrav1.BastionHostReadyCondition, 857 infrav1.VNetReadyCondition, 858 infrav1.SubnetsReadyCondition, 859 infrav1.SecurityGroupsReadyCondition, 860 infrav1.PrivateDNSZoneReadyCondition, 861 infrav1.PrivateDNSLinkReadyCondition, 862 infrav1.PrivateDNSRecordReadyCondition, 863 infrav1.PrivateEndpointsReadyCondition, 864 }}) 865 } 866 867 // Close closes the current scope persisting the cluster configuration and status. 868 func (s *ClusterScope) Close(ctx context.Context) error { 869 return s.PatchObject(ctx) 870 } 871 872 // AdditionalTags returns AdditionalTags from the scope's AzureCluster. 873 func (s *ClusterScope) AdditionalTags() infrav1.Tags { 874 tags := make(infrav1.Tags) 875 if s.AzureCluster.Spec.AdditionalTags != nil { 876 tags = s.AzureCluster.Spec.AdditionalTags.DeepCopy() 877 } 878 return tags 879 } 880 881 // APIServerPort returns the APIServerPort to use when creating the load balancer. 882 func (s *ClusterScope) APIServerPort() int32 { 883 if s.Cluster.Spec.ClusterNetwork != nil && s.Cluster.Spec.ClusterNetwork.APIServerPort != nil { 884 return *s.Cluster.Spec.ClusterNetwork.APIServerPort 885 } 886 return 6443 887 } 888 889 // APIServerHost returns the hostname used to reach the API server. 890 func (s *ClusterScope) APIServerHost() string { 891 if s.IsAPIServerPrivate() { 892 return azure.GeneratePrivateFQDN(s.GetPrivateDNSZoneName()) 893 } 894 return s.APIServerPublicIP().DNSName 895 } 896 897 // SetFailureDomain sets a failure domain in a cluster's status by its id. 898 // The provided failure domain spec may be overridden to false by cluster's spec property. 899 func (s *ClusterScope) SetFailureDomain(id string, spec clusterv1.FailureDomainSpec) { 900 if s.AzureCluster.Status.FailureDomains == nil { 901 s.AzureCluster.Status.FailureDomains = make(clusterv1.FailureDomains) 902 } 903 904 if fd, ok := s.AzureCluster.Spec.FailureDomains[id]; ok && !fd.ControlPlane { 905 spec.ControlPlane = false 906 } 907 908 s.AzureCluster.Status.FailureDomains[id] = spec 909 } 910 911 // FailureDomains returns the failure domains for the cluster. 912 func (s *ClusterScope) FailureDomains() []*string { 913 fds := make([]*string, len(s.AzureCluster.Status.FailureDomains)) 914 i := 0 915 for id := range s.AzureCluster.Status.FailureDomains { 916 fds[i] = ptr.To(id) 917 i++ 918 } 919 920 // sort in increasing order restoring the original sort.Strings(fds) behavior 921 sort.Slice(fds, func(i, j int) bool { 922 return *fds[i] < *fds[j] 923 }) 924 925 return fds 926 } 927 928 // SetControlPlaneSecurityRules sets the default security rules of the control plane subnet. 929 // Note that this is not done in a webhook as it requires a valid Cluster object to exist to get the API Server port. 930 func (s *ClusterScope) SetControlPlaneSecurityRules() { 931 if s.ControlPlaneSubnet().SecurityGroup.SecurityRules == nil { 932 subnet := s.ControlPlaneSubnet() 933 subnet.SecurityGroup.SecurityRules = infrav1.SecurityRules{ 934 infrav1.SecurityRule{ 935 Name: "allow_ssh", 936 Description: "Allow SSH", 937 Priority: 2200, 938 Protocol: infrav1.SecurityGroupProtocolTCP, 939 Direction: infrav1.SecurityRuleDirectionInbound, 940 Source: ptr.To("*"), 941 SourcePorts: ptr.To("*"), 942 Destination: ptr.To("*"), 943 DestinationPorts: ptr.To("22"), 944 Action: infrav1.SecurityRuleActionAllow, 945 }, 946 infrav1.SecurityRule{ 947 Name: "allow_apiserver", 948 Description: "Allow K8s API Server", 949 Priority: 2201, 950 Protocol: infrav1.SecurityGroupProtocolTCP, 951 Direction: infrav1.SecurityRuleDirectionInbound, 952 Source: ptr.To("*"), 953 SourcePorts: ptr.To("*"), 954 Destination: ptr.To("*"), 955 DestinationPorts: ptr.To(strconv.Itoa(int(s.APIServerPort()))), 956 Action: infrav1.SecurityRuleActionAllow, 957 }, 958 } 959 s.AzureCluster.Spec.NetworkSpec.UpdateControlPlaneSubnet(subnet) 960 } 961 } 962 963 // SetDNSName sets the API Server public IP DNS name. 964 // Note: this logic exists only for purposes of ensuring backwards compatibility for old clusters created without an APIServerLB, and should be removed in the future. 965 func (s *ClusterScope) SetDNSName() { 966 // for back compat, set the old API Server defaults if no API Server Spec has been set by new webhooks. 967 lb := s.APIServerLB() 968 if lb == nil || lb.Name == "" { 969 lbName := fmt.Sprintf("%s-%s", s.ClusterName(), "public-lb") 970 ip, dns := s.GenerateLegacyFQDN() 971 lb = &infrav1.LoadBalancerSpec{ 972 Name: lbName, 973 FrontendIPs: []infrav1.FrontendIP{ 974 { 975 Name: azure.GenerateFrontendIPConfigName(lbName), 976 PublicIP: &infrav1.PublicIPSpec{ 977 Name: ip, 978 DNSName: dns, 979 }, 980 }, 981 }, 982 LoadBalancerClassSpec: infrav1.LoadBalancerClassSpec{ 983 SKU: infrav1.SKUStandard, 984 Type: infrav1.Public, 985 }, 986 } 987 lb.DeepCopyInto(s.APIServerLB()) 988 } 989 // Generate valid FQDN if not set. 990 // Note: this function uses the AzureCluster subscription ID. 991 if !s.IsAPIServerPrivate() && s.APIServerPublicIP().DNSName == "" { 992 s.APIServerPublicIP().DNSName = s.GenerateFQDN(s.APIServerPublicIP().Name) 993 } 994 } 995 996 // SetLongRunningOperationState will set the future on the AzureCluster status to allow the resource to continue 997 // in the next reconciliation. 998 func (s *ClusterScope) SetLongRunningOperationState(future *infrav1.Future) { 999 futures.Set(s.AzureCluster, future) 1000 } 1001 1002 // GetLongRunningOperationState will get the future on the AzureCluster status. 1003 func (s *ClusterScope) GetLongRunningOperationState(name, service, futureType string) *infrav1.Future { 1004 return futures.Get(s.AzureCluster, name, service, futureType) 1005 } 1006 1007 // DeleteLongRunningOperationState will delete the future from the AzureCluster status. 1008 func (s *ClusterScope) DeleteLongRunningOperationState(name, service, futureType string) { 1009 futures.Delete(s.AzureCluster, name, service, futureType) 1010 } 1011 1012 // UpdateDeleteStatus updates a condition on the AzureCluster status after a DELETE operation. 1013 func (s *ClusterScope) UpdateDeleteStatus(condition clusterv1.ConditionType, service string, err error) { 1014 switch { 1015 case err == nil: 1016 conditions.MarkFalse(s.AzureCluster, condition, infrav1.DeletedReason, clusterv1.ConditionSeverityInfo, "%s successfully deleted", service) 1017 case azure.IsOperationNotDoneError(err): 1018 conditions.MarkFalse(s.AzureCluster, condition, infrav1.DeletingReason, clusterv1.ConditionSeverityInfo, "%s deleting", service) 1019 default: 1020 conditions.MarkFalse(s.AzureCluster, condition, infrav1.DeletionFailedReason, clusterv1.ConditionSeverityError, "%s failed to delete. err: %s", service, err.Error()) 1021 } 1022 } 1023 1024 // UpdatePutStatus updates a condition on the AzureCluster status after a PUT operation. 1025 func (s *ClusterScope) UpdatePutStatus(condition clusterv1.ConditionType, service string, err error) { 1026 switch { 1027 case err == nil: 1028 conditions.MarkTrue(s.AzureCluster, condition) 1029 case azure.IsOperationNotDoneError(err): 1030 conditions.MarkFalse(s.AzureCluster, condition, infrav1.CreatingReason, clusterv1.ConditionSeverityInfo, "%s creating or updating", service) 1031 default: 1032 conditions.MarkFalse(s.AzureCluster, condition, infrav1.FailedReason, clusterv1.ConditionSeverityError, "%s failed to create or update. err: %s", service, err.Error()) 1033 } 1034 } 1035 1036 // UpdatePatchStatus updates a condition on the AzureCluster status after a PATCH operation. 1037 func (s *ClusterScope) UpdatePatchStatus(condition clusterv1.ConditionType, service string, err error) { 1038 switch { 1039 case err == nil: 1040 conditions.MarkTrue(s.AzureCluster, condition) 1041 case azure.IsOperationNotDoneError(err): 1042 conditions.MarkFalse(s.AzureCluster, condition, infrav1.UpdatingReason, clusterv1.ConditionSeverityInfo, "%s updating", service) 1043 default: 1044 conditions.MarkFalse(s.AzureCluster, condition, infrav1.FailedReason, clusterv1.ConditionSeverityError, "%s failed to update. err: %s", service, err.Error()) 1045 } 1046 } 1047 1048 // AnnotationJSON returns a map[string]interface from a JSON annotation. 1049 func (s *ClusterScope) AnnotationJSON(annotation string) (map[string]interface{}, error) { 1050 out := map[string]interface{}{} 1051 jsonAnnotation := s.AzureCluster.GetAnnotations()[annotation] 1052 if jsonAnnotation == "" { 1053 return out, nil 1054 } 1055 err := json.Unmarshal([]byte(jsonAnnotation), &out) 1056 if err != nil { 1057 return out, err 1058 } 1059 return out, nil 1060 } 1061 1062 // UpdateAnnotationJSON updates the `annotation` with 1063 // `content`. `content` in this case should be a `map[string]interface{}` 1064 // suitable for turning into JSON. This `content` map will be marshalled into a 1065 // JSON string before being set as the given `annotation`. 1066 func (s *ClusterScope) UpdateAnnotationJSON(annotation string, content map[string]interface{}) error { 1067 b, err := json.Marshal(content) 1068 if err != nil { 1069 return err 1070 } 1071 s.SetAnnotation(annotation, string(b)) 1072 return nil 1073 } 1074 1075 // SetAnnotation sets a key value annotation on the AzureCluster. 1076 func (s *ClusterScope) SetAnnotation(key, value string) { 1077 if s.AzureCluster.Annotations == nil { 1078 s.AzureCluster.Annotations = map[string]string{} 1079 } 1080 s.AzureCluster.Annotations[key] = value 1081 } 1082 1083 // PrivateEndpointSpecs returns the private endpoint specs. 1084 func (s *ClusterScope) PrivateEndpointSpecs() []azure.ASOResourceSpecGetter[*asonetworkv1api20220701.PrivateEndpoint] { 1085 subnetsList := s.AzureCluster.Spec.NetworkSpec.Subnets 1086 numberOfSubnets := len(subnetsList) 1087 if s.IsAzureBastionEnabled() { 1088 subnetsList = append(subnetsList, s.AzureCluster.Spec.BastionSpec.AzureBastion.Subnet) 1089 numberOfSubnets++ 1090 } 1091 1092 // privateEndpointSpecs will be an empty list if no private endpoints were found. 1093 // We pre-allocate the list to avoid unnecessary allocations during append. 1094 privateEndpointSpecs := make([]azure.ASOResourceSpecGetter[*asonetworkv1api20220701.PrivateEndpoint], 0, numberOfSubnets) 1095 1096 for _, subnet := range subnetsList { 1097 for _, privateEndpoint := range subnet.PrivateEndpoints { 1098 privateEndpointSpec := &privateendpoints.PrivateEndpointSpec{ 1099 Name: privateEndpoint.Name, 1100 ResourceGroup: s.ResourceGroup(), 1101 Location: privateEndpoint.Location, 1102 CustomNetworkInterfaceName: privateEndpoint.CustomNetworkInterfaceName, 1103 PrivateIPAddresses: privateEndpoint.PrivateIPAddresses, 1104 SubnetID: subnet.ID, 1105 ApplicationSecurityGroups: privateEndpoint.ApplicationSecurityGroups, 1106 ManualApproval: privateEndpoint.ManualApproval, 1107 ClusterName: s.ClusterName(), 1108 AdditionalTags: s.AdditionalTags(), 1109 } 1110 1111 for _, privateLinkServiceConnection := range privateEndpoint.PrivateLinkServiceConnections { 1112 pl := privateendpoints.PrivateLinkServiceConnection{ 1113 PrivateLinkServiceID: privateLinkServiceConnection.PrivateLinkServiceID, 1114 Name: privateLinkServiceConnection.Name, 1115 RequestMessage: privateLinkServiceConnection.RequestMessage, 1116 GroupIDs: privateLinkServiceConnection.GroupIDs, 1117 } 1118 privateEndpointSpec.PrivateLinkServiceConnections = append(privateEndpointSpec.PrivateLinkServiceConnections, pl) 1119 } 1120 privateEndpointSpecs = append(privateEndpointSpecs, privateEndpointSpec) 1121 } 1122 } 1123 1124 return privateEndpointSpecs 1125 } 1126 1127 func (s *ClusterScope) getLastAppliedSecurityRules(nsgName string) map[string]interface{} { 1128 // Retrieve the last applied security rules for all NSGs. 1129 lastAppliedSecurityRulesAll, err := s.AnnotationJSON(azure.SecurityRuleLastAppliedAnnotation) 1130 if err != nil { 1131 return map[string]interface{}{} 1132 } 1133 1134 // Retrieve the last applied security rules for this NSG. 1135 lastAppliedSecurityRules, ok := lastAppliedSecurityRulesAll[nsgName].(map[string]interface{}) 1136 if !ok { 1137 lastAppliedSecurityRules = map[string]interface{}{} 1138 } 1139 return lastAppliedSecurityRules 1140 }