Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for AKS Extensions & Marketplace Offers #4360

Merged
merged 1 commit into from
Jan 24, 2024
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].
willie-yao marked this conversation as resolved.
Show resolved Hide 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 @@
m.setDefaultSubnet()
m.setDefaultOIDCIssuerProfile()
m.setDefaultDNSPrefix()
m.setDefaultAKSExtensions()

return nil
}
Expand Down Expand Up @@ -265,6 +266,10 @@
allErrs = append(allErrs, errs...)
}

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

Check warning on line 271 in api/v1beta1/azuremanagedcontrolplane_webhook.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L270-L271

Added lines #L270 - L271 were not covered by tests

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

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 @@
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",
),
)
}

Check warning on line 744 in api/v1beta1/azuremanagedcontrolplane_webhook.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L737-L744

Added lines #L737 - L744 were not covered by tests
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",
),
)
}

Check warning on line 771 in api/v1beta1/azuremanagedcontrolplane_webhook.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L764-L771

Added lines #L764 - L771 were not covered by tests
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",
),
)
}

Check warning on line 780 in api/v1beta1/azuremanagedcontrolplane_webhook.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L773-L780

Added lines #L773 - L780 were not covered by tests
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",
),
)
}

Check warning on line 789 in api/v1beta1/azuremanagedcontrolplane_webhook.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L782-L789

Added lines #L782 - L789 were not covered by tests
if extension.Identity != oldExtension.Identity {
willie-yao marked this conversation as resolved.
Show resolved Hide resolved
allErrs = append(allErrs,
field.Invalid(
field.NewPath("Spec", "Extensions", fmt.Sprintf("[%d]", i), "Identity"),
extension.Identity,
"field is immutable",
),
)
}

Check warning on line 798 in api/v1beta1/azuremanagedcontrolplane_webhook.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L791-L798

Added lines #L791 - L798 were not covered by tests
}

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 @@
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"))
}

Check warning on line 830 in api/v1beta1/azuremanagedcontrolplane_webhook.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L829-L830

Added lines #L829 - L830 were not covered by tests
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"))
}

Check warning on line 838 in api/v1beta1/azuremanagedcontrolplane_webhook.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L833-L838

Added lines #L833 - L838 were not covered by tests
} 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
Loading