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

     1  /*
     2  Copyright 2022 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 managedclusters
    18  
    19  import (
    20  	"context"
    21  	"encoding/base64"
    22  	"fmt"
    23  	"net"
    24  
    25  	asocontainerservicev1preview "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20230202preview"
    26  	asocontainerservicev1 "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231001"
    27  	asocontainerservicev1hub "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231001/storage"
    28  	"github.com/Azure/azure-service-operator/v2/pkg/genruntime"
    29  	"github.com/pkg/errors"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/utils/ptr"
    33  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    34  	"sigs.k8s.io/cluster-api-provider-azure/azure"
    35  	"sigs.k8s.io/cluster-api-provider-azure/azure/converters"
    36  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/agentpools"
    37  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/aso"
    38  	"sigs.k8s.io/cluster-api-provider-azure/util/tele"
    39  	"sigs.k8s.io/cluster-api-provider-azure/util/versions"
    40  	"sigs.k8s.io/cluster-api/util/secret"
    41  )
    42  
    43  // ManagedClusterSpec contains properties to create a managed cluster.
    44  type ManagedClusterSpec struct {
    45  	// Name is the name of this AKS Cluster.
    46  	Name string
    47  
    48  	// ResourceGroup is the name of the Azure resource group for this AKS Cluster.
    49  	ResourceGroup string
    50  
    51  	// NodeResourceGroup is the name of the Azure resource group containing IaaS VMs.
    52  	NodeResourceGroup string
    53  
    54  	// ClusterName is the name of the owning Cluster API Cluster resource.
    55  	ClusterName string
    56  
    57  	// VnetSubnetID is the Azure Resource ID for the subnet which should contain nodes.
    58  	VnetSubnetID string
    59  
    60  	// Location is a string matching one of the canonical Azure region names. Examples: "westus2", "eastus".
    61  	Location string
    62  
    63  	// Tags is a set of tags to add to this cluster.
    64  	Tags map[string]string
    65  
    66  	// Version defines the desired Kubernetes version.
    67  	Version string
    68  
    69  	// LoadBalancerSKU for the managed cluster. Possible values include: 'Standard', 'Basic'. Defaults to Standard.
    70  	LoadBalancerSKU string
    71  
    72  	// NetworkPlugin used for building Kubernetes network. Possible values include: 'azure', 'kubenet'. Defaults to azure.
    73  	NetworkPlugin string
    74  
    75  	// NetworkPluginMode is the mode the network plugin should use.
    76  	NetworkPluginMode *infrav1.NetworkPluginMode
    77  
    78  	// NetworkPolicy used for building Kubernetes network. Possible values include: 'azure', 'calico', 'cilium'.
    79  	NetworkPolicy string
    80  
    81  	// NetworkDataplane used for building Kubernetes network. Possible values include: 'azure', 'cilium'.
    82  	NetworkDataplane *infrav1.NetworkDataplaneType
    83  
    84  	// OutboundType used for building Kubernetes network. Possible values include: 'loadBalancer', 'managedNATGateway', 'userAssignedNATGateway', 'userDefinedRouting'.
    85  	OutboundType *infrav1.ManagedControlPlaneOutboundType
    86  
    87  	// SSHPublicKey is a string literal containing an ssh public key. Will autogenerate and discard if not provided.
    88  	SSHPublicKey string
    89  
    90  	// GetAllAgentPools is a function that returns the list of agent pool specifications in this cluster.
    91  	GetAllAgentPools func() ([]azure.ASOResourceSpecGetter[genruntime.MetaObject], error)
    92  
    93  	// PodCIDR is the CIDR block for IP addresses distributed to pods
    94  	PodCIDR string
    95  
    96  	// ServiceCIDR is the CIDR block for IP addresses distributed to services
    97  	ServiceCIDR string
    98  
    99  	// DNSServiceIP is an IP address assigned to the Kubernetes DNS service
   100  	DNSServiceIP *string
   101  
   102  	// AddonProfiles are the profiles of managed cluster add-on.
   103  	AddonProfiles []AddonProfile
   104  
   105  	// AADProfile is Azure Active Directory configuration to integrate with AKS, for aad authentication.
   106  	AADProfile *AADProfile
   107  
   108  	// SKU is the SKU of the AKS to be provisioned.
   109  	SKU *SKU
   110  
   111  	// LoadBalancerProfile is the profile of the cluster load balancer.
   112  	LoadBalancerProfile *LoadBalancerProfile
   113  
   114  	// APIServerAccessProfile is the access profile for AKS API server.
   115  	APIServerAccessProfile *APIServerAccessProfile
   116  
   117  	// AutoScalerProfile is the parameters to be applied to the cluster-autoscaler when enabled.
   118  	AutoScalerProfile *AutoScalerProfile
   119  
   120  	// Identity is the AKS control plane Identity configuration
   121  	Identity *infrav1.Identity
   122  
   123  	// KubeletUserAssignedIdentity is the user-assigned identity for kubelet to authenticate to ACR.
   124  	KubeletUserAssignedIdentity string
   125  
   126  	// HTTPProxyConfig is the HTTP proxy configuration for the cluster.
   127  	HTTPProxyConfig *HTTPProxyConfig
   128  
   129  	// OIDCIssuerProfile is the OIDC issuer profile of the Managed Cluster.
   130  	OIDCIssuerProfile *OIDCIssuerProfile
   131  
   132  	// DNSPrefix allows the user to customize dns prefix.
   133  	DNSPrefix *string
   134  
   135  	// DisableLocalAccounts disables getting static credentials for this cluster when set. Expected to only be used for AAD clusters.
   136  	DisableLocalAccounts *bool
   137  
   138  	// AutoUpgradeProfile defines auto upgrade configuration.
   139  	AutoUpgradeProfile *ManagedClusterAutoUpgradeProfile
   140  
   141  	// SecurityProfile defines the security profile for the cluster.
   142  	SecurityProfile *ManagedClusterSecurityProfile
   143  
   144  	// Patches are extra patches to be applied to the ASO resource.
   145  	Patches []string
   146  
   147  	// Preview enables the preview API version.
   148  	Preview bool
   149  }
   150  
   151  // ManagedClusterAutoUpgradeProfile auto upgrade profile for a managed cluster.
   152  type ManagedClusterAutoUpgradeProfile struct {
   153  	// UpgradeChannel defines the channel for auto upgrade configuration.
   154  	UpgradeChannel *infrav1.UpgradeChannel
   155  }
   156  
   157  // HTTPProxyConfig is the HTTP proxy configuration for the cluster.
   158  type HTTPProxyConfig struct {
   159  	// HTTPProxy is the HTTP proxy server endpoint to use.
   160  	HTTPProxy *string `json:"httpProxy,omitempty"`
   161  
   162  	// HTTPSProxy is the HTTPS proxy server endpoint to use.
   163  	HTTPSProxy *string `json:"httpsProxy,omitempty"`
   164  
   165  	// NoProxy is the endpoints that should not go through proxy.
   166  	NoProxy []string `json:"noProxy,omitempty"`
   167  
   168  	// TrustedCA is the Alternative CA cert to use for connecting to proxy servers.
   169  	TrustedCA *string `json:"trustedCa,omitempty"`
   170  }
   171  
   172  // AADProfile is Azure Active Directory configuration to integrate with AKS, for aad authentication.
   173  type AADProfile struct {
   174  	// Managed defines whether to enable managed AAD.
   175  	Managed bool
   176  
   177  	// EnableAzureRBAC defines whether to enable Azure RBAC for Kubernetes authorization.
   178  	EnableAzureRBAC bool
   179  
   180  	// AdminGroupObjectIDs are the AAD group object IDs that will have admin role of the cluster.
   181  	AdminGroupObjectIDs []string
   182  }
   183  
   184  // AddonProfile is the profile of a managed cluster add-on.
   185  type AddonProfile struct {
   186  	Name    string
   187  	Config  map[string]string
   188  	Enabled bool
   189  }
   190  
   191  // SKU is an AKS SKU.
   192  type SKU struct {
   193  	// Tier is the tier of a managed cluster SKU.
   194  	Tier string
   195  }
   196  
   197  // LoadBalancerProfile is the profile of the cluster load balancer.
   198  type LoadBalancerProfile struct {
   199  	// Load balancer profile must specify at most one of ManagedOutboundIPs, OutboundIPPrefixes and OutboundIPs.
   200  	// By default the AKS cluster automatically creates a public IP in the AKS-managed infrastructure resource group and assigns it to the load balancer outbound pool.
   201  	// Alternatively, you can assign your own custom public IP or public IP prefix at cluster creation time.
   202  	// See https://learn.microsoft.com/azure/aks/load-balancer-standard#provide-your-own-outbound-public-ips-or-prefixes
   203  
   204  	// ManagedOutboundIPs are the desired managed outbound IPs for the cluster load balancer.
   205  	ManagedOutboundIPs *int
   206  
   207  	// OutboundIPPrefixes are the desired outbound IP Prefix resources for the cluster load balancer.
   208  	OutboundIPPrefixes []string
   209  
   210  	// OutboundIPs are the desired outbound IP resources for the cluster load balancer.
   211  	OutboundIPs []string
   212  
   213  	// AllocatedOutboundPorts are the desired number of allocated SNAT ports per VM. Allowed values must be in the range of 0 to 64000 (inclusive). The default value is 0 which results in Azure dynamically allocating ports.
   214  	AllocatedOutboundPorts *int
   215  
   216  	// IdleTimeoutInMinutes  are the desired outbound flow idle timeout in minutes. Allowed values must be in the range of 4 to 120 (inclusive). The default value is 30 minutes.
   217  	IdleTimeoutInMinutes *int
   218  }
   219  
   220  // APIServerAccessProfile is the access profile for AKS API server.
   221  type APIServerAccessProfile struct {
   222  	// AuthorizedIPRanges are the authorized IP Ranges to kubernetes API server.
   223  	AuthorizedIPRanges []string
   224  	// EnablePrivateCluster defines hether to create the cluster as a private cluster or not.
   225  	EnablePrivateCluster *bool
   226  	// PrivateDNSZone is the private dns zone for private clusters.
   227  	PrivateDNSZone *string
   228  	// EnablePrivateClusterPublicFQDN defines whether to create additional public FQDN for private cluster or not.
   229  	EnablePrivateClusterPublicFQDN *bool
   230  }
   231  
   232  // AutoScalerProfile parameters to be applied to the cluster-autoscaler when enabled.
   233  type AutoScalerProfile struct {
   234  	// BalanceSimilarNodeGroups - Valid values are 'true' and 'false'
   235  	BalanceSimilarNodeGroups *string
   236  	// Expander - If not specified, the default is 'random'. See [expanders](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md#what-are-expanders) for more information.
   237  	Expander *string
   238  	// MaxEmptyBulkDelete - The default is 10.
   239  	MaxEmptyBulkDelete *string
   240  	// MaxGracefulTerminationSec - The default is 600.
   241  	MaxGracefulTerminationSec *string
   242  	// MaxNodeProvisionTime - The default is '15m'. Values must be an integer followed by an 'm'. No unit of time other than minutes (m) is supported.
   243  	MaxNodeProvisionTime *string
   244  	// MaxTotalUnreadyPercentage - The default is 45. The maximum is 100 and the minimum is 0.
   245  	MaxTotalUnreadyPercentage *string
   246  	// NewPodScaleUpDelay - For scenarios like burst/batch scale where you don't want CA to act before the kubernetes scheduler could schedule all the pods, you can tell CA to ignore unscheduled pods before they're a certain age. The default is '0s'. Values must be an integer followed by a unit ('s' for seconds, 'm' for minutes, 'h' for hours, etc).
   247  	NewPodScaleUpDelay *string
   248  	// OkTotalUnreadyCount - This must be an integer. The default is 3.
   249  	OkTotalUnreadyCount *string
   250  	// ScanInterval - The default is '10s'. Values must be an integer number of seconds.
   251  	ScanInterval *string
   252  	// ScaleDownDelayAfterAdd - The default is '10m'. Values must be an integer followed by an 'm'. No unit of time other than minutes (m) is supported.
   253  	ScaleDownDelayAfterAdd *string
   254  	// ScaleDownDelayAfterDelete - The default is the scan-interval. Values must be an integer followed by an 'm'. No unit of time other than minutes (m) is supported.
   255  	ScaleDownDelayAfterDelete *string
   256  	// ScaleDownDelayAfterFailure - The default is '3m'. Values must be an integer followed by an 'm'. No unit of time other than minutes (m) is supported.
   257  	ScaleDownDelayAfterFailure *string
   258  	// ScaleDownUnneededTime - The default is '10m'. Values must be an integer followed by an 'm'. No unit of time other than minutes (m) is supported.
   259  	ScaleDownUnneededTime *string
   260  	// ScaleDownUnreadyTime - The default is '20m'. Values must be an integer followed by an 'm'. No unit of time other than minutes (m) is supported.
   261  	ScaleDownUnreadyTime *string
   262  	// ScaleDownUtilizationThreshold - The default is '0.5'.
   263  	ScaleDownUtilizationThreshold *string
   264  	// SkipNodesWithLocalStorage - The default is true.
   265  	SkipNodesWithLocalStorage *string
   266  	// SkipNodesWithSystemPods - The default is true.
   267  	SkipNodesWithSystemPods *string
   268  }
   269  
   270  // OIDCIssuerProfile is the OIDC issuer profile of the Managed Cluster.
   271  type OIDCIssuerProfile struct {
   272  	// Enabled is whether the OIDC issuer is enabled.
   273  	Enabled *bool
   274  }
   275  
   276  // ManagedClusterSecurityProfile defines the security profile for the cluster.
   277  type ManagedClusterSecurityProfile struct {
   278  	// AzureKeyVaultKms defines Azure Key Vault key management service settings for the security profile.
   279  	AzureKeyVaultKms *AzureKeyVaultKms
   280  
   281  	// Defender defines Microsoft Defender settings for the security profile.
   282  	Defender *ManagedClusterSecurityProfileDefender
   283  
   284  	// ImageCleaner settings for the security profile.
   285  	ImageCleaner *ManagedClusterSecurityProfileImageCleaner
   286  
   287  	// Workloadidentity enables Kubernetes applications to access Azure cloud resources securely with Azure AD.
   288  	WorkloadIdentity *ManagedClusterSecurityProfileWorkloadIdentity
   289  }
   290  
   291  // ManagedClusterSecurityProfileDefender defines Microsoft Defender settings for the security profile.
   292  type ManagedClusterSecurityProfileDefender struct {
   293  	// LogAnalyticsWorkspaceResourceID is the ID of the Log Analytics workspace that has to be associated with Microsoft Defender.
   294  	// When Microsoft Defender is enabled, this field is required and must be a valid workspace resource ID.
   295  	LogAnalyticsWorkspaceResourceID *string
   296  
   297  	// SecurityMonitoring profile defines the Microsoft Defender threat detection for Cloud settings for the security profile.
   298  	SecurityMonitoring *ManagedClusterSecurityProfileDefenderSecurityMonitoring
   299  }
   300  
   301  // ManagedClusterSecurityProfileDefenderSecurityMonitoring settings for the security profile threat detection.
   302  type ManagedClusterSecurityProfileDefenderSecurityMonitoring struct {
   303  	// Enabled enables Defender threat detection
   304  	Enabled *bool
   305  }
   306  
   307  // ManagedClusterSecurityProfileImageCleaner removes unused images from nodes, freeing up disk space and helping to reduce attack surface area.
   308  type ManagedClusterSecurityProfileImageCleaner struct {
   309  	// Enabled enables Image Cleaner on AKS cluster.
   310  	Enabled *bool
   311  
   312  	// Image Cleaner scanning interval in hours.
   313  	IntervalHours *int
   314  }
   315  
   316  // ManagedClusterSecurityProfileWorkloadIdentity defines Workload identity settings for the security profile.
   317  type ManagedClusterSecurityProfileWorkloadIdentity struct {
   318  	// Enabled enables workload identity.
   319  	Enabled *bool
   320  }
   321  
   322  // AzureKeyVaultKms Azure Key Vault key management service settings for the security profile.
   323  type AzureKeyVaultKms struct {
   324  	// Enabled enables Azure Key Vault key management service. The default is false.
   325  	Enabled *bool
   326  
   327  	// KeyID defines the Identifier of Azure Key Vault key.
   328  	// When Azure Key Vault key management service is enabled, this field is required and must be a valid key identifier.
   329  	KeyID *string
   330  
   331  	// KeyVaultNetworkAccess defines the network access of key vault.
   332  	// The possible values are Public and Private.
   333  	// Public means the key vault allows public access from all networks.
   334  	// Private means the key vault disables public access and enables private link. The default value is Public.
   335  	KeyVaultNetworkAccess *infrav1.KeyVaultNetworkAccessTypes
   336  
   337  	// KeyVaultResourceID is the Resource ID of key vault. When keyVaultNetworkAccess is Private, this field is required and must be a valid resource ID.
   338  	KeyVaultResourceID *string
   339  }
   340  
   341  // buildAutoScalerProfile builds the AutoScalerProfile for the ManagedClusterProperties.
   342  func buildAutoScalerProfile(autoScalerProfile *AutoScalerProfile) *asocontainerservicev1.ManagedClusterProperties_AutoScalerProfile {
   343  	if autoScalerProfile == nil {
   344  		return nil
   345  	}
   346  
   347  	mcAutoScalerProfile := &asocontainerservicev1.ManagedClusterProperties_AutoScalerProfile{
   348  		BalanceSimilarNodeGroups:      autoScalerProfile.BalanceSimilarNodeGroups,
   349  		MaxEmptyBulkDelete:            autoScalerProfile.MaxEmptyBulkDelete,
   350  		MaxGracefulTerminationSec:     autoScalerProfile.MaxGracefulTerminationSec,
   351  		MaxNodeProvisionTime:          autoScalerProfile.MaxNodeProvisionTime,
   352  		MaxTotalUnreadyPercentage:     autoScalerProfile.MaxTotalUnreadyPercentage,
   353  		NewPodScaleUpDelay:            autoScalerProfile.NewPodScaleUpDelay,
   354  		OkTotalUnreadyCount:           autoScalerProfile.OkTotalUnreadyCount,
   355  		ScanInterval:                  autoScalerProfile.ScanInterval,
   356  		ScaleDownDelayAfterAdd:        autoScalerProfile.ScaleDownDelayAfterAdd,
   357  		ScaleDownDelayAfterDelete:     autoScalerProfile.ScaleDownDelayAfterDelete,
   358  		ScaleDownDelayAfterFailure:    autoScalerProfile.ScaleDownDelayAfterFailure,
   359  		ScaleDownUnneededTime:         autoScalerProfile.ScaleDownUnneededTime,
   360  		ScaleDownUnreadyTime:          autoScalerProfile.ScaleDownUnreadyTime,
   361  		ScaleDownUtilizationThreshold: autoScalerProfile.ScaleDownUtilizationThreshold,
   362  		SkipNodesWithLocalStorage:     autoScalerProfile.SkipNodesWithLocalStorage,
   363  		SkipNodesWithSystemPods:       autoScalerProfile.SkipNodesWithSystemPods,
   364  	}
   365  	if autoScalerProfile.Expander != nil {
   366  		mcAutoScalerProfile.Expander = ptr.To(asocontainerservicev1.ManagedClusterProperties_AutoScalerProfile_Expander(*autoScalerProfile.Expander))
   367  	}
   368  
   369  	return mcAutoScalerProfile
   370  }
   371  
   372  // getManagedClusterVersion gets the desired managed k8s version.
   373  // If autoupgrade channels is set to patch, stable or rapid, clusters can be upgraded to higher version by AKS.
   374  // If autoupgrade is triggered, existing kubernetes version will be higher than the user desired kubernetes version.
   375  // CAPZ should honour the upgrade and it should not downgrade to the lower desired version.
   376  func (s *ManagedClusterSpec) getManagedClusterVersion(existing *asocontainerservicev1.ManagedCluster) string {
   377  	if existing == nil || existing.Status.CurrentKubernetesVersion == nil {
   378  		return s.Version
   379  	}
   380  	return versions.GetHigherK8sVersion(s.Version, *existing.Status.CurrentKubernetesVersion)
   381  }
   382  
   383  // ResourceRef implements azure.ASOResourceSpecGetter.
   384  func (s *ManagedClusterSpec) ResourceRef() genruntime.MetaObject {
   385  	if s.Preview {
   386  		return &asocontainerservicev1preview.ManagedCluster{
   387  			ObjectMeta: metav1.ObjectMeta{
   388  				Name: s.Name,
   389  			},
   390  		}
   391  	}
   392  	return &asocontainerservicev1.ManagedCluster{
   393  		ObjectMeta: metav1.ObjectMeta{
   394  			Name: s.Name,
   395  		},
   396  	}
   397  }
   398  
   399  // Parameters returns the parameters for the managed clusters.
   400  //
   401  //nolint:gocyclo // Function requires a lot of nil checks that raise complexity.
   402  func (s *ManagedClusterSpec) Parameters(ctx context.Context, existingObj genruntime.MetaObject) (params genruntime.MetaObject, err error) {
   403  	ctx, _, done := tele.StartSpanWithLogger(ctx, "managedclusters.Service.Parameters")
   404  	defer done()
   405  
   406  	// If existing is preview, convert to stable then back to preview at the end of the function.
   407  	var existing *asocontainerservicev1.ManagedCluster
   408  	var existingStatus asocontainerservicev1preview.ManagedCluster_STATUS
   409  	if existingObj != nil {
   410  		if s.Preview {
   411  			existingPreview := existingObj.(*asocontainerservicev1preview.ManagedCluster)
   412  			existingStatus = existingPreview.Status
   413  			hub := &asocontainerservicev1hub.ManagedCluster{}
   414  			if err := existingPreview.ConvertTo(hub); err != nil {
   415  				return nil, err
   416  			}
   417  			stable := &asocontainerservicev1.ManagedCluster{}
   418  			if err := stable.ConvertFrom(hub); err != nil {
   419  				return nil, err
   420  			}
   421  			existing = stable.DeepCopy()
   422  		} else {
   423  			existing = existingObj.(*asocontainerservicev1.ManagedCluster)
   424  		}
   425  	}
   426  
   427  	managedCluster := existing
   428  	if managedCluster == nil {
   429  		managedCluster = &asocontainerservicev1.ManagedCluster{
   430  			Spec: asocontainerservicev1.ManagedCluster_Spec{
   431  				Tags: infrav1.Build(infrav1.BuildParams{
   432  					Lifecycle:   infrav1.ResourceLifecycleOwned,
   433  					ClusterName: s.ClusterName,
   434  					Name:        ptr.To(s.Name),
   435  					Role:        ptr.To(infrav1.CommonRole),
   436  					// Additional tags managed separately
   437  				}),
   438  			},
   439  		}
   440  	}
   441  
   442  	managedCluster.Spec.AzureName = s.Name
   443  	managedCluster.Spec.Owner = &genruntime.KnownResourceReference{
   444  		Name: s.ResourceGroup,
   445  	}
   446  	managedCluster.Spec.Identity = &asocontainerservicev1.ManagedClusterIdentity{
   447  		Type: ptr.To(asocontainerservicev1.ManagedClusterIdentity_Type_SystemAssigned),
   448  	}
   449  	managedCluster.Spec.Location = &s.Location
   450  	managedCluster.Spec.NodeResourceGroup = &s.NodeResourceGroup
   451  	managedCluster.Spec.EnableRBAC = ptr.To(true)
   452  	managedCluster.Spec.DnsPrefix = s.DNSPrefix
   453  
   454  	if kubernetesVersion := s.getManagedClusterVersion(existing); kubernetesVersion != "" {
   455  		managedCluster.Spec.KubernetesVersion = &kubernetesVersion
   456  	}
   457  
   458  	managedCluster.Spec.ServicePrincipalProfile = &asocontainerservicev1.ManagedClusterServicePrincipalProfile{
   459  		ClientId: ptr.To("msi"),
   460  	}
   461  	managedCluster.Spec.NetworkProfile = &asocontainerservicev1.ContainerServiceNetworkProfile{
   462  		NetworkPlugin:   azure.AliasOrNil[asocontainerservicev1.NetworkPlugin](&s.NetworkPlugin),
   463  		LoadBalancerSku: azure.AliasOrNil[asocontainerservicev1.ContainerServiceNetworkProfile_LoadBalancerSku](&s.LoadBalancerSKU),
   464  		NetworkPolicy:   azure.AliasOrNil[asocontainerservicev1.ContainerServiceNetworkProfile_NetworkPolicy](&s.NetworkPolicy),
   465  	}
   466  	if s.NetworkDataplane != nil {
   467  		managedCluster.Spec.NetworkProfile.NetworkDataplane = ptr.To(asocontainerservicev1.ContainerServiceNetworkProfile_NetworkDataplane(*s.NetworkDataplane))
   468  	}
   469  	managedCluster.Spec.AutoScalerProfile = buildAutoScalerProfile(s.AutoScalerProfile)
   470  
   471  	var decodedSSHPublicKey []byte
   472  	if s.SSHPublicKey != "" {
   473  		decodedSSHPublicKey, err = base64.StdEncoding.DecodeString(s.SSHPublicKey)
   474  		if err != nil {
   475  			return nil, errors.Wrap(err, "failed to decode SSHPublicKey")
   476  		}
   477  	}
   478  
   479  	if decodedSSHPublicKey != nil {
   480  		managedCluster.Spec.LinuxProfile = &asocontainerservicev1.ContainerServiceLinuxProfile{
   481  			AdminUsername: ptr.To(azure.DefaultAKSUserName),
   482  			Ssh: &asocontainerservicev1.ContainerServiceSshConfiguration{
   483  				PublicKeys: []asocontainerservicev1.ContainerServiceSshPublicKey{
   484  					{
   485  						KeyData: ptr.To(string(decodedSSHPublicKey)),
   486  					},
   487  				},
   488  			},
   489  		}
   490  	}
   491  
   492  	if s.NetworkPluginMode != nil {
   493  		managedCluster.Spec.NetworkProfile.NetworkPluginMode = ptr.To(asocontainerservicev1.ContainerServiceNetworkProfile_NetworkPluginMode(*s.NetworkPluginMode))
   494  	}
   495  
   496  	if s.PodCIDR != "" {
   497  		managedCluster.Spec.NetworkProfile.PodCidr = &s.PodCIDR
   498  	}
   499  
   500  	if s.ServiceCIDR != "" {
   501  		managedCluster.Spec.NetworkProfile.ServiceCidr = &s.ServiceCIDR
   502  		managedCluster.Spec.NetworkProfile.DnsServiceIP = s.DNSServiceIP
   503  		if s.DNSServiceIP == nil {
   504  			ip, _, err := net.ParseCIDR(s.ServiceCIDR)
   505  			if err != nil {
   506  				return nil, fmt.Errorf("failed to parse service cidr: %w", err)
   507  			}
   508  			// HACK: set the last octet of the IP to .10
   509  			// This ensures the dns IP is valid in the service cidr without forcing the user
   510  			// to specify it in both the Capi cluster and the Azure control plane.
   511  			// https://golang.org/src/net/ip.go#L48
   512  			ip[15] = byte(10)
   513  			dnsIP := ip.String()
   514  			managedCluster.Spec.NetworkProfile.DnsServiceIP = &dnsIP
   515  		}
   516  	}
   517  
   518  	// OperatorSpec defines how the Secrets generated by ASO should look for the AKS cluster kubeconfigs.
   519  	// There is no prescribed naming convention that must be followed.
   520  	managedCluster.Spec.OperatorSpec = &asocontainerservicev1.ManagedClusterOperatorSpec{
   521  		Secrets: &asocontainerservicev1.ManagedClusterOperatorSecrets{
   522  			AdminCredentials: &genruntime.SecretDestination{
   523  				Name: adminKubeconfigSecretName(s.ClusterName),
   524  				Key:  secret.KubeconfigDataName,
   525  			},
   526  		},
   527  	}
   528  
   529  	if s.AADProfile != nil {
   530  		managedCluster.Spec.AadProfile = &asocontainerservicev1.ManagedClusterAADProfile{
   531  			Managed:             &s.AADProfile.Managed,
   532  			EnableAzureRBAC:     &s.AADProfile.EnableAzureRBAC,
   533  			AdminGroupObjectIDs: s.AADProfile.AdminGroupObjectIDs,
   534  		}
   535  		if s.DisableLocalAccounts != nil {
   536  			managedCluster.Spec.DisableLocalAccounts = s.DisableLocalAccounts
   537  		}
   538  
   539  		if ptr.Deref(s.DisableLocalAccounts, false) {
   540  			// admin credentials cannot be fetched when local accounts are disabled
   541  			managedCluster.Spec.OperatorSpec.Secrets.AdminCredentials = nil
   542  		}
   543  		if s.AADProfile.Managed {
   544  			managedCluster.Spec.OperatorSpec.Secrets.UserCredentials = &genruntime.SecretDestination{
   545  				Name: userKubeconfigSecretName(s.ClusterName),
   546  				Key:  secret.KubeconfigDataName,
   547  			}
   548  		}
   549  	}
   550  
   551  	for i := range s.AddonProfiles {
   552  		if managedCluster.Spec.AddonProfiles == nil {
   553  			managedCluster.Spec.AddonProfiles = map[string]asocontainerservicev1.ManagedClusterAddonProfile{}
   554  		}
   555  		item := s.AddonProfiles[i]
   556  		addonProfile := asocontainerservicev1.ManagedClusterAddonProfile{
   557  			Enabled: &item.Enabled,
   558  		}
   559  		if item.Config != nil {
   560  			addonProfile.Config = item.Config
   561  		}
   562  		managedCluster.Spec.AddonProfiles[item.Name] = addonProfile
   563  	}
   564  
   565  	if s.SKU != nil {
   566  		tierName := asocontainerservicev1.ManagedClusterSKU_Tier(s.SKU.Tier)
   567  		managedCluster.Spec.Sku = &asocontainerservicev1.ManagedClusterSKU{
   568  			Name: ptr.To(asocontainerservicev1.ManagedClusterSKU_Name("Base")),
   569  			Tier: ptr.To(tierName),
   570  		}
   571  	}
   572  
   573  	if s.LoadBalancerProfile != nil {
   574  		managedCluster.Spec.NetworkProfile.LoadBalancerProfile = s.GetLoadBalancerProfile()
   575  	}
   576  
   577  	if s.APIServerAccessProfile != nil {
   578  		managedCluster.Spec.ApiServerAccessProfile = &asocontainerservicev1.ManagedClusterAPIServerAccessProfile{
   579  			EnablePrivateCluster:           s.APIServerAccessProfile.EnablePrivateCluster,
   580  			PrivateDNSZone:                 s.APIServerAccessProfile.PrivateDNSZone,
   581  			EnablePrivateClusterPublicFQDN: s.APIServerAccessProfile.EnablePrivateClusterPublicFQDN,
   582  		}
   583  
   584  		if s.APIServerAccessProfile.AuthorizedIPRanges != nil {
   585  			managedCluster.Spec.ApiServerAccessProfile.AuthorizedIPRanges = s.APIServerAccessProfile.AuthorizedIPRanges
   586  		}
   587  	}
   588  
   589  	if s.OutboundType != nil {
   590  		managedCluster.Spec.NetworkProfile.OutboundType = ptr.To(asocontainerservicev1.ContainerServiceNetworkProfile_OutboundType(*s.OutboundType))
   591  	}
   592  
   593  	if s.Identity != nil {
   594  		managedCluster.Spec.Identity, err = getIdentity(s.Identity)
   595  		if err != nil {
   596  			return nil, errors.Wrapf(err, "Identity is not valid: %s", err)
   597  		}
   598  	}
   599  
   600  	if s.KubeletUserAssignedIdentity != "" {
   601  		managedCluster.Spec.IdentityProfile = map[string]asocontainerservicev1.UserAssignedIdentity{
   602  			kubeletIdentityKey: {
   603  				ResourceReference: &genruntime.ResourceReference{
   604  					ARMID: s.KubeletUserAssignedIdentity,
   605  				},
   606  			},
   607  		}
   608  	}
   609  
   610  	if s.HTTPProxyConfig != nil {
   611  		managedCluster.Spec.HttpProxyConfig = &asocontainerservicev1.ManagedClusterHTTPProxyConfig{
   612  			HttpProxy:  s.HTTPProxyConfig.HTTPProxy,
   613  			HttpsProxy: s.HTTPProxyConfig.HTTPSProxy,
   614  			TrustedCa:  s.HTTPProxyConfig.TrustedCA,
   615  		}
   616  
   617  		if s.HTTPProxyConfig.NoProxy != nil {
   618  			managedCluster.Spec.HttpProxyConfig.NoProxy = s.HTTPProxyConfig.NoProxy
   619  		}
   620  	}
   621  
   622  	if s.OIDCIssuerProfile != nil {
   623  		managedCluster.Spec.OidcIssuerProfile = &asocontainerservicev1.ManagedClusterOIDCIssuerProfile{
   624  			Enabled: s.OIDCIssuerProfile.Enabled,
   625  		}
   626  		if ptr.Deref(s.OIDCIssuerProfile.Enabled, false) {
   627  			managedCluster.Spec.OperatorSpec.ConfigMaps = &asocontainerservicev1.ManagedClusterOperatorConfigMaps{
   628  				OIDCIssuerProfile: &genruntime.ConfigMapDestination{
   629  					Name: oidcIssuerURLConfigMapName(s.ClusterName),
   630  					Key:  oidcIssuerProfileURL,
   631  				},
   632  			}
   633  		}
   634  	}
   635  
   636  	if s.AutoUpgradeProfile != nil {
   637  		managedCluster.Spec.AutoUpgradeProfile = &asocontainerservicev1.ManagedClusterAutoUpgradeProfile{
   638  			UpgradeChannel: (*asocontainerservicev1.ManagedClusterAutoUpgradeProfile_UpgradeChannel)(s.AutoUpgradeProfile.UpgradeChannel),
   639  		}
   640  	}
   641  
   642  	if s.SecurityProfile != nil {
   643  		securityProfile := &asocontainerservicev1.ManagedClusterSecurityProfile{}
   644  		if s.SecurityProfile.AzureKeyVaultKms != nil {
   645  			securityProfile.AzureKeyVaultKms = &asocontainerservicev1.AzureKeyVaultKms{
   646  				Enabled: s.SecurityProfile.AzureKeyVaultKms.Enabled,
   647  				KeyId:   s.SecurityProfile.AzureKeyVaultKms.KeyID,
   648  			}
   649  			if s.SecurityProfile.AzureKeyVaultKms.KeyVaultNetworkAccess != nil {
   650  				keyVaultNetworkAccess := string(*s.SecurityProfile.AzureKeyVaultKms.KeyVaultNetworkAccess)
   651  				securityProfile.AzureKeyVaultKms.KeyVaultNetworkAccess = ptr.To(asocontainerservicev1.AzureKeyVaultKms_KeyVaultNetworkAccess(keyVaultNetworkAccess))
   652  			}
   653  			if s.SecurityProfile.AzureKeyVaultKms.KeyVaultResourceID != nil {
   654  				securityProfile.AzureKeyVaultKms.KeyVaultResourceReference = &genruntime.ResourceReference{
   655  					ARMID: *s.SecurityProfile.AzureKeyVaultKms.KeyVaultResourceID,
   656  				}
   657  			}
   658  		}
   659  		if s.SecurityProfile.Defender != nil {
   660  			securityProfile.Defender = &asocontainerservicev1.ManagedClusterSecurityProfileDefender{
   661  				LogAnalyticsWorkspaceResourceReference: &genruntime.ResourceReference{
   662  					ARMID: *s.SecurityProfile.Defender.LogAnalyticsWorkspaceResourceID,
   663  				},
   664  			}
   665  			if s.SecurityProfile.Defender.SecurityMonitoring != nil {
   666  				securityProfile.Defender.SecurityMonitoring = &asocontainerservicev1.ManagedClusterSecurityProfileDefenderSecurityMonitoring{
   667  					Enabled: s.SecurityProfile.Defender.SecurityMonitoring.Enabled,
   668  				}
   669  			}
   670  		}
   671  		if s.SecurityProfile.ImageCleaner != nil {
   672  			securityProfile.ImageCleaner = &asocontainerservicev1.ManagedClusterSecurityProfileImageCleaner{
   673  				Enabled:       s.SecurityProfile.ImageCleaner.Enabled,
   674  				IntervalHours: s.SecurityProfile.ImageCleaner.IntervalHours,
   675  			}
   676  		}
   677  		if s.SecurityProfile.WorkloadIdentity != nil {
   678  			securityProfile.WorkloadIdentity = &asocontainerservicev1.ManagedClusterSecurityProfileWorkloadIdentity{
   679  				Enabled: s.SecurityProfile.WorkloadIdentity.Enabled,
   680  			}
   681  		}
   682  		managedCluster.Spec.SecurityProfile = securityProfile
   683  	}
   684  
   685  	// Only include AgentPoolProfiles during initial cluster creation. Agent pools are managed solely by the
   686  	// AzureManagedMachinePool controller thereafter.
   687  	var prevAgentPoolProfiles []asocontainerservicev1preview.ManagedClusterAgentPoolProfile
   688  	managedCluster.Spec.AgentPoolProfiles = nil
   689  	if managedCluster.Status.AgentPoolProfiles == nil {
   690  		// Add all agent pools to cluster spec that will be submitted to the API
   691  		agentPoolSpecs, err := s.GetAllAgentPools()
   692  		if err != nil {
   693  			return nil, errors.Wrapf(err, "failed to get agent pool specs for managed cluster %s", s.Name)
   694  		}
   695  
   696  		scheme := runtime.NewScheme()
   697  		if err := asocontainerservicev1.AddToScheme(scheme); err != nil {
   698  			return nil, errors.Wrap(err, "error constructing scheme")
   699  		}
   700  		if err := asocontainerservicev1preview.AddToScheme(scheme); err != nil {
   701  			return nil, errors.Wrap(err, "error constructing scheme")
   702  		}
   703  		for _, agentPoolSpec := range agentPoolSpecs {
   704  			agentPool, err := aso.PatchedParameters(ctx, scheme, agentPoolSpec, nil)
   705  			if err != nil {
   706  				return nil, errors.Wrapf(err, "failed to get agent pool parameters for managed cluster %s", s.Name)
   707  			}
   708  			agentPoolSpecTyped := agentPoolSpec.(*agentpools.AgentPoolSpec)
   709  			if s.Preview {
   710  				agentPoolTyped := agentPool.(*asocontainerservicev1preview.ManagedClustersAgentPool)
   711  				agentPoolTyped.Spec.AzureName = agentPoolSpecTyped.AzureName
   712  				profile := converters.AgentPoolToManagedClusterAgentPoolPreviewProfile(agentPoolTyped)
   713  				prevAgentPoolProfiles = append(prevAgentPoolProfiles, profile)
   714  			} else {
   715  				agentPoolTyped := agentPool.(*asocontainerservicev1.ManagedClustersAgentPool)
   716  				agentPoolTyped.Spec.AzureName = agentPoolSpecTyped.AzureName
   717  				profile := converters.AgentPoolToManagedClusterAgentPoolProfile(agentPoolTyped)
   718  				managedCluster.Spec.AgentPoolProfiles = append(managedCluster.Spec.AgentPoolProfiles, profile)
   719  			}
   720  		}
   721  	}
   722  
   723  	if s.Preview {
   724  		hub := &asocontainerservicev1hub.ManagedCluster{}
   725  		if err := managedCluster.ConvertTo(hub); err != nil {
   726  			return nil, err
   727  		}
   728  		prev := &asocontainerservicev1preview.ManagedCluster{}
   729  		if err := prev.ConvertFrom(hub); err != nil {
   730  			return nil, err
   731  		}
   732  		if existing != nil {
   733  			prev.Status = existingStatus
   734  		}
   735  		if prevAgentPoolProfiles != nil {
   736  			prev.Spec.AgentPoolProfiles = prevAgentPoolProfiles
   737  		}
   738  		return prev, nil
   739  	}
   740  
   741  	return managedCluster, nil
   742  }
   743  
   744  // GetLoadBalancerProfile returns an asocontainerservicev1.ManagedClusterLoadBalancerProfile from the
   745  // information present in ManagedClusterSpec.LoadBalancerProfile.
   746  func (s *ManagedClusterSpec) GetLoadBalancerProfile() (loadBalancerProfile *asocontainerservicev1.ManagedClusterLoadBalancerProfile) {
   747  	loadBalancerProfile = &asocontainerservicev1.ManagedClusterLoadBalancerProfile{
   748  		AllocatedOutboundPorts: s.LoadBalancerProfile.AllocatedOutboundPorts,
   749  		IdleTimeoutInMinutes:   s.LoadBalancerProfile.IdleTimeoutInMinutes,
   750  	}
   751  	if s.LoadBalancerProfile.ManagedOutboundIPs != nil {
   752  		loadBalancerProfile.ManagedOutboundIPs = &asocontainerservicev1.ManagedClusterLoadBalancerProfile_ManagedOutboundIPs{Count: s.LoadBalancerProfile.ManagedOutboundIPs}
   753  	}
   754  	if len(s.LoadBalancerProfile.OutboundIPPrefixes) > 0 {
   755  		loadBalancerProfile.OutboundIPPrefixes = &asocontainerservicev1.ManagedClusterLoadBalancerProfile_OutboundIPPrefixes{
   756  			PublicIPPrefixes: convertToResourceReferences(s.LoadBalancerProfile.OutboundIPPrefixes),
   757  		}
   758  	}
   759  	if len(s.LoadBalancerProfile.OutboundIPs) > 0 {
   760  		loadBalancerProfile.OutboundIPs = &asocontainerservicev1.ManagedClusterLoadBalancerProfile_OutboundIPs{
   761  			PublicIPs: convertToResourceReferences(s.LoadBalancerProfile.OutboundIPs),
   762  		}
   763  	}
   764  	return
   765  }
   766  
   767  func convertToResourceReferences(resources []string) []asocontainerservicev1.ResourceReference {
   768  	resourceReferences := make([]asocontainerservicev1.ResourceReference, len(resources))
   769  	for i := range resources {
   770  		resourceReferences[i] = asocontainerservicev1.ResourceReference{
   771  			Reference: &genruntime.ResourceReference{
   772  				ARMID: resources[i],
   773  			},
   774  		}
   775  	}
   776  	return resourceReferences
   777  }
   778  
   779  func getIdentity(identity *infrav1.Identity) (managedClusterIdentity *asocontainerservicev1.ManagedClusterIdentity, err error) {
   780  	if identity.Type == "" {
   781  		return
   782  	}
   783  
   784  	managedClusterIdentity = &asocontainerservicev1.ManagedClusterIdentity{
   785  		Type: ptr.To(asocontainerservicev1.ManagedClusterIdentity_Type(identity.Type)),
   786  	}
   787  	if ptr.Deref(managedClusterIdentity.Type, "") == asocontainerservicev1.ManagedClusterIdentity_Type_UserAssigned {
   788  		if identity.UserAssignedIdentityResourceID == "" {
   789  			err = errors.Errorf("Identity is set to \"UserAssigned\" but no UserAssignedIdentityResourceID is present")
   790  			return
   791  		}
   792  		managedClusterIdentity.UserAssignedIdentities = []asocontainerservicev1.UserAssignedIdentityDetails{
   793  			{
   794  				Reference: genruntime.ResourceReference{
   795  					ARMID: identity.UserAssignedIdentityResourceID,
   796  				},
   797  			},
   798  		}
   799  	}
   800  	return
   801  }
   802  
   803  func adminKubeconfigSecretName(clusterName string) string {
   804  	return secret.Name(clusterName+"-aso", secret.Kubeconfig)
   805  }
   806  
   807  func oidcIssuerURLConfigMapName(clusterName string) string {
   808  	return secret.Name(clusterName+"-aso", "oidc-issuer-profile")
   809  }
   810  
   811  func userKubeconfigSecretName(clusterName string) string {
   812  	return secret.Name(clusterName+"-user-aso", secret.Kubeconfig)
   813  }
   814  
   815  // WasManaged implements azure.ASOResourceSpecGetter.
   816  func (s *ManagedClusterSpec) WasManaged(resource genruntime.MetaObject) bool {
   817  	// CAPZ has never supported BYO managed clusters.
   818  	return true
   819  }
   820  
   821  var _ aso.TagsGetterSetter[genruntime.MetaObject] = (*ManagedClusterSpec)(nil)
   822  
   823  // GetAdditionalTags implements aso.TagsGetterSetter.
   824  func (s *ManagedClusterSpec) GetAdditionalTags() infrav1.Tags {
   825  	return s.Tags
   826  }
   827  
   828  // GetDesiredTags implements aso.TagsGetterSetter.
   829  func (s *ManagedClusterSpec) GetDesiredTags(resource genruntime.MetaObject) infrav1.Tags {
   830  	if s.Preview {
   831  		return resource.(*asocontainerservicev1preview.ManagedCluster).Spec.Tags
   832  	}
   833  	return resource.(*asocontainerservicev1.ManagedCluster).Spec.Tags
   834  }
   835  
   836  // SetTags implements aso.TagsGetterSetter.
   837  func (s *ManagedClusterSpec) SetTags(resource genruntime.MetaObject, tags infrav1.Tags) {
   838  	if s.Preview {
   839  		resource.(*asocontainerservicev1preview.ManagedCluster).Spec.Tags = tags
   840  		return
   841  	}
   842  	resource.(*asocontainerservicev1.ManagedCluster).Spec.Tags = tags
   843  }
   844  
   845  var _ aso.Patcher = (*ManagedClusterSpec)(nil)
   846  
   847  // ExtraPatches implements aso.Patcher.
   848  func (s *ManagedClusterSpec) ExtraPatches() []string {
   849  	return s.Patches
   850  }