Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ WEBHOOK_ROOT ?= $(MANIFEST_ROOT)/webhook
RBAC_ROOT ?= $(MANIFEST_ROOT)/rbac
ASO_CRDS_PATH := $(MANIFEST_ROOT)/aso/crds.yaml
ASO_VERSION := v2.5.0
ASO_CRDS := resourcegroups.resources.azure.com natgateways.network.azure.com managedclusters.containerservice.azure.com managedclustersagentpools.containerservice.azure.com bastionhosts.network.azure.com virtualnetworks.network.azure.com virtualnetworkssubnets.network.azure.com privateendpoints.network.azure.com fleetsmembers.containerservice.azure.com
ASO_CRDS := resourcegroups.resources.azure.com natgateways.network.azure.com managedclusters.containerservice.azure.com managedclustersagentpools.containerservice.azure.com bastionhosts.network.azure.com virtualnetworks.network.azure.com virtualnetworkssubnets.network.azure.com privateendpoints.network.azure.com fleetsmembers.containerservice.azure.com extensions.kubernetesconfiguration.azure.com

# Allow overriding the imagePullPolicy
PULL_POLICY ?= Always
Expand Down
11 changes: 11 additions & 0 deletions api/v1beta1/azuremanagedcontrolplane_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,14 @@ func (m *AzureManagedControlPlane) setDefaultDNSPrefix() {
m.Spec.DNSPrefix = ptr.To(m.Name)
}
}

func (m *AzureManagedControlPlane) setDefaultAKSExtensions() {
for _, extension := range m.Spec.Extensions {
if extension.Plan.Name == "" {
extension.Plan.Name = fmt.Sprintf("%s-%s", m.Name, extension.Plan.Product)
}
if extension.AutoUpgradeMinorVersion == nil {
extension.AutoUpgradeMinorVersion = ptr.To(true)
}
}
}
46 changes: 46 additions & 0 deletions api/v1beta1/azuremanagedcontrolplane_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,52 @@ type OIDCIssuerProfile struct {
Enabled *bool `json:"enabled,omitempty"`
}

// AKSExtension represents the configuration for an AKS cluster extension.
// See also [AKS doc].
Comment thread
willie-yao marked this conversation as resolved.
//
// [AKS doc]: https://learn.microsoft.com/en-us/azure/aks/cluster-extensions
type AKSExtension struct {
// Name is the name of the extension.
Name string `json:"name"`

// AKSAssignedIdentityType is the type of the AKS assigned identity.
// +optional
AKSAssignedIdentityType AKSAssignedIdentity `json:"aksAssignedIdentityType,omitempty"`

// AutoUpgradeMinorVersion is a flag to note if this extension participates in auto upgrade of minor version, or not.
// +kubebuilder:default=true
// +optional
AutoUpgradeMinorVersion *bool `json:"autoUpgradeMinorVersion,omitempty"`

// ConfigurationSettings are the name-value pairs for configuring this extension.
// +optional
ConfigurationSettings map[string]string `json:"configurationSettings,omitempty"`

// ExtensionType is the type of the Extension of which this resource is an instance.
// It must be one of the Extension Types registered with Microsoft.KubernetesConfiguration by the Extension publisher.
ExtensionType *string `json:"extensionType"`

// Plan is the plan of the extension.
Plan *ExtensionPlan `json:"plan"`

// ReleaseTrain is the release train this extension participates in for auto-upgrade (e.g. Stable, Preview, etc.)
// This is only used if autoUpgradeMinorVersion is ‘true’.
// +optional
ReleaseTrain *string `json:"releaseTrain,omitempty"`

// Scope is the scope at which this extension is enabled.
// +optional
Scope *ExtensionScope `json:"scope,omitempty"`

// Version is the version of the extension.
// +optional
Version *string `json:"version,omitempty"`

// Identity is the identity type of the Extension resource in an AKS cluster.
// +optional
Identity ExtensionIdentity `json:"identity,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:resource:path=azuremanagedcontrolplanes,scope=Namespaced,categories=cluster-api,shortName=amcp
// +kubebuilder:storageversion
Expand Down
128 changes: 128 additions & 0 deletions api/v1beta1/azuremanagedcontrolplane_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func (mw *azureManagedControlPlaneWebhook) Default(ctx context.Context, obj runt
m.setDefaultSubnet()
m.setDefaultOIDCIssuerProfile()
m.setDefaultDNSPrefix()
m.setDefaultAKSExtensions()

return nil
}
Expand Down Expand Up @@ -265,6 +266,10 @@ func (mw *azureManagedControlPlaneWebhook) ValidateUpdate(ctx context.Context, o
allErrs = append(allErrs, errs...)
}

if errs := validateAKSExtensionsUpdate(old.Spec.Extensions, m.Spec.Extensions); len(errs) > 0 {
allErrs = append(allErrs, errs...)
}

if len(allErrs) == 0 {
return nil, m.Validate(mw.Client)
}
Expand Down Expand Up @@ -314,6 +319,8 @@ func (m *AzureManagedControlPlane) Validate(cli client.Client) error {

allErrs = append(allErrs, validateAutoScalerProfile(m.Spec.AutoScalerProfile, field.NewPath("spec").Child("AutoScalerProfile"))...)

allErrs = append(allErrs, validateAKSExtensions(m.Spec.Extensions, field.NewPath("spec").Child("AKSExtensions"))...)

return allErrs.ToAggregate()
}

Expand Down Expand Up @@ -711,6 +718,89 @@ func (m *AzureManagedControlPlane) validateFleetsMember(old *AzureManagedControl
return allErrs
}

// validateAKSExtensionsUpdate validates update to AKS extensions.
func validateAKSExtensionsUpdate(old []AKSExtension, current []AKSExtension) field.ErrorList {
var allErrs field.ErrorList

oldAKSExtensionsMap := make(map[string]AKSExtension, len(old))
oldAKSExtensionsIndex := make(map[string]int, len(old))
for i, extension := range old {
oldAKSExtensionsMap[extension.Name] = extension
oldAKSExtensionsIndex[extension.Name] = i
}
for i, extension := range current {
oldExtension, ok := oldAKSExtensionsMap[extension.Name]
if !ok {
continue
}
if extension.Name != oldExtension.Name {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("Spec", "Extensions", fmt.Sprintf("[%d]", i), "Name"),
extension.Name,
"field is immutable",
),
)
}
if (oldExtension.ExtensionType != nil && extension.ExtensionType != nil) && *extension.ExtensionType != *oldExtension.ExtensionType {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("Spec", "Extensions", fmt.Sprintf("[%d]", i), "ExtensionType"),
extension.ExtensionType,
"field is immutable",
),
)
}
if (extension.Plan != nil && oldExtension.Plan != nil) && *extension.Plan != *oldExtension.Plan {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("Spec", "Extensions", fmt.Sprintf("[%d]", i), "Plan"),
extension.Plan,
"field is immutable",
),
)
}
if extension.Scope != oldExtension.Scope {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("Spec", "Extensions", fmt.Sprintf("[%d]", i), "Scope"),
extension.Scope,
"field is immutable",
),
)
}
if (extension.ReleaseTrain != nil && oldExtension.ReleaseTrain != nil) && *extension.ReleaseTrain != *oldExtension.ReleaseTrain {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("Spec", "Extensions", fmt.Sprintf("[%d]", i), "ReleaseTrain"),
extension.ReleaseTrain,
"field is immutable",
),
)
}
if (extension.Version != nil && oldExtension.Version != nil) && *extension.Version != *oldExtension.Version {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("Spec", "Extensions", fmt.Sprintf("[%d]", i), "Version"),
extension.Version,
"field is immutable",
),
)
}
if extension.Identity != oldExtension.Identity {
Comment thread
willie-yao marked this conversation as resolved.
allErrs = append(allErrs,
field.Invalid(
field.NewPath("Spec", "Extensions", fmt.Sprintf("[%d]", i), "Identity"),
extension.Identity,
"field is immutable",
),
)
}
}

return allErrs
}

func validateName(name string, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
if lName := strings.ToLower(name); strings.Contains(lName, "microsoft") ||
Expand All @@ -722,6 +812,44 @@ func validateName(name string, fldPath *field.Path) field.ErrorList {
return allErrs
}

// validateAKSExtensions validates the AKS extensions.
func validateAKSExtensions(extensions []AKSExtension, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
for _, extension := range extensions {
if extension.Version != nil && (extension.AutoUpgradeMinorVersion == nil || (extension.AutoUpgradeMinorVersion != nil && *extension.AutoUpgradeMinorVersion)) {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("Version"), "Version must not be given if AutoUpgradeMinorVersion is true (or not provided, as it is true by default)"))
}
if extension.Plan.Product == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("Plan", "Product"), "Product must be provided"))
}
if extension.Plan.Publisher == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("Plan", "Publisher"), "Publisher must be provided"))
}
if extension.AutoUpgradeMinorVersion == ptr.To(false) && extension.ReleaseTrain != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("ReleaseTrain"), "ReleaseTrain must not be given if AutoUpgradeMinorVersion is false"))
}
if extension.Scope != nil {
if extension.Scope.ScopeType == ExtensionScopeCluster {
if extension.Scope.ReleaseNamespace == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("Scope", "ReleaseNamespace"), "ReleaseNamespace must be provided if Scope is Cluster"))
}
if extension.Scope.TargetNamespace != "" {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("Scope", "TargetNamespace"), "TargetNamespace can only be given if Scope is Namespace"))
}
} else if extension.Scope.ScopeType == ExtensionScopeNamespace {
if extension.Scope.TargetNamespace == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("Scope", "TargetNamespace"), "TargetNamespace must be provided if Scope is Namespace"))
}
if extension.Scope.ReleaseNamespace != "" {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("Scope", "ReleaseNamespace"), "ReleaseNamespace can only be given if Scope is Cluster"))
}
}
}
}

return allErrs
}

// validateAutoScalerProfile validates an AutoScalerProfile.
func validateAutoScalerProfile(autoScalerProfile *AutoScalerProfile, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
Expand Down
Loading