@@ -19,7 +19,6 @@ package webhooks
19
19
import (
20
20
"context"
21
21
"fmt"
22
- "reflect"
23
22
"strings"
24
23
25
24
"github.com/pkg/errors"
@@ -176,10 +175,6 @@ func (webhook *ClusterClass) validate(ctx context.Context, oldClusterClass, newC
176
175
// Ensure no MachineHealthCheck currently in use has been removed from the ClusterClass.
177
176
allErrs = append (allErrs ,
178
177
validateUpdatesToMachineHealthCheckClasses (clusters , oldClusterClass , newClusterClass )... )
179
-
180
- // Ensure no Variable would be invalidated by the update in spec
181
- allErrs = append (allErrs ,
182
- validateVariableUpdates (clusters , oldClusterClass , newClusterClass , field .NewPath ("spec" , "variables" ))... )
183
178
}
184
179
185
180
if len (allErrs ) > 0 {
@@ -188,118 +183,6 @@ func (webhook *ClusterClass) validate(ctx context.Context, oldClusterClass, newC
188
183
return nil
189
184
}
190
185
191
- // validateVariableUpdates checks if the updates made to ClusterClassVariables are valid.
192
- // It retrieves a list of variables of interest and validates:
193
- // 1) Altered ClusterClassVariables defined on any exiting Cluster are still valid with the updated Schema.
194
- // 2) Removed ClusterClassVariables are not in use on any Cluster using the ClusterClass.
195
- // 3) Added ClusterClassVariables defined on any exiting Cluster are still valid with the updated Schema.
196
- // 4) Required ClusterClassVariables are defined on each Cluster using the ClusterClass.
197
- func validateVariableUpdates (clusters []clusterv1.Cluster , oldClusterClass , newClusterClass * clusterv1.ClusterClass , path * field.Path ) field.ErrorList {
198
- tracker := map [string ][]string {}
199
-
200
- // Get the old ClusterClassVariables as a map
201
- oldVars , _ := getClusterClassVariablesMapWithReverseIndex (oldClusterClass .Spec .Variables )
202
-
203
- // Get the new ClusterClassVariables as a map with an index linking them to their place in the ClusterClass Variable array.
204
- // Note: The index is used to improve the error recording below.
205
- newVars , clusterClassVariablesIndex := getClusterClassVariablesMapWithReverseIndex (newClusterClass .Spec .Variables )
206
-
207
- // Compute the diff between old and new ClusterClassVariables.
208
- varsDiff := getClusterClassVariablesForValidation (oldVars , newVars )
209
-
210
- errorInfo := errorAggregator {}
211
- allClusters := []string {}
212
-
213
- // Validate the variable values on each Cluster ensuring they are still compatible with the new ClusterClass.
214
- for _ , cluster := range clusters {
215
- allClusters = append (allClusters , cluster .Name )
216
- for _ , c := range cluster .Spec .Topology .Variables {
217
- // copy variable to avoid memory aliasing.
218
- clusterVar := c
219
-
220
- // Add Cluster Variable entry in clusterVariableReferences to track where it is in use.
221
- tracker [clusterVar .Name ] = append (tracker [clusterVar .Name ], cluster .Name )
222
-
223
- // 1) Error if a variable with a schema altered in the update is no longer valid on the Cluster.
224
- if alteredVar , ok := varsDiff [variableValidationKey {clusterVar .Name , altered }]; ok {
225
- if errs := variables .ValidateClusterVariable (& clusterVar , alteredVar , field .NewPath ("" )); len (errs ) > 0 {
226
- errorInfo .add (alteredVar .Name , altered , cluster .Name )
227
- }
228
- continue
229
- }
230
-
231
- // 2) Error if a variable removed in the update is still in use in some Clusters.
232
- if _ , ok := varsDiff [variableValidationKey {clusterVar .Name , removed }]; ok {
233
- errorInfo .add (clusterVar .Name , removed , cluster .Name )
234
- continue
235
- }
236
-
237
- // 3) Error if a variable has been added in the update check is no longer valid on the Cluster.
238
- // NOTE: This can't occur in normal circumstances as a variable must be defined in a ClusterClass in order to be introduced in
239
- // a Cluster. This check may catch errors in cases involving broken Clusters.
240
- if addedVar , ok := varsDiff [variableValidationKey {clusterVar .Name , added }]; ok {
241
- if errs := variables .ValidateClusterVariable (& clusterVar , addedVar , field .NewPath ("" )); len (errs ) > 0 {
242
- errorInfo .add (addedVar .Name , added , cluster .Name )
243
- }
244
- continue
245
- }
246
- }
247
-
248
- if cluster .Spec .Topology .Workers == nil {
249
- continue
250
- }
251
-
252
- for _ , md := range cluster .Spec .Topology .Workers .MachineDeployments {
253
- // Continue if there are no variable overrides.
254
- if md .Variables == nil || len (md .Variables .Overrides ) == 0 {
255
- continue
256
- }
257
-
258
- for _ , c := range md .Variables .Overrides {
259
- // copy variable to avoid memory aliasing.
260
- clusterVar := c
261
-
262
- // 1) Error if a variable with a schema altered in the update is no longer valid on the Cluster.
263
- if alteredVar , ok := varsDiff [variableValidationKey {clusterVar .Name , altered }]; ok {
264
- if errs := variables .ValidateClusterVariable (& clusterVar , alteredVar , field .NewPath ("" )); len (errs ) > 0 {
265
- errorInfo .add (fmt .Sprintf ("%s/%s" , md .Name , alteredVar .Name ), altered , cluster .Name )
266
- }
267
- continue
268
- }
269
-
270
- // 2) Error if a variable removed in the update is still in use in some Clusters.
271
- if _ , ok := varsDiff [variableValidationKey {clusterVar .Name , removed }]; ok {
272
- errorInfo .add (fmt .Sprintf ("%s/%s" , md .Name , clusterVar .Name ), removed , cluster .Name )
273
- continue
274
- }
275
-
276
- // 3) Error if a variable has been added in the update check is no longer valid on the Cluster.
277
- // NOTE: This can't occur in normal circumstances as a variable must be defined in a ClusterClass in order to be introduced in
278
- // a Cluster. This check may catch errors in cases involving broken Clusters.
279
- if addedVar , ok := varsDiff [variableValidationKey {clusterVar .Name , added }]; ok {
280
- if errs := variables .ValidateClusterVariable (& clusterVar , addedVar , field .NewPath ("" )); len (errs ) > 0 {
281
- errorInfo .add (fmt .Sprintf ("%s/%s" , md .Name , addedVar .Name ), added , cluster .Name )
282
- }
283
- continue
284
- }
285
- }
286
- }
287
- }
288
-
289
- // 4) Error if a required variable is not defined top-level in every cluster using the ClusterClass.
290
- for v := range varsDiff {
291
- if v .action == required {
292
- clustersMissingVariable := clustersWithoutVar (allClusters , tracker [v .name ])
293
- if len (clustersMissingVariable ) > 0 {
294
- errorInfo .add (v .name , v .action , clustersMissingVariable ... )
295
- }
296
- }
297
- }
298
-
299
- // Aggregate the errors from the validation checks and return one error message of each type for each variable with a list of violating clusters.
300
- return aggregateErrors (errorInfo , clusterClassVariablesIndex , path )
301
- }
302
-
303
186
// validateUpdatesToMachineHealthCheckClasses checks if the updates made to MachineHealthChecks are valid.
304
187
// It makes sure that if a MachineHealthCheck definition is dropped from the ClusterClass then none of the
305
188
// clusters using the ClusterClass rely on it to create a MachineHealthCheck.
@@ -366,106 +249,6 @@ func validateUpdatesToMachineHealthCheckClasses(clusters []clusterv1.Cluster, ol
366
249
return allErrs
367
250
}
368
251
369
- type errorAggregator map [variableValidationKey ][]string
370
-
371
- func (e errorAggregator ) add (name string , action variableValidationType , references ... string ) {
372
- e [variableValidationKey {name , action }] = append (e [variableValidationKey {name , action }], references ... )
373
- }
374
-
375
- func aggregateErrors (errs map [variableValidationKey ][]string , clusterClassVariablesIndex map [string ]int , path * field.Path ) field.ErrorList {
376
- var allErrs , addedErrs , alteredErrs , removedErrs , requiredErrs field.ErrorList
377
- // Look through the errors and append aggregated error messages naming the Clusters blocking changes to existing variables.
378
- for variable , clusters := range errs {
379
- switch variable .action {
380
- case altered :
381
- alteredErrs = append (alteredErrs ,
382
- field .Forbidden (path .Index (clusterClassVariablesIndex [variable .name ]),
383
- fmt .Sprintf ("variable schema change for %s is invalid. It failed validation in Clusters %v" , variable .name , clusters ),
384
- ))
385
- case removed :
386
- removedErrs = append (removedErrs ,
387
- field .Forbidden (path .Index (clusterClassVariablesIndex [variable .name ]),
388
- fmt .Sprintf ("variable %s can not be removed. It is used in Clusters %v" , variable .name , clusters ),
389
- ))
390
- case added :
391
- addedErrs = append (addedErrs ,
392
- field .Forbidden (path .Index (clusterClassVariablesIndex [variable .name ]),
393
- fmt .Sprintf ("variable %s can not be added. It failed validation in Clusters %v" , variable .name , clusters ),
394
- ))
395
- case required :
396
- requiredErrs = append (requiredErrs ,
397
- field .Forbidden (path .Index (clusterClassVariablesIndex [variable .name ]),
398
- fmt .Sprintf ("variable %v is required but is not defined in Clusters %v" , variable .name , clusters ),
399
- ))
400
- }
401
- }
402
-
403
- // Add the slices of errors one by one to maintain a defined order.
404
- allErrs = append (allErrs , alteredErrs ... )
405
- allErrs = append (allErrs , removedErrs ... )
406
- allErrs = append (allErrs , addedErrs ... )
407
- allErrs = append (allErrs , requiredErrs ... )
408
-
409
- return allErrs
410
- }
411
-
412
- func clustersWithoutVar (allClusters , clustersWithVar []string ) []string {
413
- clustersWithVarMap := make (map [string ]interface {})
414
- for _ , cluster := range clustersWithVar {
415
- clustersWithVarMap [cluster ] = nil
416
- }
417
- var clusterDiff []string
418
- for _ , cluster := range allClusters {
419
- if _ , found := clustersWithVarMap [cluster ]; ! found {
420
- clusterDiff = append (clusterDiff , cluster )
421
- }
422
- }
423
- return clusterDiff
424
- }
425
-
426
- type variableValidationType int
427
-
428
- const (
429
- added variableValidationType = iota
430
- altered
431
- removed
432
- required
433
- )
434
-
435
- // variableValidationKey holds the name of the variable and a validation action to perform on it.
436
- type variableValidationKey struct {
437
- name string
438
- action variableValidationType
439
- }
440
-
441
- // getClusterClassVariablesForValidation returns a struct with the four classes of ClusterClass Variables that require validation:
442
- // - added which have been added to the ClusterClass on update.
443
- // - altered which have had their schemas changed on update.
444
- // - removed which have been removed from the ClusterClass on update.
445
- // - required (with 'required' : true) from the new ClusterClass.
446
- func getClusterClassVariablesForValidation (oldVars , newVars map [string ]* clusterv1.ClusterClassVariable ) map [variableValidationKey ]* clusterv1.ClusterClassVariable {
447
- out := map [variableValidationKey ]* clusterv1.ClusterClassVariable {}
448
-
449
- // Compare the old Variable map to the new one to discover which variables have been removed and which have been altered.
450
- for k , v := range oldVars {
451
- if _ , ok := newVars [k ]; ! ok {
452
- out [variableValidationKey {action : removed , name : k }] = v
453
- } else if ! reflect .DeepEqual (v .Schema , newVars [k ].Schema ) {
454
- out [variableValidationKey {action : altered , name : k }] = newVars [k ]
455
- }
456
- }
457
- // Compare the new ClusterClassVariables to the new ClusterClassVariables to find out what variables have been added and which are now required.
458
- for k , v := range newVars {
459
- if _ , ok := oldVars [k ]; ! ok {
460
- out [variableValidationKey {action : added , name : k }] = v
461
- }
462
- if v .Required {
463
- out [variableValidationKey {action : required , name : v .Name }] = v
464
- }
465
- }
466
- return out
467
- }
468
-
469
252
func (webhook * ClusterClass ) validateRemovedMachineDeploymentClassesAreNotUsed (clusters []clusterv1.Cluster , oldClusterClass , newClusterClass * clusterv1.ClusterClass ) field.ErrorList {
470
253
var allErrs field.ErrorList
471
254
0 commit comments