Skip to content

Commit

Permalink
Add support for AKS Marketplace extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
willie-yao committed Jan 18, 2024
1 parent 3ed87c4 commit c942ecf
Show file tree
Hide file tree
Showing 27 changed files with 2,360 additions and 1 deletion.
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
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 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 @@ -198,3 +198,14 @@ func (m *AzureManagedControlPlane) setDefaultDNSPrefix() {
m.Spec.DNSPrefix = ptr.To(m.Name)
}
}

func (m *AzureManagedControlPlane) setDefaultMarketplaceExtensions() {
for _, extension := range m.Spec.MarketplaceExtensions {
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 @@ -421,6 +421,52 @@ type OIDCIssuerProfile struct {
Enabled *bool `json:"enabled,omitempty"`
}

// MarketplaceExtension represents the configuration for a marketplace extension.
// See also [AKS doc].
//
// [AKS doc]: https://learn.microsoft.com/en-us/azure/aks/cluster-extensions
type MarketplaceExtension 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 *MarketplacePlan `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 @@ -94,6 +94,7 @@ func (mw *azureManagedControlPlaneWebhook) Default(ctx context.Context, obj runt
m.setDefaultSubnet()
m.setDefaultOIDCIssuerProfile()
m.setDefaultDNSPrefix()
m.setDefaultMarketplaceExtensions()

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

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

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

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L265-L266

Added lines #L265 - L266 were not covered by tests

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

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

allErrs = append(allErrs, validateMarketplaceExtensions(m.Spec.MarketplaceExtensions, field.NewPath("spec").Child("MarketplaceExtensions"))...)

return allErrs.ToAggregate()
}

Expand Down Expand Up @@ -687,6 +694,89 @@ func (m *AzureManagedControlPlane) validateOIDCIssuerProfileUpdate(old *AzureMan
return allErrs
}

// validateMarketplaceExtensionsUpdate validates update to Marketplace extensions.
func validateMarketplaceExtensionsUpdate(old []MarketplaceExtension, current []MarketplaceExtension) field.ErrorList {
var allErrs field.ErrorList

oldMarketplaceExtensionsMap := make(map[string]MarketplaceExtension, len(old))
oldMarketplaceExtensionsIndex := make(map[string]int, len(old))
for i, extension := range old {
oldMarketplaceExtensionsMap[extension.Name] = extension
oldMarketplaceExtensionsIndex[extension.Name] = i
}
for i, extension := range current {
oldExtension, ok := oldMarketplaceExtensionsMap[extension.Name]
if !ok {
continue
}
if extension.Name != oldExtension.Name {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("Spec", "MarketplaceExtensions", fmt.Sprintf("[%d]", i), "Name"),
extension.Name,
"field is immutable",
),
)
}

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

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L713-L720

Added lines #L713 - L720 were not covered by tests
if (oldExtension.ExtensionType != nil && extension.ExtensionType != nil) && *extension.ExtensionType != *oldExtension.ExtensionType {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("Spec", "MarketplaceExtensions", 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", "MarketplaceExtensions", fmt.Sprintf("[%d]", i), "Plan"),
extension.Plan,
"field is immutable",
),
)
}
if extension.Scope != oldExtension.Scope {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("Spec", "MarketplaceExtensions", fmt.Sprintf("[%d]", i), "Scope"),
extension.Scope,
"field is immutable",
),
)
}

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

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L740-L747

Added lines #L740 - L747 were not covered by tests
if (extension.ReleaseTrain != nil && oldExtension.ReleaseTrain != nil) && *extension.ReleaseTrain != *oldExtension.ReleaseTrain {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("Spec", "MarketplaceExtensions", fmt.Sprintf("[%d]", i), "ReleaseTrain"),
extension.ReleaseTrain,
"field is immutable",
),
)
}

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

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L749-L756

Added lines #L749 - L756 were not covered by tests
if (extension.Version != nil && oldExtension.Version != nil) && *extension.Version != *oldExtension.Version {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("Spec", "MarketplaceExtensions", fmt.Sprintf("[%d]", i), "Version"),
extension.Version,
"field is immutable",
),
)
}

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

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L758-L765

Added lines #L758 - L765 were not covered by tests
if extension.Identity != oldExtension.Identity {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("Spec", "MarketplaceExtensions", fmt.Sprintf("[%d]", i), "Identity"),
extension.Identity,
"field is immutable",
),
)
}

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

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L767-L774

Added lines #L767 - L774 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 @@ -698,6 +788,44 @@ func validateName(name string, fldPath *field.Path) field.ErrorList {
return allErrs
}

// validateMarketplaceExtensions validates the Marketplace extensions.
func validateMarketplaceExtensions(extensions []MarketplaceExtension, 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 806 in api/v1beta1/azuremanagedcontrolplane_webhook.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L805-L806

Added lines #L805 - L806 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 814 in api/v1beta1/azuremanagedcontrolplane_webhook.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L809-L814

Added lines #L809 - L814 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

0 comments on commit c942ecf

Please sign in to comment.