sigs.k8s.io/cluster-api-provider-azure@v1.17.0/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 := NewAzureCredentialsProvider(ctx, params.Client, params.AzureCluster.Spec.IdentityRef, params.AzureCluster.Namespace) 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.Vnet().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 ctx := context.Background() 586 ctx, log, done := tele.StartSpanWithLogger(ctx, "scope.ClusterScope.IsVnetManaged") 587 defer done() 588 589 vnet := s.VNetSpec().ResourceRef() 590 vnet.SetNamespace(s.ASOOwner().GetNamespace()) 591 err := s.Client.Get(ctx, client.ObjectKeyFromObject(vnet), vnet) 592 if err != nil { 593 log.Error(err, "Unable to determine if ClusterScope VNET is managed by capz, assuming unmanaged", "AzureCluster", s.ClusterName()) 594 return false 595 } 596 597 isManaged := infrav1.Tags(vnet.Status.Tags).HasOwned(s.ClusterName()) 598 s.cache.isVnetManaged = ptr.To(isManaged) 599 return isManaged 600 } 601 602 // IsIPv6Enabled returns true if IPv6 is enabled. 603 func (s *ClusterScope) IsIPv6Enabled() bool { 604 for _, cidr := range s.AzureCluster.Spec.NetworkSpec.Vnet.CIDRBlocks { 605 if net.IsIPv6CIDRString(cidr) { 606 return true 607 } 608 } 609 return false 610 } 611 612 // Subnets returns the cluster subnets. 613 func (s *ClusterScope) Subnets() infrav1.Subnets { 614 return s.AzureCluster.Spec.NetworkSpec.Subnets 615 } 616 617 // ControlPlaneSubnet returns the cluster control plane subnet. 618 func (s *ClusterScope) ControlPlaneSubnet() infrav1.SubnetSpec { 619 subnet, _ := s.AzureCluster.Spec.NetworkSpec.GetControlPlaneSubnet() 620 return subnet 621 } 622 623 // NodeSubnets returns the subnets with the node role. 624 func (s *ClusterScope) NodeSubnets() []infrav1.SubnetSpec { 625 subnets := []infrav1.SubnetSpec{} 626 for _, subnet := range s.AzureCluster.Spec.NetworkSpec.Subnets { 627 if subnet.Role == infrav1.SubnetNode || subnet.Role == infrav1.SubnetCluster { 628 subnets = append(subnets, subnet) 629 } 630 } 631 632 return subnets 633 } 634 635 // Subnet returns the subnet with the provided name. 636 func (s *ClusterScope) Subnet(name string) infrav1.SubnetSpec { 637 for _, sn := range s.AzureCluster.Spec.NetworkSpec.Subnets { 638 if sn.Name == name { 639 return sn 640 } 641 } 642 643 return infrav1.SubnetSpec{} 644 } 645 646 // SetSubnet sets the subnet spec for the subnet with the same name. 647 func (s *ClusterScope) SetSubnet(subnetSpec infrav1.SubnetSpec) { 648 for i, sn := range s.AzureCluster.Spec.NetworkSpec.Subnets { 649 if sn.Name == subnetSpec.Name { 650 s.AzureCluster.Spec.NetworkSpec.Subnets[i] = subnetSpec 651 return 652 } 653 } 654 } 655 656 // SetNatGatewayIDInSubnets sets the NAT Gateway ID in the subnets with the same name. 657 func (s *ClusterScope) SetNatGatewayIDInSubnets(name string, id string) { 658 for _, subnet := range s.Subnets() { 659 if subnet.NatGateway.Name == name { 660 subnet.NatGateway.ID = id 661 s.SetSubnet(subnet) 662 } 663 } 664 } 665 666 // UpdateSubnetCIDRs updates the subnet CIDRs for the subnet with the same name. 667 func (s *ClusterScope) UpdateSubnetCIDRs(name string, cidrBlocks []string) { 668 subnetSpecInfra := s.Subnet(name) 669 subnetSpecInfra.CIDRBlocks = cidrBlocks 670 s.SetSubnet(subnetSpecInfra) 671 } 672 673 // UpdateSubnetID updates the subnet ID for the subnet with the same name. 674 func (s *ClusterScope) UpdateSubnetID(name string, id string) { 675 subnetSpecInfra := s.Subnet(name) 676 subnetSpecInfra.ID = id 677 s.SetSubnet(subnetSpecInfra) 678 } 679 680 // ControlPlaneRouteTable returns the cluster controlplane routetable. 681 func (s *ClusterScope) ControlPlaneRouteTable() infrav1.RouteTable { 682 subnet, _ := s.AzureCluster.Spec.NetworkSpec.GetControlPlaneSubnet() 683 return subnet.RouteTable 684 } 685 686 // APIServerLB returns the cluster API Server load balancer. 687 func (s *ClusterScope) APIServerLB() *infrav1.LoadBalancerSpec { 688 return &s.AzureCluster.Spec.NetworkSpec.APIServerLB 689 } 690 691 // NodeOutboundLB returns the cluster node outbound load balancer. 692 func (s *ClusterScope) NodeOutboundLB() *infrav1.LoadBalancerSpec { 693 return s.AzureCluster.Spec.NetworkSpec.NodeOutboundLB 694 } 695 696 // ControlPlaneOutboundLB returns the cluster control plane outbound load balancer. 697 func (s *ClusterScope) ControlPlaneOutboundLB() *infrav1.LoadBalancerSpec { 698 return s.AzureCluster.Spec.NetworkSpec.ControlPlaneOutboundLB 699 } 700 701 // APIServerLBName returns the API Server LB name. 702 func (s *ClusterScope) APIServerLBName() string { 703 return s.APIServerLB().Name 704 } 705 706 // IsAPIServerPrivate returns true if the API Server LB is of type Internal. 707 func (s *ClusterScope) IsAPIServerPrivate() bool { 708 return s.APIServerLB().Type == infrav1.Internal 709 } 710 711 // APIServerPublicIP returns the API Server public IP. 712 func (s *ClusterScope) APIServerPublicIP() *infrav1.PublicIPSpec { 713 return s.APIServerLB().FrontendIPs[0].PublicIP 714 } 715 716 // APIServerPrivateIP returns the API Server private IP. 717 func (s *ClusterScope) APIServerPrivateIP() string { 718 return s.APIServerLB().FrontendIPs[0].PrivateIPAddress 719 } 720 721 // GetPrivateDNSZoneName returns the Private DNS Zone from the spec or generate it from cluster name. 722 func (s *ClusterScope) GetPrivateDNSZoneName() string { 723 if len(s.AzureCluster.Spec.NetworkSpec.PrivateDNSZoneName) > 0 { 724 return s.AzureCluster.Spec.NetworkSpec.PrivateDNSZoneName 725 } 726 return azure.GeneratePrivateDNSZoneName(s.ClusterName()) 727 } 728 729 // APIServerLBPoolName returns the API Server LB backend pool name. 730 func (s *ClusterScope) APIServerLBPoolName() string { 731 return s.APIServerLB().BackendPool.Name 732 } 733 734 // OutboundLB returns the outbound LB. 735 func (s *ClusterScope) outboundLB(role string) *infrav1.LoadBalancerSpec { 736 if role == infrav1.Node { 737 return s.NodeOutboundLB() 738 } 739 if s.IsAPIServerPrivate() { 740 return s.ControlPlaneOutboundLB() 741 } 742 return s.APIServerLB() 743 } 744 745 // OutboundLBName returns the name of the outbound LB. 746 func (s *ClusterScope) OutboundLBName(role string) string { 747 lb := s.outboundLB(role) 748 if lb == nil { 749 return "" 750 } 751 return lb.Name 752 } 753 754 // OutboundPoolName returns the outbound LB backend pool name. 755 func (s *ClusterScope) OutboundPoolName(role string) string { 756 lb := s.outboundLB(role) 757 if lb == nil { 758 return "" 759 } 760 return lb.BackendPool.Name 761 } 762 763 // ResourceGroup returns the cluster resource group. 764 func (s *ClusterScope) ResourceGroup() string { 765 return s.AzureCluster.Spec.ResourceGroup 766 } 767 768 // NodeResourceGroup returns the resource group where nodes live. 769 // For AzureClusters this is the same as the cluster RG. 770 func (s *ClusterScope) NodeResourceGroup() string { 771 return s.ResourceGroup() 772 } 773 774 // ClusterName returns the cluster name. 775 func (s *ClusterScope) ClusterName() string { 776 return s.Cluster.Name 777 } 778 779 // Namespace returns the cluster namespace. 780 func (s *ClusterScope) Namespace() string { 781 return s.Cluster.Namespace 782 } 783 784 // Location returns the cluster location. 785 func (s *ClusterScope) Location() string { 786 return s.AzureCluster.Spec.Location 787 } 788 789 // AvailabilitySetEnabled informs machines that they should be part of an Availability Set. 790 func (s *ClusterScope) AvailabilitySetEnabled() bool { 791 return len(s.AzureCluster.Status.FailureDomains) == 0 792 } 793 794 // CloudProviderConfigOverrides returns the cloud provider config overrides for the cluster. 795 func (s *ClusterScope) CloudProviderConfigOverrides() *infrav1.CloudProviderConfigOverrides { 796 return s.AzureCluster.Spec.CloudProviderConfigOverrides 797 } 798 799 // ExtendedLocationName returns ExtendedLocation name for the cluster. 800 func (s *ClusterScope) ExtendedLocationName() string { 801 if s.ExtendedLocation() == nil { 802 return "" 803 } 804 return s.ExtendedLocation().Name 805 } 806 807 // ExtendedLocationType returns ExtendedLocation type for the cluster. 808 func (s *ClusterScope) ExtendedLocationType() string { 809 if s.ExtendedLocation() == nil { 810 return "" 811 } 812 return s.ExtendedLocation().Type 813 } 814 815 // ExtendedLocation returns the cluster extendedLocation. 816 func (s *ClusterScope) ExtendedLocation() *infrav1.ExtendedLocationSpec { 817 return s.AzureCluster.Spec.ExtendedLocation 818 } 819 820 // GenerateFQDN generates a fully qualified domain name, based on a hash, cluster name and cluster location. 821 func (s *ClusterScope) GenerateFQDN(ipName string) string { 822 h := fnv.New32a() 823 if _, err := fmt.Fprintf(h, "%s/%s/%s", s.SubscriptionID(), s.ResourceGroup(), ipName); err != nil { 824 return "" 825 } 826 hash := fmt.Sprintf("%x", h.Sum32()) 827 return strings.ToLower(fmt.Sprintf("%s-%s.%s.%s", s.ClusterName(), hash, s.Location(), s.AzureClients.ResourceManagerVMDNSSuffix)) 828 } 829 830 // GenerateLegacyFQDN generates an IP name and a fully qualified domain name, based on a hash, cluster name and cluster location. 831 // Deprecated: use GenerateFQDN instead. 832 func (s *ClusterScope) GenerateLegacyFQDN() (ip string, domain string) { 833 h := fnv.New32a() 834 if _, err := fmt.Fprintf(h, "%s/%s/%s", s.SubscriptionID(), s.ResourceGroup(), s.ClusterName()); err != nil { 835 return "", "" 836 } 837 ipName := fmt.Sprintf("%s-%x", s.ClusterName(), h.Sum32()) 838 fqdn := fmt.Sprintf("%s.%s.%s", ipName, s.Location(), s.AzureClients.ResourceManagerVMDNSSuffix) 839 return ipName, fqdn 840 } 841 842 // ListOptionsLabelSelector returns a ListOptions with a label selector for clusterName. 843 func (s *ClusterScope) ListOptionsLabelSelector() client.ListOption { 844 return client.MatchingLabels(map[string]string{ 845 clusterv1.ClusterNameLabel: s.Cluster.Name, 846 }) 847 } 848 849 // PatchObject persists the cluster configuration and status. 850 func (s *ClusterScope) PatchObject(ctx context.Context) error { 851 ctx, _, done := tele.StartSpanWithLogger(ctx, "scope.ClusterScope.PatchObject") 852 defer done() 853 854 conditions.SetSummary(s.AzureCluster) 855 856 return s.patchHelper.Patch( 857 ctx, 858 s.AzureCluster, 859 patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{ 860 clusterv1.ReadyCondition, 861 infrav1.ResourceGroupReadyCondition, 862 infrav1.RouteTablesReadyCondition, 863 infrav1.NetworkInfrastructureReadyCondition, 864 infrav1.VnetPeeringReadyCondition, 865 infrav1.DisksReadyCondition, 866 infrav1.NATGatewaysReadyCondition, 867 infrav1.LoadBalancersReadyCondition, 868 infrav1.BastionHostReadyCondition, 869 infrav1.VNetReadyCondition, 870 infrav1.SubnetsReadyCondition, 871 infrav1.SecurityGroupsReadyCondition, 872 infrav1.PrivateDNSZoneReadyCondition, 873 infrav1.PrivateDNSLinkReadyCondition, 874 infrav1.PrivateDNSRecordReadyCondition, 875 infrav1.PrivateEndpointsReadyCondition, 876 }}) 877 } 878 879 // Close closes the current scope persisting the cluster configuration and status. 880 func (s *ClusterScope) Close(ctx context.Context) error { 881 return s.PatchObject(ctx) 882 } 883 884 // AdditionalTags returns AdditionalTags from the scope's AzureCluster. 885 func (s *ClusterScope) AdditionalTags() infrav1.Tags { 886 tags := make(infrav1.Tags) 887 if s.AzureCluster.Spec.AdditionalTags != nil { 888 tags = s.AzureCluster.Spec.AdditionalTags.DeepCopy() 889 } 890 return tags 891 } 892 893 // APIServerPort returns the APIServerPort to use when creating the load balancer. 894 func (s *ClusterScope) APIServerPort() int32 { 895 if s.Cluster.Spec.ClusterNetwork != nil && s.Cluster.Spec.ClusterNetwork.APIServerPort != nil { 896 return *s.Cluster.Spec.ClusterNetwork.APIServerPort 897 } 898 return 6443 899 } 900 901 // APIServerHost returns the hostname used to reach the API server. 902 func (s *ClusterScope) APIServerHost() string { 903 if s.IsAPIServerPrivate() { 904 return azure.GeneratePrivateFQDN(s.GetPrivateDNSZoneName()) 905 } 906 return s.APIServerPublicIP().DNSName 907 } 908 909 // SetFailureDomain sets a failure domain in a cluster's status by its id. 910 // The provided failure domain spec may be overridden to false by cluster's spec property. 911 func (s *ClusterScope) SetFailureDomain(id string, spec clusterv1.FailureDomainSpec) { 912 if s.AzureCluster.Status.FailureDomains == nil { 913 s.AzureCluster.Status.FailureDomains = make(clusterv1.FailureDomains) 914 } 915 916 if fd, ok := s.AzureCluster.Spec.FailureDomains[id]; ok && !fd.ControlPlane { 917 spec.ControlPlane = false 918 } 919 920 s.AzureCluster.Status.FailureDomains[id] = spec 921 } 922 923 // FailureDomains returns the failure domains for the cluster. 924 func (s *ClusterScope) FailureDomains() []*string { 925 fds := make([]*string, len(s.AzureCluster.Status.FailureDomains)) 926 i := 0 927 for id := range s.AzureCluster.Status.FailureDomains { 928 fds[i] = ptr.To(id) 929 i++ 930 } 931 932 // sort in increasing order restoring the original sort.Strings(fds) behavior 933 sort.Slice(fds, func(i, j int) bool { 934 return *fds[i] < *fds[j] 935 }) 936 937 return fds 938 } 939 940 // SetControlPlaneSecurityRules sets the default security rules of the control plane subnet. 941 // Note that this is not done in a webhook as it requires a valid Cluster object to exist to get the API Server port. 942 func (s *ClusterScope) SetControlPlaneSecurityRules() { 943 if s.ControlPlaneSubnet().SecurityGroup.SecurityRules == nil { 944 subnet := s.ControlPlaneSubnet() 945 subnet.SecurityGroup.SecurityRules = infrav1.SecurityRules{ 946 infrav1.SecurityRule{ 947 Name: "allow_ssh", 948 Description: "Allow SSH", 949 Priority: 2200, 950 Protocol: infrav1.SecurityGroupProtocolTCP, 951 Direction: infrav1.SecurityRuleDirectionInbound, 952 Source: ptr.To("*"), 953 SourcePorts: ptr.To("*"), 954 Destination: ptr.To("*"), 955 DestinationPorts: ptr.To("22"), 956 Action: infrav1.SecurityRuleActionAllow, 957 }, 958 infrav1.SecurityRule{ 959 Name: "allow_apiserver", 960 Description: "Allow K8s API Server", 961 Priority: 2201, 962 Protocol: infrav1.SecurityGroupProtocolTCP, 963 Direction: infrav1.SecurityRuleDirectionInbound, 964 Source: ptr.To("*"), 965 SourcePorts: ptr.To("*"), 966 Destination: ptr.To("*"), 967 DestinationPorts: ptr.To(strconv.Itoa(int(s.APIServerPort()))), 968 Action: infrav1.SecurityRuleActionAllow, 969 }, 970 } 971 s.AzureCluster.Spec.NetworkSpec.UpdateControlPlaneSubnet(subnet) 972 } 973 } 974 975 // SetDNSName sets the API Server public IP DNS name. 976 // 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. 977 func (s *ClusterScope) SetDNSName() { 978 // for back compat, set the old API Server defaults if no API Server Spec has been set by new webhooks. 979 lb := s.APIServerLB() 980 if lb == nil || lb.Name == "" { 981 lbName := fmt.Sprintf("%s-%s", s.ClusterName(), "public-lb") 982 ip, dns := s.GenerateLegacyFQDN() 983 lb = &infrav1.LoadBalancerSpec{ 984 Name: lbName, 985 FrontendIPs: []infrav1.FrontendIP{ 986 { 987 Name: azure.GenerateFrontendIPConfigName(lbName), 988 PublicIP: &infrav1.PublicIPSpec{ 989 Name: ip, 990 DNSName: dns, 991 }, 992 }, 993 }, 994 LoadBalancerClassSpec: infrav1.LoadBalancerClassSpec{ 995 SKU: infrav1.SKUStandard, 996 Type: infrav1.Public, 997 }, 998 } 999 lb.DeepCopyInto(s.APIServerLB()) 1000 } 1001 // Generate valid FQDN if not set. 1002 // Note: this function uses the AzureCluster subscription ID. 1003 if !s.IsAPIServerPrivate() && s.APIServerPublicIP().DNSName == "" { 1004 s.APIServerPublicIP().DNSName = s.GenerateFQDN(s.APIServerPublicIP().Name) 1005 } 1006 } 1007 1008 // SetLongRunningOperationState will set the future on the AzureCluster status to allow the resource to continue 1009 // in the next reconciliation. 1010 func (s *ClusterScope) SetLongRunningOperationState(future *infrav1.Future) { 1011 futures.Set(s.AzureCluster, future) 1012 } 1013 1014 // GetLongRunningOperationState will get the future on the AzureCluster status. 1015 func (s *ClusterScope) GetLongRunningOperationState(name, service, futureType string) *infrav1.Future { 1016 return futures.Get(s.AzureCluster, name, service, futureType) 1017 } 1018 1019 // DeleteLongRunningOperationState will delete the future from the AzureCluster status. 1020 func (s *ClusterScope) DeleteLongRunningOperationState(name, service, futureType string) { 1021 futures.Delete(s.AzureCluster, name, service, futureType) 1022 } 1023 1024 // UpdateDeleteStatus updates a condition on the AzureCluster status after a DELETE operation. 1025 func (s *ClusterScope) UpdateDeleteStatus(condition clusterv1.ConditionType, service string, err error) { 1026 switch { 1027 case err == nil: 1028 conditions.MarkFalse(s.AzureCluster, condition, infrav1.DeletedReason, clusterv1.ConditionSeverityInfo, "%s successfully deleted", service) 1029 case azure.IsOperationNotDoneError(err): 1030 conditions.MarkFalse(s.AzureCluster, condition, infrav1.DeletingReason, clusterv1.ConditionSeverityInfo, "%s deleting", service) 1031 default: 1032 conditions.MarkFalse(s.AzureCluster, condition, infrav1.DeletionFailedReason, clusterv1.ConditionSeverityError, "%s failed to delete. err: %s", service, err.Error()) 1033 } 1034 } 1035 1036 // UpdatePutStatus updates a condition on the AzureCluster status after a PUT operation. 1037 func (s *ClusterScope) UpdatePutStatus(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.CreatingReason, clusterv1.ConditionSeverityInfo, "%s creating or updating", service) 1043 default: 1044 conditions.MarkFalse(s.AzureCluster, condition, infrav1.FailedReason, clusterv1.ConditionSeverityError, "%s failed to create or update. err: %s", service, err.Error()) 1045 } 1046 } 1047 1048 // UpdatePatchStatus updates a condition on the AzureCluster status after a PATCH operation. 1049 func (s *ClusterScope) UpdatePatchStatus(condition clusterv1.ConditionType, service string, err error) { 1050 switch { 1051 case err == nil: 1052 conditions.MarkTrue(s.AzureCluster, condition) 1053 case azure.IsOperationNotDoneError(err): 1054 conditions.MarkFalse(s.AzureCluster, condition, infrav1.UpdatingReason, clusterv1.ConditionSeverityInfo, "%s updating", service) 1055 default: 1056 conditions.MarkFalse(s.AzureCluster, condition, infrav1.FailedReason, clusterv1.ConditionSeverityError, "%s failed to update. err: %s", service, err.Error()) 1057 } 1058 } 1059 1060 // AnnotationJSON returns a map[string]interface from a JSON annotation. 1061 func (s *ClusterScope) AnnotationJSON(annotation string) (map[string]interface{}, error) { 1062 out := map[string]interface{}{} 1063 jsonAnnotation := s.AzureCluster.GetAnnotations()[annotation] 1064 if jsonAnnotation == "" { 1065 return out, nil 1066 } 1067 err := json.Unmarshal([]byte(jsonAnnotation), &out) 1068 if err != nil { 1069 return out, err 1070 } 1071 return out, nil 1072 } 1073 1074 // UpdateAnnotationJSON updates the `annotation` with 1075 // `content`. `content` in this case should be a `map[string]interface{}` 1076 // suitable for turning into JSON. This `content` map will be marshalled into a 1077 // JSON string before being set as the given `annotation`. 1078 func (s *ClusterScope) UpdateAnnotationJSON(annotation string, content map[string]interface{}) error { 1079 b, err := json.Marshal(content) 1080 if err != nil { 1081 return err 1082 } 1083 s.SetAnnotation(annotation, string(b)) 1084 return nil 1085 } 1086 1087 // SetAnnotation sets a key value annotation on the AzureCluster. 1088 func (s *ClusterScope) SetAnnotation(key, value string) { 1089 if s.AzureCluster.Annotations == nil { 1090 s.AzureCluster.Annotations = map[string]string{} 1091 } 1092 s.AzureCluster.Annotations[key] = value 1093 } 1094 1095 // PrivateEndpointSpecs returns the private endpoint specs. 1096 func (s *ClusterScope) PrivateEndpointSpecs() []azure.ASOResourceSpecGetter[*asonetworkv1api20220701.PrivateEndpoint] { 1097 subnetsList := s.AzureCluster.Spec.NetworkSpec.Subnets 1098 numberOfSubnets := len(subnetsList) 1099 if s.IsAzureBastionEnabled() { 1100 subnetsList = append(subnetsList, s.AzureCluster.Spec.BastionSpec.AzureBastion.Subnet) 1101 numberOfSubnets++ 1102 } 1103 1104 // privateEndpointSpecs will be an empty list if no private endpoints were found. 1105 // We pre-allocate the list to avoid unnecessary allocations during append. 1106 privateEndpointSpecs := make([]azure.ASOResourceSpecGetter[*asonetworkv1api20220701.PrivateEndpoint], 0, numberOfSubnets) 1107 1108 for _, subnet := range subnetsList { 1109 for _, privateEndpoint := range subnet.PrivateEndpoints { 1110 privateEndpointSpec := &privateendpoints.PrivateEndpointSpec{ 1111 Name: privateEndpoint.Name, 1112 ResourceGroup: s.ResourceGroup(), 1113 Location: privateEndpoint.Location, 1114 CustomNetworkInterfaceName: privateEndpoint.CustomNetworkInterfaceName, 1115 PrivateIPAddresses: privateEndpoint.PrivateIPAddresses, 1116 SubnetID: subnet.ID, 1117 ApplicationSecurityGroups: privateEndpoint.ApplicationSecurityGroups, 1118 ManualApproval: privateEndpoint.ManualApproval, 1119 ClusterName: s.ClusterName(), 1120 AdditionalTags: s.AdditionalTags(), 1121 } 1122 1123 for _, privateLinkServiceConnection := range privateEndpoint.PrivateLinkServiceConnections { 1124 pl := privateendpoints.PrivateLinkServiceConnection{ 1125 PrivateLinkServiceID: privateLinkServiceConnection.PrivateLinkServiceID, 1126 Name: privateLinkServiceConnection.Name, 1127 RequestMessage: privateLinkServiceConnection.RequestMessage, 1128 GroupIDs: privateLinkServiceConnection.GroupIDs, 1129 } 1130 privateEndpointSpec.PrivateLinkServiceConnections = append(privateEndpointSpec.PrivateLinkServiceConnections, pl) 1131 } 1132 privateEndpointSpecs = append(privateEndpointSpecs, privateEndpointSpec) 1133 } 1134 } 1135 1136 return privateEndpointSpecs 1137 } 1138 1139 func (s *ClusterScope) getLastAppliedSecurityRules(nsgName string) map[string]interface{} { 1140 // Retrieve the last applied security rules for all NSGs. 1141 lastAppliedSecurityRulesAll, err := s.AnnotationJSON(azure.SecurityRuleLastAppliedAnnotation) 1142 if err != nil { 1143 return map[string]interface{}{} 1144 } 1145 1146 // Retrieve the last applied security rules for this NSG. 1147 lastAppliedSecurityRules, ok := lastAppliedSecurityRulesAll[nsgName].(map[string]interface{}) 1148 if !ok { 1149 lastAppliedSecurityRules = map[string]interface{}{} 1150 } 1151 return lastAppliedSecurityRules 1152 }