diff --git a/azure/scope/managedcontrolplane.go b/azure/scope/managedcontrolplane.go index 036d6acc3205..1935bfb2580d 100644 --- a/azure/scope/managedcontrolplane.go +++ b/azure/scope/managedcontrolplane.go @@ -474,6 +474,16 @@ func (s *ManagedControlPlaneScope) ManagedClusterSpec() (azure.ManagedClusterSpe } } + if s.ControlPlane.Spec.AddonProfiles != nil { + for _, profile := range s.ControlPlane.Spec.AddonProfiles { + managedClusterSpec.AddonProfiles = append(managedClusterSpec.AddonProfiles, azure.AddonProfile{ + Name: profile.Name, + Enabled: profile.Enabled, + Config: profile.Config, + }) + } + } + if s.ControlPlane.Spec.SKU != nil { managedClusterSpec.SKU = &azure.SKU{ Tier: string(s.ControlPlane.Spec.SKU.Tier), diff --git a/azure/scope/managedcontrolplane_test.go b/azure/scope/managedcontrolplane_test.go index 650f575d5509..80bf75e3f853 100644 --- a/azure/scope/managedcontrolplane_test.go +++ b/azure/scope/managedcontrolplane_test.go @@ -670,6 +670,101 @@ func TestManagedControlPlaneScope_Taints(t *testing.T) { } } +func TestManagedControlPlaneScope_AddonProfiles(t *testing.T) { + scheme := runtime.NewScheme() + _ = capiv1exp.AddToScheme(scheme) + _ = infrav1.AddToScheme(scheme) + + cases := []struct { + Name string + Input ManagedControlPlaneScopeParams + Expected azure.ManagedClusterSpec + }{ + { + Name: "Without add-ons", + Input: ManagedControlPlaneScopeParams{ + AzureClients: AzureClients{ + Authorizer: autorest.NullAuthorizer{}, + }, + Cluster: &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster1", + Namespace: "default", + }, + }, + ControlPlane: &infrav1.AzureManagedControlPlane{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster1", + Namespace: "default", + }, + Spec: infrav1.AzureManagedControlPlaneSpec{ + SubscriptionID: "00000000-0000-0000-0000-000000000000", + }, + }, + MachinePool: getMachinePool("pool0"), + InfraMachinePool: getAzureMachinePool("pool0", infrav1.NodePoolModeSystem), + PatchTarget: getAzureMachinePool("pool0", infrav1.NodePoolModeSystem), + }, + Expected: azure.ManagedClusterSpec{ + Name: "cluster1", + VnetSubnetID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups//providers/Microsoft.Network/virtualNetworks//subnets/", + }, + }, + { + Name: "With add-ons", + Input: ManagedControlPlaneScopeParams{ + AzureClients: AzureClients{ + Authorizer: autorest.NullAuthorizer{}, + }, + Cluster: &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster1", + Namespace: "default", + }, + }, + ControlPlane: &infrav1.AzureManagedControlPlane{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster1", + Namespace: "default", + }, + Spec: infrav1.AzureManagedControlPlaneSpec{ + SubscriptionID: "00000000-0000-0000-0000-000000000000", + AddonProfiles: []infrav1.AddonProfile{ + {Name: "addon1", Config: nil, Enabled: false}, + {Name: "addon2", Config: map[string]string{"k1": "v1", "k2": "v2"}, Enabled: true}, + }, + }, + }, + MachinePool: getMachinePool("pool0"), + InfraMachinePool: getAzureMachinePool("pool0", infrav1.NodePoolModeSystem), + PatchTarget: getAzureMachinePool("pool0", infrav1.NodePoolModeSystem), + }, + Expected: azure.ManagedClusterSpec{ + Name: "cluster1", + VnetSubnetID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups//providers/Microsoft.Network/virtualNetworks//subnets/", + AddonProfiles: []azure.AddonProfile{ + {Name: "addon1", Config: nil, Enabled: false}, + {Name: "addon2", Config: map[string]string{"k1": "v1", "k2": "v2"}, Enabled: true}, + }, + }, + }, + } + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + g := NewWithT(t) + fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(c.Input.MachinePool, c.Input.InfraMachinePool, c.Input.ControlPlane).Build() + c.Input.Client = fakeClient + s, err := NewManagedControlPlaneScope(context.TODO(), c.Input) + g.Expect(err).To(Succeed()) + managedCluster, err := s.ManagedClusterSpec() + g.Expect(err).To(Succeed()) + g.Expect(managedCluster).To(Equal(c.Expected)) + }) + } +} + func getAzureMachinePool(name string, mode infrav1.NodePoolMode) *infrav1.AzureManagedMachinePool { return &infrav1.AzureManagedMachinePool{ ObjectMeta: metav1.ObjectMeta{ diff --git a/azure/services/managedclusters/managedclusters.go b/azure/services/managedclusters/managedclusters.go index 464b9cd94734..e58d0db13b61 100644 --- a/azure/services/managedclusters/managedclusters.go +++ b/azure/services/managedclusters/managedclusters.go @@ -99,6 +99,30 @@ func computeDiffOfNormalizedClusters(managedCluster containerservice.ManagedClus } } + if managedCluster.AddonProfiles != nil { + for k, v := range managedCluster.AddonProfiles { + if propertiesNormalized.AddonProfiles == nil { + propertiesNormalized.AddonProfiles = map[string]*containerservice.ManagedClusterAddonProfile{} + } + propertiesNormalized.AddonProfiles[k] = &containerservice.ManagedClusterAddonProfile{ + Enabled: v.Enabled, + Config: v.Config, + } + } + } + + if existingMC.AddonProfiles != nil { + for k, v := range existingMC.AddonProfiles { + if existingMCPropertiesNormalized.AddonProfiles == nil { + existingMCPropertiesNormalized.AddonProfiles = map[string]*containerservice.ManagedClusterAddonProfile{} + } + existingMCPropertiesNormalized.AddonProfiles[k] = &containerservice.ManagedClusterAddonProfile{ + Enabled: v.Enabled, + Config: v.Config, + } + } + } + if managedCluster.NetworkProfile != nil { propertiesNormalized.NetworkProfile.LoadBalancerProfile = managedCluster.NetworkProfile.LoadBalancerProfile } @@ -257,6 +281,8 @@ func (s *Service) Reconcile(ctx context.Context) error { } } + handleAddonProfiles(managedCluster, managedClusterSpec) + if managedClusterSpec.SKU != nil { tierName := containerservice.ManagedClusterSKUTier(managedClusterSpec.SKU.Tier) managedCluster.Sku = &containerservice.ManagedClusterSKU{ @@ -352,6 +378,22 @@ func (s *Service) Reconcile(ctx context.Context) error { return nil } +func handleAddonProfiles(managedCluster containerservice.ManagedCluster, spec azure.ManagedClusterSpec) { + for i := range spec.AddonProfiles { + if managedCluster.AddonProfiles == nil { + managedCluster.AddonProfiles = map[string]*containerservice.ManagedClusterAddonProfile{} + } + item := spec.AddonProfiles[i] + addonProfile := &containerservice.ManagedClusterAddonProfile{ + Enabled: &item.Enabled, + } + if item.Config != nil { + addonProfile.Config = *to.StringMapPtr(item.Config) + } + managedCluster.AddonProfiles[item.Name] = addonProfile + } +} + // Delete deletes the managed cluster. func (s *Service) Delete(ctx context.Context) error { ctx, _, done := tele.StartSpanWithLogger(ctx, "managedclusters.Service.Delete") diff --git a/azure/types.go b/azure/types.go index 6146eee2e39d..a134ae2615ce 100644 --- a/azure/types.go +++ b/azure/types.go @@ -239,6 +239,9 @@ type ManagedClusterSpec struct { // AADProfile is Azure Active Directory configuration to integrate with AKS, for aad authentication. AADProfile *AADProfile + // AddonProfiles are the profiles of managed cluster add-on. + AddonProfiles []AddonProfile + // SKU is the SKU of the AKS to be provisioned. SKU *SKU @@ -261,6 +264,13 @@ type AADProfile struct { AdminGroupObjectIDs []string } +// AddonProfile - Profile of managed cluster add-on. +type AddonProfile struct { + Name string + Config map[string]string + Enabled bool +} + // SKU - AKS SKU. type SKU struct { // Tier - Tier of a managed cluster SKU. diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremanagedcontrolplanes.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremanagedcontrolplanes.yaml index a53b7ff8b23b..1a590ec36f7a 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremanagedcontrolplanes.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremanagedcontrolplanes.yaml @@ -530,6 +530,26 @@ spec: resources managed by the Azure provider, in addition to the ones added by default. type: object + addonProfiles: + description: AddonProfiles are the profiles of managed cluster add-on. + items: + properties: + config: + additionalProperties: + type: string + description: Config - Key-value pairs for configuring an add-on. + type: object + enabled: + description: Enabled - Whether the add-on is enabled or not. + type: boolean + name: + description: Name- The name of managed cluster add-on. + type: string + required: + - enabled + - name + type: object + type: array apiServerAccessProfile: description: APIServerAccessProfile is the access profile for AKS API server. diff --git a/exp/api/v1alpha3/azuremanagedcontrolplane_conversion.go b/exp/api/v1alpha3/azuremanagedcontrolplane_conversion.go index beaad676c1b9..b0e1f4fd729a 100644 --- a/exp/api/v1alpha3/azuremanagedcontrolplane_conversion.go +++ b/exp/api/v1alpha3/azuremanagedcontrolplane_conversion.go @@ -40,6 +40,7 @@ func (src *AzureManagedControlPlane) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.SKU = restored.Spec.SKU dst.Spec.LoadBalancerProfile = restored.Spec.LoadBalancerProfile dst.Spec.APIServerAccessProfile = restored.Spec.APIServerAccessProfile + dst.Spec.AddonProfiles = restored.Spec.AddonProfiles dst.Status.LongRunningOperationStates = restored.Status.LongRunningOperationStates dst.Status.Conditions = restored.Status.Conditions diff --git a/exp/api/v1alpha3/zz_generated.conversion.go b/exp/api/v1alpha3/zz_generated.conversion.go index b4de3b2342f6..829810de806a 100644 --- a/exp/api/v1alpha3/zz_generated.conversion.go +++ b/exp/api/v1alpha3/zz_generated.conversion.go @@ -763,6 +763,7 @@ func autoConvert_v1beta1_AzureManagedControlPlaneSpec_To_v1alpha3_AzureManagedCo out.LoadBalancerSKU = (*string)(unsafe.Pointer(in.LoadBalancerSKU)) // WARNING: in.IdentityRef requires manual conversion: does not exist in peer-type out.AADProfile = (*AADProfile)(unsafe.Pointer(in.AADProfile)) + // WARNING: in.AddonProfiles requires manual conversion: does not exist in peer-type // WARNING: in.SKU requires manual conversion: does not exist in peer-type // WARNING: in.LoadBalancerProfile requires manual conversion: does not exist in peer-type // WARNING: in.APIServerAccessProfile requires manual conversion: does not exist in peer-type diff --git a/exp/api/v1alpha4/azuremanagedcontrolplane_conversion.go b/exp/api/v1alpha4/azuremanagedcontrolplane_conversion.go index 2daf630c6051..2f891523b3ba 100644 --- a/exp/api/v1alpha4/azuremanagedcontrolplane_conversion.go +++ b/exp/api/v1alpha4/azuremanagedcontrolplane_conversion.go @@ -36,6 +36,7 @@ func (src *AzureManagedControlPlane) ConvertTo(dstRaw conversion.Hub) error { return err } + dst.Spec.AddonProfiles = restored.Spec.AddonProfiles dst.Status.Conditions = restored.Status.Conditions return nil @@ -52,6 +53,11 @@ func (dst *AzureManagedControlPlane) ConvertFrom(srcRaw conversion.Hub) error { return utilconversion.MarshalData(src, dst) } +// Convert_v1beta1_AzureManagedControlPlaneSpec_To_v1alpha4_AzureManagedControlPlaneSpec is an autogenerated conversion function. +func Convert_v1beta1_AzureManagedControlPlaneSpec_To_v1alpha4_AzureManagedControlPlaneSpec(in *expv1beta1.AzureManagedControlPlaneSpec, out *AzureManagedControlPlaneSpec, s apiconversion.Scope) error { + return autoConvert_v1beta1_AzureManagedControlPlaneSpec_To_v1alpha4_AzureManagedControlPlaneSpec(in, out, s) +} + // Convert_v1beta1_AzureManagedControlPlaneStatus_To_v1alpha4_AzureManagedControlPlaneStatus is an autogenerated conversion function. func Convert_v1beta1_AzureManagedControlPlaneStatus_To_v1alpha4_AzureManagedControlPlaneStatus(in *expv1beta1.AzureManagedControlPlaneStatus, out *AzureManagedControlPlaneStatus, s apiconversion.Scope) error { return autoConvert_v1beta1_AzureManagedControlPlaneStatus_To_v1alpha4_AzureManagedControlPlaneStatus(in, out, s) diff --git a/exp/api/v1alpha4/zz_generated.conversion.go b/exp/api/v1alpha4/zz_generated.conversion.go index 22621ab865fb..fe340c35bc71 100644 --- a/exp/api/v1alpha4/zz_generated.conversion.go +++ b/exp/api/v1alpha4/zz_generated.conversion.go @@ -239,11 +239,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.AzureManagedControlPlaneSpec)(nil), (*AzureManagedControlPlaneSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_AzureManagedControlPlaneSpec_To_v1alpha4_AzureManagedControlPlaneSpec(a.(*v1beta1.AzureManagedControlPlaneSpec), b.(*AzureManagedControlPlaneSpec), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*AzureManagedControlPlaneStatus)(nil), (*v1beta1.AzureManagedControlPlaneStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha4_AzureManagedControlPlaneStatus_To_v1beta1_AzureManagedControlPlaneStatus(a.(*AzureManagedControlPlaneStatus), b.(*v1beta1.AzureManagedControlPlaneStatus), scope) }); err != nil { @@ -349,6 +344,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.AzureManagedControlPlaneSpec)(nil), (*AzureManagedControlPlaneSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_AzureManagedControlPlaneSpec_To_v1alpha4_AzureManagedControlPlaneSpec(a.(*v1beta1.AzureManagedControlPlaneSpec), b.(*AzureManagedControlPlaneSpec), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta1.AzureManagedControlPlaneStatus)(nil), (*AzureManagedControlPlaneStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_AzureManagedControlPlaneStatus_To_v1alpha4_AzureManagedControlPlaneStatus(a.(*v1beta1.AzureManagedControlPlaneStatus), b.(*AzureManagedControlPlaneStatus), scope) }); err != nil { @@ -1066,17 +1066,13 @@ func autoConvert_v1beta1_AzureManagedControlPlaneSpec_To_v1alpha4_AzureManagedCo out.LoadBalancerSKU = (*string)(unsafe.Pointer(in.LoadBalancerSKU)) out.IdentityRef = (*v1.ObjectReference)(unsafe.Pointer(in.IdentityRef)) out.AADProfile = (*AADProfile)(unsafe.Pointer(in.AADProfile)) + // WARNING: in.AddonProfiles requires manual conversion: does not exist in peer-type out.SKU = (*SKU)(unsafe.Pointer(in.SKU)) out.LoadBalancerProfile = (*LoadBalancerProfile)(unsafe.Pointer(in.LoadBalancerProfile)) out.APIServerAccessProfile = (*APIServerAccessProfile)(unsafe.Pointer(in.APIServerAccessProfile)) return nil } -// Convert_v1beta1_AzureManagedControlPlaneSpec_To_v1alpha4_AzureManagedControlPlaneSpec is an autogenerated conversion function. -func Convert_v1beta1_AzureManagedControlPlaneSpec_To_v1alpha4_AzureManagedControlPlaneSpec(in *v1beta1.AzureManagedControlPlaneSpec, out *AzureManagedControlPlaneSpec, s conversion.Scope) error { - return autoConvert_v1beta1_AzureManagedControlPlaneSpec_To_v1alpha4_AzureManagedControlPlaneSpec(in, out, s) -} - func autoConvert_v1alpha4_AzureManagedControlPlaneStatus_To_v1beta1_AzureManagedControlPlaneStatus(in *AzureManagedControlPlaneStatus, out *v1beta1.AzureManagedControlPlaneStatus, s conversion.Scope) error { out.Ready = in.Ready out.Initialized = in.Initialized diff --git a/exp/api/v1beta1/azuremanagedcontrolplane_types.go b/exp/api/v1beta1/azuremanagedcontrolplane_types.go index 221a579f8a43..67d442c92ddc 100644 --- a/exp/api/v1beta1/azuremanagedcontrolplane_types.go +++ b/exp/api/v1beta1/azuremanagedcontrolplane_types.go @@ -97,6 +97,10 @@ type AzureManagedControlPlaneSpec struct { // +optional AADProfile *AADProfile `json:"aadProfile,omitempty"` + // AddonProfiles are the profiles of managed cluster add-on. + // +optional + AddonProfiles []AddonProfile `json:"addonProfiles,omitempty"` + // SKU is the SKU of the AKS to be provisioned. // +optional SKU *SKU `json:"sku,omitempty"` @@ -121,6 +125,18 @@ type AADProfile struct { AdminGroupObjectIDs []string `json:"adminGroupObjectIDs"` } +type AddonProfile struct { + // Name- The name of managed cluster add-on. + Name string `json:"name"` + + // Config - Key-value pairs for configuring an add-on. + // +optional + Config map[string]string `json:"config,omitempty"` + + // Enabled - Whether the add-on is enabled or not. + Enabled bool `json:"enabled"` +} + // AzureManagedControlPlaneSkuTier - Tier of a managed cluster SKU. // +kubebuilder:validation:Enum=Free;Paid type AzureManagedControlPlaneSkuTier string diff --git a/exp/api/v1beta1/zz_generated.deepcopy.go b/exp/api/v1beta1/zz_generated.deepcopy.go index 801b358f4645..ce460bfc9a6e 100644 --- a/exp/api/v1beta1/zz_generated.deepcopy.go +++ b/exp/api/v1beta1/zz_generated.deepcopy.go @@ -86,6 +86,28 @@ func (in *APIServerAccessProfile) DeepCopy() *APIServerAccessProfile { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AddonProfile) DeepCopyInto(out *AddonProfile) { + *out = *in + if in.Config != nil { + in, out := &in.Config, &out.Config + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonProfile. +func (in *AddonProfile) DeepCopy() *AddonProfile { + if in == nil { + return nil + } + out := new(AddonProfile) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AzureMachinePool) DeepCopyInto(out *AzureMachinePool) { *out = *in @@ -642,6 +664,13 @@ func (in *AzureManagedControlPlaneSpec) DeepCopyInto(out *AzureManagedControlPla *out = new(AADProfile) (*in).DeepCopyInto(*out) } + if in.AddonProfiles != nil { + in, out := &in.AddonProfiles, &out.AddonProfiles + *out = make([]AddonProfile, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.SKU != nil { in, out := &in.SKU, &out.SKU *out = new(SKU) diff --git a/templates/cluster-template-aks-multi-tenancy.yaml b/templates/cluster-template-aks-multi-tenancy.yaml index c24c9341e681..6b4dce3c1ca0 100644 --- a/templates/cluster-template-aks-multi-tenancy.yaml +++ b/templates/cluster-template-aks-multi-tenancy.yaml @@ -23,6 +23,9 @@ metadata: name: ${CLUSTER_NAME} namespace: default spec: + addonProfiles: + - enabled: true + name: azurepolicy identityRef: apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 kind: AzureClusterIdentity diff --git a/templates/flavors/aks-multi-tenancy/cluster-template.yaml b/templates/flavors/aks-multi-tenancy/cluster-template.yaml index 218c72abacd5..6427ab3b24be 100644 --- a/templates/flavors/aks-multi-tenancy/cluster-template.yaml +++ b/templates/flavors/aks-multi-tenancy/cluster-template.yaml @@ -33,6 +33,9 @@ spec: location: "${AZURE_LOCATION}" sshPublicKey: ${AZURE_SSH_PUBLIC_KEY_B64:=""} version: "${KUBERNETES_VERSION}" + addonProfiles: + - name: "azurepolicy" + enabled: true --- # Due to the nature of managed Kubernetes and the control plane implementation, # the infrastructure provider for AKS cluster is basically a no-op. diff --git a/templates/test/ci/cluster-template-prow-aks-multi-tenancy.yaml b/templates/test/ci/cluster-template-prow-aks-multi-tenancy.yaml index 40c669d317b3..faa132b58245 100644 --- a/templates/test/ci/cluster-template-prow-aks-multi-tenancy.yaml +++ b/templates/test/ci/cluster-template-prow-aks-multi-tenancy.yaml @@ -27,6 +27,9 @@ spec: buildProvenance: ${BUILD_PROVENANCE} creationTimestamp: ${TIMESTAMP} jobName: ${JOB_NAME} + addonProfiles: + - enabled: true + name: azurepolicy identityRef: apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 kind: AzureClusterIdentity