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
22 changes: 21 additions & 1 deletion fleetconfig-controller/api/v1alpha1/fleetconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ type FleetConfigSpec struct {
// +optional
AddOnConfigs []AddOnConfig `json:"addOnConfigs,omitempty"`

// +optional
HubAddOns []HubAddOn `json:"hubAddOns,omitempty"`

// Timeout is the timeout in seconds for all clusteradm operations, including init, accept, join, upgrade, etc.
// +kubebuilder:default:=300
// +optional
Expand Down Expand Up @@ -417,7 +420,7 @@ func (s *Spoke) conditionName(c int) string {

// AddOn enables add-on installation on the cluster.
type AddOn struct {
// The name of the add-on being enabled. Must match one of the default or manually configured add-on names.
// The name of the add-on being enabled. Must match one of the AddOnConfigs or HubAddOns names.
// +required
ConfigName string `json:"configName"`

Expand Down Expand Up @@ -767,3 +770,20 @@ func (m *FleetConfig) IsUnjoined(spoke Spoke, joinedSpoke JoinedSpoke) bool {
// if both exist and are true, compare timestamps
return unjoinedC.LastTransitionTime.After(joinedC.LastTransitionTime.Time)
}

// HubAddOn is the configuration for enabling a built-in AddOn.
type HubAddOn struct {
// Name is the name of the HubAddOn.
// +kubebuilder:validation:Enum=argocd;governance-policy-framework
// +required
Name string `json:"name"`

// The namespace to install the add-on in. If left empty, installs into the "open-cluster-management-addon" namespace.
// +optional
InstallNamespace string `json:"installNamespace,omitempty"`

// Whether or not the selected namespace should be created. If left empty, defaults to false.
// +kubebuilder:default:=false
// +optional
CreateNamespace bool `json:"createNamespace,omitempty"`
}
2 changes: 2 additions & 0 deletions fleetconfig-controller/api/v1alpha1/fleetconfig_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func (v *FleetConfigCustomValidator) ValidateCreate(ctx context.Context, obj run

allErrs = append(allErrs, validateAddonConfigs(ctx, v.client, nil, fc)...)
allErrs = append(allErrs, validateAddons(fc)...)
allErrs = append(allErrs, validateHubAddons(ctx, nil, fc)...)
if len(allErrs) > 0 {
return warnings, errors.NewInvalid(GroupKind, fc.Name, allErrs)
}
Expand Down Expand Up @@ -168,6 +169,7 @@ func (v *FleetConfigCustomValidator) ValidateUpdate(ctx context.Context, oldObj,

errs := validateAddonConfigs(ctx, v.client, oldFc, fc)
errs = append(errs, validateAddons(fc)...)
errs = append(errs, validateHubAddons(ctx, oldFc, fc)...)
if len(errs) > 0 {
return nil, errors.NewInvalid(GroupKind, fc.Name, errs)
}
Expand Down
84 changes: 75 additions & 9 deletions fleetconfig-controller/api/v1alpha1/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,18 @@ func validateAddonConfigs(ctx context.Context, client client.Client, oldObject,
mcAddOns, err := getManagedClusterAddOns(ctx)
if err != nil {
errs = append(errs, field.InternalError(field.NewPath("addOnConfigs"), err))
} else {
// Check if any removed addon configs are still in use
for _, removedConfig := range removedAddOnConfigs {
if isAddonConfigInUse(mcAddOns, removedConfig) {
errs = append(errs, field.Invalid(field.NewPath("addOnConfigs"), removedConfig,
fmt.Sprintf("cannot remove addon config %s as it is still in use by managedclusteraddons", removedConfig)))
}
return errs
}
var inUseAddOnConfigs []string
for _, removedConfig := range removedAddOnConfigs {
if isAddondEnabled(mcAddOns, removedConfig) {
inUseAddOnConfigs = append(inUseAddOnConfigs, removedConfig)
}
}
if len(inUseAddOnConfigs) > 0 {
errs = append(errs, field.Invalid(field.NewPath("addOnConfigs"), inUseAddOnConfigs,
fmt.Sprintf("cannot remove addOnConfigs %v as they are still in use by managedclusteraddons", inUseAddOnConfigs)))
}
}
}

Expand All @@ -163,6 +166,9 @@ func validateAddons(newObject *FleetConfig) field.ErrorList {
for _, ca := range newObject.Spec.AddOnConfigs {
configuredAddons[ca.Name] = true
}
for _, ha := range newObject.Spec.HubAddOns {
configuredAddons[ha.Name] = true
}
for i, s := range newObject.Spec.Spokes {
for j, a := range s.AddOns {
if !configuredAddons[a.ConfigName] {
Expand All @@ -174,6 +180,66 @@ func validateAddons(newObject *FleetConfig) field.ErrorList {
return errs
}

// validateHubAddons validates HubAddOn configurations and usage
func validateHubAddons(ctx context.Context, oldObject, newObject *FleetConfig) field.ErrorList {
errs := field.ErrorList{}

// Check for name clashes between HubAddOns and AddOnConfigs
addOnConfigNames := make(map[string]struct{})
for _, ac := range newObject.Spec.AddOnConfigs {
addOnConfigNames[ac.Name] = struct{}{}
}

for i, ha := range newObject.Spec.HubAddOns {
if _, found := addOnConfigNames[ha.Name]; found {
errs = append(errs, field.Invalid(field.NewPath("hubAddOn").Index(i), ha.Name,
fmt.Sprintf("hubAddOn name %s clashes with an existing addOnConfig name", ha.Name)))
}
}

// Check if any removed hub addons are still in use by managed cluster addons
if oldObject != nil {
oldHubAddOns := make(map[string]struct{})
for _, ha := range oldObject.Spec.HubAddOns {
oldHubAddOns[ha.Name] = struct{}{}
}

newHubAddOns := make(map[string]struct{})
for _, ha := range newObject.Spec.HubAddOns {
newHubAddOns[ha.Name] = struct{}{}
}

removedHubAddOns := make([]string, 0)
for name := range oldHubAddOns {
if _, found := newHubAddOns[name]; !found {
removedHubAddOns = append(removedHubAddOns, name)
}
}

// Check if any removed hub addons are still in use by managed cluster addons
if len(removedHubAddOns) > 0 {
mcAddOns, err := getManagedClusterAddOns(ctx)
if err != nil {
errs = append(errs, field.InternalError(field.NewPath("hubAddOn"), err))
return errs
}
var inUseHubAddOns []string
for _, removedHubAddOn := range removedHubAddOns {
if isAddondEnabled(mcAddOns, removedHubAddOn) {
inUseHubAddOns = append(inUseHubAddOns, removedHubAddOn)
}
}
if len(inUseHubAddOns) > 0 {
errs = append(errs, field.Invalid(field.NewPath("hubAddOn"), inUseHubAddOns,
fmt.Sprintf("cannot remove hubAddOns %v as they are still in use by managedclusteraddons", inUseHubAddOns)))
}

}
}

return errs
}

// getManagedClusterAddOns lists all ManagedClusterAddOns in all namespaces.
func getManagedClusterAddOns(ctx context.Context) ([]addonv1alpha1.ManagedClusterAddOn, error) {
restConfig, err := ctrl.GetConfig()
Expand All @@ -192,10 +258,10 @@ func getManagedClusterAddOns(ctx context.Context) ([]addonv1alpha1.ManagedCluste
}

// isAddonConfigInUse checks if a removed addon config is still referenced by any ManagedClusterAddOn.
func isAddonConfigInUse(mcAddOns []addonv1alpha1.ManagedClusterAddOn, removedConfig string) bool {
func isAddondEnabled(mcAddOns []addonv1alpha1.ManagedClusterAddOn, removedAddon string) bool {
for _, mcao := range mcAddOns {
for _, cr := range mcao.Status.ConfigReferences {
if cr.DesiredConfig.Name == removedConfig {
if cr.DesiredConfig.Name == removedAddon {
return true
}
}
Expand Down
20 changes: 20 additions & 0 deletions fleetconfig-controller/api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,30 @@ spec:
required:
- kubeconfig
type: object
hubAddOns:
items:
description: HubAddOn is the configuration for enabling a built-in
AddOn.
properties:
createNamespace:
default: false
description: Whether or not the selected namespace should be
created. If left empty, defaults to false.
type: boolean
installNamespace:
description: The namespace to install the add-on in. If left
empty, installs into the "open-cluster-management-addon" namespace.
type: string
name:
description: Name is the name of the HubAddOn.
enum:
- argocd
- governance-policy-framework
type: string
required:
- name
type: object
type: array
logVerbosity:
default: 0
description: LogVerbosity is the verbosity of the logs.
Expand Down Expand Up @@ -336,8 +360,7 @@ spec:
type: object
configName:
description: The name of the add-on being enabled. Must
match one of the default or manually configured add-on
names.
match one of the AddOnConfigs or HubAddOns names.
type: string
installNamespace:
description: The namespace to install the add-on in. If
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,30 @@ spec:
required:
- kubeconfig
type: object
hubAddOns:
items:
description: HubAddOn is the configuration for enabling a built-in
AddOn.
properties:
createNamespace:
default: false
description: Whether or not the selected namespace should be
created. If left empty, defaults to false.
type: boolean
installNamespace:
description: The namespace to install the add-on in. If left
empty, installs into the "open-cluster-management-addon" namespace.
type: string
name:
description: Name is the name of the HubAddOn.
enum:
- argocd
- governance-policy-framework
type: string
required:
- name
type: object
type: array
logVerbosity:
default: 0
description: LogVerbosity is the verbosity of the logs.
Expand Down Expand Up @@ -326,8 +350,7 @@ spec:
type: object
configName:
description: The name of the add-on being enabled. Must
match one of the default or manually configured add-on
names.
match one of the AddOnConfigs or HubAddOns names.
type: string
installNamespace:
description: The namespace to install the add-on in. If
Expand Down
Loading