@@ -22,6 +22,7 @@ import (
22
22
"encoding/json"
23
23
"fmt"
24
24
"strconv"
25
+ "time"
25
26
26
27
"github.com/pkg/errors"
27
28
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -101,6 +102,12 @@ type ByObjectConfig struct {
101
102
// Note: If this is enabled, we will use the corresponding Go type of the object for Get & List calls to avoid
102
103
// creating additional informers for UnstructuredList/PartialObjectMetadataList.
103
104
UseCache bool
105
+
106
+ // UseStatusForStorageVersionMigration configures if the storage version migration for this CRD should
107
+ // be triggered via the status endpoint instead of an update on the CRs directly (which is the default).
108
+ // As mutating and validating webhooks are usually not configured on the status subresource this can help to
109
+ // avoid mutating & validation webhook errors that would block the no-op updates and thus the storage migration.
110
+ UseStatusForStorageVersionMigration bool
104
111
}
105
112
106
113
func (r * CRDMigrator ) SetupWithManager (ctx context.Context , mgr ctrl.Manager , controllerOptions controller.Options ) error {
@@ -164,7 +171,7 @@ func (r *CRDMigrator) setup(scheme *runtime.Scheme) error {
164
171
r .configByCRDName [contract .CalculateCRDName (gvk .Group , gvk .Kind )] = cfg
165
172
}
166
173
167
- r .storageVersionMigrationCache = cache .New [objectEntry ]()
174
+ r .storageVersionMigrationCache = cache.New [objectEntry ](1 * time . Hour )
168
175
return nil
169
176
}
170
177
@@ -230,7 +237,7 @@ func (r *CRDMigrator) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.R
230
237
231
238
// If phase should be run and .status.storedVersions != [storageVersion], run storage version migration.
232
239
if r .crdMigrationPhasesToRun .Has (StorageVersionMigrationPhase ) && storageVersionMigrationRequired (crd , storageVersion ) {
233
- if err := r .reconcileStorageVersionMigration (ctx , crd , customResourceObjects , storageVersion ); err != nil {
240
+ if err := r .reconcileStorageVersionMigration (ctx , crd , migrationConfig , customResourceObjects , storageVersion ); err != nil {
234
241
return ctrl.Result {}, err
235
242
}
236
243
@@ -252,18 +259,13 @@ func (r *CRDMigrator) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.R
252
259
}
253
260
254
261
func storageVersionForCRD (crd * apiextensionsv1.CustomResourceDefinition ) (string , error ) {
255
- var storageVersion string
256
262
for _ , v := range crd .Spec .Versions {
257
263
if v .Storage {
258
- storageVersion = v .Name
259
- break
264
+ return v .Name , nil
260
265
}
261
266
}
262
- if storageVersion == "" {
263
- return "" , errors .Errorf ("could not find storage version for CustomResourceDefinition %s" , crd .Name )
264
- }
265
267
266
- return storageVersion , nil
268
+ return "" , errors . Errorf ( "could not find storage version for CustomResourceDefinition %s" , crd . Name )
267
269
}
268
270
269
271
func storageVersionMigrationRequired (crd * apiextensionsv1.CustomResourceDefinition , storageVersion string ) bool {
@@ -360,7 +362,7 @@ func listObjectsFromAPIReader(ctx context.Context, c client.Reader, objectList c
360
362
return objs , nil
361
363
}
362
364
363
- func (r * CRDMigrator ) reconcileStorageVersionMigration (ctx context.Context , crd * apiextensionsv1.CustomResourceDefinition , customResourceObjects []client.Object , storageVersion string ) error {
365
+ func (r * CRDMigrator ) reconcileStorageVersionMigration (ctx context.Context , crd * apiextensionsv1.CustomResourceDefinition , migrationConfig ByObjectConfig , customResourceObjects []client.Object , storageVersion string ) error {
364
366
if len (customResourceObjects ) == 0 {
365
367
return nil
366
368
}
@@ -374,6 +376,7 @@ func (r *CRDMigrator) reconcileStorageVersionMigration(ctx context.Context, crd
374
376
Kind : crd .Spec .Names .Kind ,
375
377
}
376
378
379
+ errs := []error {}
377
380
for _ , obj := range customResourceObjects {
378
381
e := objectEntry {
379
382
Kind : gvk .Kind ,
@@ -384,6 +387,9 @@ func (r *CRDMigrator) reconcileStorageVersionMigration(ctx context.Context, crd
384
387
}
385
388
386
389
if _ , alreadyMigrated := r .storageVersionMigrationCache .Has (e .Key ()); alreadyMigrated {
390
+ // Refresh the cache entry, so that we don't try to migrate the storage version for CRs that were
391
+ // already migrated successfully in cases where storage migrations failed for a subset of the CRs.
392
+ r .storageVersionMigrationCache .Add (e )
387
393
continue
388
394
}
389
395
@@ -402,16 +408,26 @@ func (r *CRDMigrator) reconcileStorageVersionMigration(ctx context.Context, crd
402
408
u .SetResourceVersion (obj .GetResourceVersion ())
403
409
404
410
log .V (4 ).Info ("Migrating to new storage version" , gvk .Kind , klog .KObj (u ))
405
- err := r .Client .Patch (ctx , u , client .Apply , client .FieldOwner ("crdmigrator" ))
411
+ var err error
412
+ if migrationConfig .UseStatusForStorageVersionMigration {
413
+ err = r .Client .Status ().Patch (ctx , u , client .Apply , client .FieldOwner ("crdmigrator" ))
414
+ } else {
415
+ err = r .Client .Patch (ctx , u , client .Apply , client .FieldOwner ("crdmigrator" ))
416
+ }
406
417
// If we got a NotFound error, the object no longer exists so no need to update it.
407
418
// If we got a Conflict error, another client wrote the object already so no need to update it.
408
419
if err != nil && ! apierrors .IsNotFound (err ) && ! apierrors .IsConflict (err ) {
409
- return errors .Wrapf (err , "failed to migrate storage version of %s %s" , gvk .Kind , klog .KObj (u ))
420
+ errs = append (errs , errors .Wrap (err , klog .KObj (u ).String ()))
421
+ continue
410
422
}
411
423
412
424
r .storageVersionMigrationCache .Add (e )
413
425
}
414
426
427
+ if len (errs ) > 0 {
428
+ return errors .Wrapf (kerrors .NewAggregate (errs ), "failed to migrate storage version of %s objects" , gvk .Kind )
429
+ }
430
+
415
431
return nil
416
432
}
417
433
@@ -431,6 +447,7 @@ func (r *CRDMigrator) reconcileCleanupManagedFields(ctx context.Context, crd *ap
431
447
}
432
448
}
433
449
450
+ errs := []error {}
434
451
for _ , obj := range customResourceObjects {
435
452
if len (obj .GetManagedFields ()) == 0 {
436
453
continue
@@ -512,10 +529,15 @@ func (r *CRDMigrator) reconcileCleanupManagedFields(ctx context.Context, crd *ap
512
529
// Note: We always have to return the conflict error directly (instead of an aggregate) so retry on conflict works.
513
530
return err
514
531
}); err != nil {
515
- return errors .Wrapf (kerrors .NewAggregate ([]error {err , getErr }), "failed to cleanup managedFields of %s %s" , crd .Spec .Names .Kind , klog .KObj (obj ))
532
+ errs = append (errs , errors .Wrap (kerrors .NewAggregate ([]error {err , getErr }), klog .KObj (obj ).String ()))
533
+ continue
516
534
}
517
535
}
518
536
537
+ if len (errs ) > 0 {
538
+ return errors .Wrapf (kerrors .NewAggregate (errs ), "failed to cleanup managedFields of %s objects" , crd .Spec .Names .Kind )
539
+ }
540
+
519
541
return nil
520
542
}
521
543
0 commit comments