Skip to content

Commit 099c49d

Browse files
committed
Add validation for ClusterClass update scenario
1 parent 7c05f39 commit 099c49d

File tree

2 files changed

+126
-0
lines changed

2 files changed

+126
-0
lines changed

internal/webhooks/clusterclass.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ func (webhook *ClusterClass) validate(ctx context.Context, oldClusterClass, newC
163163
return apierrors.NewInvalid(clusterv1.GroupVersion.WithKind("ClusterClass").GroupKind(), newClusterClass.Name, allErrs)
164164
}
165165

166+
// Ensure New ClusterClass contains Kubernetes versions of all the Clusters of ClusterClass.
167+
allErrs = append(allErrs,
168+
webhook.validateKubernetesVersionsOfClusters(clusters, oldClusterClass, newClusterClass)...)
169+
166170
// Ensure no MachineDeploymentClass currently in use has been removed from the ClusterClass.
167171
allErrs = append(allErrs,
168172
webhook.validateRemovedMachineDeploymentClassesAreNotUsed(clusters, oldClusterClass, newClusterClass)...)
@@ -246,6 +250,32 @@ func validateUpdatesToMachineHealthCheckClasses(clusters []clusterv1.Cluster, ol
246250
return allErrs
247251
}
248252

253+
func (webhook *ClusterClass) validateKubernetesVersionsOfClusters(clusters []clusterv1.Cluster, _, newClusterClass *clusterv1.ClusterClass) field.ErrorList {
254+
var allErrs field.ErrorList
255+
256+
// If there is no KubernetesVersions is set in the ClusterClass return early.
257+
if len(newClusterClass.Spec.KubernetesVersions) == 0 {
258+
return allErrs
259+
}
260+
261+
kubernetesVersions := sets.Set[string]{}
262+
for _, v := range newClusterClass.Spec.KubernetesVersions {
263+
kubernetesVersions.Insert(v)
264+
}
265+
266+
// Error if any Cluster's Kubernetes version is not set in the ClusterClass.
267+
for _, c := range clusters {
268+
if !kubernetesVersions.Has(c.Spec.Topology.Version) {
269+
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "kubernetesVersions"),
270+
fmt.Sprintf("Kubernetes Version %s is used by Cluster %q but not set in ClusterClass",
271+
c.Spec.Topology.Version, c.Name),
272+
))
273+
}
274+
}
275+
276+
return allErrs
277+
}
278+
249279
func (webhook *ClusterClass) validateRemovedMachineDeploymentClassesAreNotUsed(clusters []clusterv1.Cluster, oldClusterClass, newClusterClass *clusterv1.ClusterClass) field.ErrorList {
250280
var allErrs field.ErrorList
251281

internal/webhooks/clusterclass_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2489,6 +2489,102 @@ func TestClusterClassValidationWithClusterAwareChecks(t *testing.T) {
24892489
Build(),
24902490
expectErr: false,
24912491
},
2492+
{
2493+
name: "pass if ClusterClass does not contain any kubernetes version",
2494+
clusters: []client.Object{
2495+
builder.Cluster(metav1.NamespaceDefault, "cluster1").
2496+
WithLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}).
2497+
WithTopology(
2498+
builder.ClusterTopology().
2499+
WithClass("class1").
2500+
WithVersion("v1.33.0").
2501+
Build()).
2502+
Build(),
2503+
},
2504+
oldClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
2505+
WithInfrastructureClusterTemplate(
2506+
builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "inf").Build()).
2507+
WithControlPlaneTemplate(
2508+
builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cp1").Build()).
2509+
Build(),
2510+
newClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
2511+
WithInfrastructureClusterTemplate(
2512+
builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "inf").Build()).
2513+
WithControlPlaneTemplate(
2514+
builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cp1").Build()).
2515+
Build(),
2516+
expectErr: false,
2517+
},
2518+
{
2519+
name: "pass if ClusterClass contains cluster's kubernetes version",
2520+
clusters: []client.Object{
2521+
builder.Cluster(metav1.NamespaceDefault, "cluster1").
2522+
WithLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}).
2523+
WithTopology(
2524+
builder.ClusterTopology().
2525+
WithClass("class1").
2526+
WithVersion("v1.33.0").
2527+
Build()).
2528+
Build(),
2529+
builder.Cluster(metav1.NamespaceDefault, "cluster2").
2530+
WithLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}).
2531+
WithTopology(
2532+
builder.ClusterTopology().
2533+
WithClass("class1").
2534+
WithVersion("v1.34.0").
2535+
Build()).
2536+
Build(),
2537+
},
2538+
oldClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
2539+
WithInfrastructureClusterTemplate(
2540+
builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "inf").Build()).
2541+
WithControlPlaneTemplate(
2542+
builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cp1").Build()).
2543+
Build(),
2544+
newClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
2545+
WithInfrastructureClusterTemplate(
2546+
builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "inf").Build()).
2547+
WithControlPlaneTemplate(
2548+
builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cp1").Build()).
2549+
WithVersions("v1.32.0", "v1.33.0", "v1.34.0").
2550+
Build(),
2551+
expectErr: false,
2552+
},
2553+
{
2554+
name: "fail if ClusterClass does not contains all of cluster's kubernetes version",
2555+
clusters: []client.Object{
2556+
builder.Cluster(metav1.NamespaceDefault, "cluster1").
2557+
WithLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}).
2558+
WithTopology(
2559+
builder.ClusterTopology().
2560+
WithClass("class1").
2561+
WithVersion("v1.33.0").
2562+
Build()).
2563+
Build(),
2564+
builder.Cluster(metav1.NamespaceDefault, "cluster2").
2565+
WithLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}).
2566+
WithTopology(
2567+
builder.ClusterTopology().
2568+
WithClass("class1").
2569+
WithVersion("v1.34.0").
2570+
Build()).
2571+
Build(),
2572+
},
2573+
oldClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
2574+
WithInfrastructureClusterTemplate(
2575+
builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "inf").Build()).
2576+
WithControlPlaneTemplate(
2577+
builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cp1").Build()).
2578+
Build(),
2579+
newClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
2580+
WithInfrastructureClusterTemplate(
2581+
builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "inf").Build()).
2582+
WithControlPlaneTemplate(
2583+
builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cp1").Build()).
2584+
WithVersions("v1.32.0", "v1.33.0").
2585+
Build(),
2586+
expectErr: true,
2587+
},
24922588
}
24932589

24942590
for _, tt := range tests {

0 commit comments

Comments
 (0)