Skip to content

Commit

Permalink
allow cluster class compatible changes
Browse files Browse the repository at this point in the history
  • Loading branch information
fabriziopandini committed Sep 7, 2021
1 parent 2798730 commit 3e2807a
Show file tree
Hide file tree
Showing 3 changed files with 349 additions and 62 deletions.
158 changes: 118 additions & 40 deletions api/v1alpha4/clusterclass_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package v1alpha4

import (
"fmt"
"reflect"
"strings"

corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -115,15 +114,15 @@ func (in *ClusterClass) validate(old *ClusterClass) error {
func (in ClusterClass) validateAllRefs() field.ErrorList {
var allErrs field.ErrorList

allErrs = append(allErrs, in.Spec.Infrastructure.validate(in.Namespace, field.NewPath("spec", "infrastructure"))...)
allErrs = append(allErrs, in.Spec.ControlPlane.LocalObjectTemplate.validate(in.Namespace, field.NewPath("spec", "controlPlane"))...)
allErrs = append(allErrs, in.Spec.Infrastructure.isValid(in.Namespace, field.NewPath("spec", "infrastructure"))...)
allErrs = append(allErrs, in.Spec.ControlPlane.LocalObjectTemplate.isValid(in.Namespace, field.NewPath("spec", "controlPlane"))...)
if in.Spec.ControlPlane.MachineInfrastructure != nil {
allErrs = append(allErrs, in.Spec.ControlPlane.MachineInfrastructure.validate(in.Namespace, field.NewPath("spec", "controlPlane", "machineInfrastructure"))...)
allErrs = append(allErrs, in.Spec.ControlPlane.MachineInfrastructure.isValid(in.Namespace, field.NewPath("spec", "controlPlane", "machineInfrastructure"))...)
}

for i, class := range in.Spec.Workers.MachineDeployments {
allErrs = append(allErrs, class.Template.Bootstrap.validate(in.Namespace, field.NewPath("spec", "workers", fmt.Sprintf("machineDeployments[%v]", i), "template", "bootstrap"))...)
allErrs = append(allErrs, class.Template.Infrastructure.validate(in.Namespace, field.NewPath("spec", "workers", fmt.Sprintf("machineDeployments[%v]", i), "template", "infrastructure"))...)
allErrs = append(allErrs, class.Template.Bootstrap.isValid(in.Namespace, field.NewPath("spec", "workers", fmt.Sprintf("machineDeployments[%v]", i), "template", "bootstrap"))...)
allErrs = append(allErrs, class.Template.Infrastructure.isValid(in.Namespace, field.NewPath("spec", "workers", fmt.Sprintf("machineDeployments[%v]", i), "template", "infrastructure"))...)
}

return allErrs
Expand All @@ -138,33 +137,32 @@ func (in ClusterClass) validateCompatibleSpecChanges(old *ClusterClass) field.Er
return nil
}

// Ensure that the old MachineDeployments still exist.
allErrs = append(allErrs, in.validateMachineDeploymentsChanges(old)...)

if !reflect.DeepEqual(in.Spec.Infrastructure, old.Spec.Infrastructure) {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("spec", "infrastructure"),
in.Spec.Infrastructure,
"cannot be changed.",
),
)
}

if !reflect.DeepEqual(in.Spec.ControlPlane, old.Spec.ControlPlane) {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("spec", "controlPlane"),
in.Spec.Infrastructure,
"cannot be changed.",
),
)
// Validate changes to MachineDeployments.
allErrs = append(allErrs, in.validateMachineDeploymentsCompatibleChanges(old)...)

// Validate InfrastructureClusterTemplate changes in a compatible way.
allErrs = append(allErrs, in.Spec.Infrastructure.isCompatibleWith(
old.Spec.Infrastructure,
field.NewPath("spec", "infrastructure"),
)...)

// Validate control plane changes in a compatible way.
allErrs = append(allErrs, in.Spec.ControlPlane.LocalObjectTemplate.isCompatibleWith(
old.Spec.ControlPlane.LocalObjectTemplate,
field.NewPath("spec", "controlPlane"),
)...)

if in.Spec.ControlPlane.MachineInfrastructure != nil && old.Spec.ControlPlane.MachineInfrastructure != nil {
allErrs = append(allErrs, in.Spec.ControlPlane.MachineInfrastructure.isCompatibleWith(
*old.Spec.ControlPlane.MachineInfrastructure,
field.NewPath("spec", "controlPlane", "machineInfrastructure"),
)...)
}

return allErrs
}

func (in ClusterClass) validateMachineDeploymentsChanges(old *ClusterClass) field.ErrorList {
func (in ClusterClass) validateMachineDeploymentsCompatibleChanges(old *ClusterClass) field.ErrorList {
var allErrs field.ErrorList

// Ensure no MachineDeployment class was removed.
Expand All @@ -181,25 +179,26 @@ func (in ClusterClass) validateMachineDeploymentsChanges(old *ClusterClass) fiel
}
}

// Ensure no previous MachineDeployment class was modified.
for _, class := range in.Spec.Workers.MachineDeployments {
// Ensure previous MachineDeployment class was modified in a compatible way.
for i, class := range in.Spec.Workers.MachineDeployments {
for _, oldClass := range old.Spec.Workers.MachineDeployments {
if class.Class == oldClass.Class && !reflect.DeepEqual(class, oldClass) {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("spec", "workers", "machineDeployments"),
class,
"cannot be changed.",
),
)
if class.Class == oldClass.Class {
// NOTE: class.Template.Metadata and class.Template.Bootstrap are allowed to change;
// class.Template.Bootstrap are ensured syntactically correct by validateAllRefs.

// Validates class.Template.Infrastructure template changes in a compatible way
allErrs = append(allErrs, class.Template.Infrastructure.isCompatibleWith(
oldClass.Template.Infrastructure,
field.NewPath("spec", "workers", "machineDeployments").Index(i).Child("template", "infrastructure"),
)...)
}
}
}

return allErrs
}

func (r LocalObjectTemplate) validate(namespace string, pathPrefix *field.Path) field.ErrorList {
func (r LocalObjectTemplate) isValid(namespace string, pathPrefix *field.Path) field.ErrorList {
var allErrs field.ErrorList

// check if ref is not nil.
Expand Down Expand Up @@ -261,7 +260,86 @@ func (r LocalObjectTemplate) validate(namespace string, pathPrefix *field.Path)
field.Invalid(
pathPrefix.Child("ref", "apiVersion"),
r.Ref.APIVersion,
"cannot be empty",
"value cannot be empty",
),
)
}

return allErrs
}

func (r LocalObjectTemplate) isCompatibleWith(old LocalObjectTemplate, pathPrefix *field.Path) field.ErrorList {
var allErrs field.ErrorList

// NOTE: this func assumes that r.validate is called before, thus both ref are defined and syntactically valid;
// also namespace are enforced to be the same of the ClusterClass. We are just checking for nil Ref here to
// avoid panic.

if r.Ref == nil {
// NOTE this should never happen.
allErrs = append(allErrs,
field.Invalid(
pathPrefix.Child("ref"),
r.Ref,
"value can't be nil",
),
)
return allErrs
}
if old.Ref == nil {
// NOTE this should never happen.
allErrs = append(allErrs,
field.Invalid(
pathPrefix.Child("ref"),
old.Ref,
"value can't be nil",
),
)
return allErrs
}

// Ensure that the API Group and Kind does not change, while instead we allow version to change.

gv, err := schema.ParseGroupVersion(r.Ref.APIVersion)
if err != nil {
// NOTE this should never happen.
allErrs = append(allErrs,
field.Invalid(
pathPrefix.Child("ref", "apiVersion"),
r.Ref.APIVersion,
fmt.Sprintf("must be a valid apiVersion: %v", err),
),
)
}

oldGV, err := schema.ParseGroupVersion(old.Ref.APIVersion)
if err != nil {
// NOTE this should never happen.
allErrs = append(allErrs,
field.Invalid(
pathPrefix.Child("ref", "apiVersion"),
old.Ref.APIVersion,
fmt.Sprintf("must be a valid apiVersion: %v", err),
),
)
}

if gv.Group != oldGV.Group {
allErrs = append(allErrs,
field.Invalid(
pathPrefix.Child("ref", "apiVersion"),
r.Ref.APIVersion,
"group cannot be changed",
),
)
}

if r.Ref.Kind != old.Ref.Kind {
allErrs = append(allErrs,
field.Invalid(
pathPrefix.Child("ref", "kind"),
r.Ref.Kind,
"value cannot be changed",
),
)
}
Expand Down
Loading

0 comments on commit 3e2807a

Please sign in to comment.