@@ -73,12 +73,12 @@ type ClusterExtensionReconciler struct {
7373 ImageCache imageutil.Cache
7474 ImagePuller imageutil.Puller
7575
76- Applier Applier
77- Manager contentmanager.Manager
78- controller crcontroller.Controller
79- cache cache.Cache
80- InstalledBundleGetter InstalledBundleGetter
81- Finalizers crfinalizer.Finalizers
76+ Applier Applier
77+ Manager contentmanager.Manager
78+ controller crcontroller.Controller
79+ cache cache.Cache
80+ RevisionStatesGetter RevisionStatesGetter
81+ Finalizers crfinalizer.Finalizers
8282}
8383
8484type Applier interface {
@@ -88,8 +88,8 @@ type Applier interface {
8888 Apply (context.Context , fs.FS , * ocv1.ClusterExtension , map [string ]string , map [string ]string ) ([]client.Object , string , error )
8989}
9090
91- type InstalledBundleGetter interface {
92- GetInstalledBundle (ctx context.Context , ext * ocv1.ClusterExtension ) (* InstalledBundle , error )
91+ type RevisionStatesGetter interface {
92+ GetRevisionStates (ctx context.Context , ext * ocv1.ClusterExtension ) (* RevisionStates , error )
9393}
9494
9595//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensions,verbs=get;list;watch;update;patch
@@ -218,7 +218,7 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1.Cl
218218 }
219219
220220 l .Info ("getting installed bundle" )
221- installedBundle , err := r .InstalledBundleGetter . GetInstalledBundle (ctx , ext )
221+ revisionStates , err := r .RevisionStatesGetter . GetRevisionStates (ctx , ext )
222222 if err != nil {
223223 setInstallStatus (ext , nil )
224224 var saerr * authentication.ServiceAccountNotFoundError
@@ -232,47 +232,54 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1.Cl
232232 return ctrl.Result {}, err
233233 }
234234
235- // run resolution
236- l .Info ("resolving bundle" )
237- var bm * ocv1.BundleMetadata
238- if installedBundle != nil {
239- bm = & installedBundle .BundleMetadata
240- }
241- resolvedBundle , resolvedBundleVersion , resolvedDeprecation , err := r .Resolver .Resolve (ctx , ext , bm )
242- if err != nil {
243- // Note: We don't distinguish between resolution-specific errors and generic errors
244- setStatusProgressing (ext , err )
245- setInstalledStatusFromBundle (ext , installedBundle )
246- ensureAllConditionsWithReason (ext , ocv1 .ReasonFailed , err .Error ())
247- return ctrl.Result {}, err
248- }
235+ var resolvedRevisionMetadata * RevisionMetadata
236+ if len (revisionStates .RollingOut ) == 0 {
237+ l .Info ("resolving bundle" )
238+ var bm * ocv1.BundleMetadata
239+ if revisionStates .Installed != nil {
240+ bm = & revisionStates .Installed .BundleMetadata
241+ }
242+ resolvedBundle , resolvedBundleVersion , resolvedDeprecation , err := r .Resolver .Resolve (ctx , ext , bm )
243+ if err != nil {
244+ // Note: We don't distinguish between resolution-specific errors and generic errors
245+ setStatusProgressing (ext , err )
246+ setInstalledStatusFromBundle (ext , revisionStates .Installed )
247+ ensureAllConditionsWithReason (ext , ocv1 .ReasonFailed , err .Error ())
248+ return ctrl.Result {}, err
249+ }
249250
250- // set deprecation status after _successful_ resolution
251- // TODO:
252- // 1. It seems like deprecation status should reflect the currently installed bundle, not the resolved
253- // bundle. So perhaps we should set package and channel deprecations directly after resolution, but
254- // defer setting the bundle deprecation until we successfully install the bundle.
255- // 2. If resolution fails because it can't find a bundle, that doesn't mean we wouldn't be able to find
256- // a deprecation for the ClusterExtension's spec.packageName. Perhaps we should check for a non-nil
257- // resolvedDeprecation even if resolution returns an error. If present, we can still update some of
258- // our deprecation status.
259- // - Open question though: what if different catalogs have different opinions of what's deprecated.
260- // If we can't resolve a bundle, how do we know which catalog to trust for deprecation information?
261- // Perhaps if the package shows up in multiple catalogs and deprecations don't match, we can set
262- // the deprecation status to unknown? Or perhaps we somehow combine the deprecation information from
263- // all catalogs?
264- SetDeprecationStatus (ext , resolvedBundle .Name , resolvedDeprecation )
265-
266- resolvedBundleMetadata := bundleutil .MetadataFor (resolvedBundle .Name , * resolvedBundleVersion )
251+ // set deprecation status after _successful_ resolution
252+ // TODO:
253+ // 1. It seems like deprecation status should reflect the currently installed bundle, not the resolved
254+ // bundle. So perhaps we should set package and channel deprecations directly after resolution, but
255+ // defer setting the bundle deprecation until we successfully install the bundle.
256+ // 2. If resolution fails because it can't find a bundle, that doesn't mean we wouldn't be able to find
257+ // a deprecation for the ClusterExtension's spec.packageName. Perhaps we should check for a non-nil
258+ // resolvedDeprecation even if resolution returns an error. If present, we can still update some of
259+ // our deprecation status.
260+ // - Open question though: what if different catalogs have different opinions of what's deprecated.
261+ // If we can't resolve a bundle, how do we know which catalog to trust for deprecation information?
262+ // Perhaps if the package shows up in multiple catalogs and deprecations don't match, we can set
263+ // the deprecation status to unknown? Or perhaps we somehow combine the deprecation information from
264+ // all catalogs?
265+ SetDeprecationStatus (ext , resolvedBundle .Name , resolvedDeprecation )
266+ resolvedRevisionMetadata = & RevisionMetadata {
267+ Package : resolvedBundle .Package ,
268+ Image : resolvedBundle .Image ,
269+ BundleMetadata : bundleutil .MetadataFor (resolvedBundle .Name , * resolvedBundleVersion ),
270+ }
271+ } else {
272+ resolvedRevisionMetadata = revisionStates .RollingOut [0 ]
273+ }
267274
268275 l .Info ("unpacking resolved bundle" )
269- imageFS , _ , _ , err := r .ImagePuller .Pull (ctx , ext .GetName (), resolvedBundle .Image , r .ImageCache )
276+ imageFS , _ , _ , err := r .ImagePuller .Pull (ctx , ext .GetName (), resolvedRevisionMetadata .Image , r .ImageCache )
270277 if err != nil {
271278 // Wrap the error passed to this with the resolution information until we have successfully
272279 // installed since we intend for the progressing condition to replace the resolved condition
273280 // and will be removing the .status.resolution field from the ClusterExtension status API
274- setStatusProgressing (ext , wrapErrorWithResolutionInfo (resolvedBundleMetadata , err ))
275- setInstalledStatusFromBundle (ext , installedBundle )
281+ setStatusProgressing (ext , wrapErrorWithResolutionInfo (resolvedRevisionMetadata . BundleMetadata , err ))
282+ setInstalledStatusFromBundle (ext , revisionStates . Installed )
276283 return ctrl.Result {}, err
277284 }
278285
@@ -282,10 +289,10 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1.Cl
282289 }
283290
284291 storeLbls := map [string ]string {
285- labels .BundleNameKey : resolvedBundle .Name ,
286- labels .PackageNameKey : resolvedBundle .Package ,
287- labels .BundleVersionKey : resolvedBundleVersion . String () ,
288- labels .BundleReferenceKey : resolvedBundle .Image ,
292+ labels .BundleNameKey : resolvedRevisionMetadata .Name ,
293+ labels .PackageNameKey : resolvedRevisionMetadata .Package ,
294+ labels .BundleVersionKey : resolvedRevisionMetadata . Version ,
295+ labels .BundleReferenceKey : resolvedRevisionMetadata .Image ,
289296 }
290297
291298 l .Info ("applying bundle contents" )
@@ -300,18 +307,14 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1.Cl
300307 // The only way to eventually recover from permission errors is to keep retrying).
301308 managedObjs , _ , err := r .Applier .Apply (ctx , imageFS , ext , objLbls , storeLbls )
302309 if err != nil {
303- setStatusProgressing (ext , wrapErrorWithResolutionInfo (resolvedBundleMetadata , err ))
310+ setStatusProgressing (ext , wrapErrorWithResolutionInfo (resolvedRevisionMetadata . BundleMetadata , err ))
304311 // Now that we're actually trying to install, use the error
305- setInstalledStatusFromBundle (ext , installedBundle )
312+ setInstalledStatusFromBundle (ext , revisionStates . Installed )
306313 return ctrl.Result {}, err
307314 }
308315
309- newInstalledBundle := & InstalledBundle {
310- BundleMetadata : resolvedBundleMetadata ,
311- Image : resolvedBundle .Image ,
312- }
313316 // Successful install
314- setInstalledStatusFromBundle (ext , newInstalledBundle )
317+ setInstalledStatusFromBundle (ext , resolvedRevisionMetadata )
315318
316319 l .Info ("watching managed objects" )
317320 cache , err := r .Manager .Get (ctx , ext )
@@ -490,16 +493,22 @@ func clusterExtensionRequestsForCatalog(c client.Reader, logger logr.Logger) crh
490493 }
491494}
492495
493- type HelmInstalledBundleGetter struct {
494- helmclient.ActionClientGetter
496+ type RevisionMetadata struct {
497+ Package string
498+ Image string
499+ ocv1.BundleMetadata
495500}
496501
497- type InstalledBundle struct {
498- ocv1.BundleMetadata
499- Image string
502+ type RevisionStates struct {
503+ Installed * RevisionMetadata
504+ RollingOut []* RevisionMetadata
505+ }
506+
507+ type HelmRevisionStatesGetter struct {
508+ helmclient.ActionClientGetter
500509}
501510
502- func (d * HelmInstalledBundleGetter ) GetInstalledBundle (ctx context.Context , ext * ocv1.ClusterExtension ) (* InstalledBundle , error ) {
511+ func (d * HelmRevisionStatesGetter ) GetRevisionStates (ctx context.Context , ext * ocv1.ClusterExtension ) (* RevisionStates , error ) {
503512 cl , err := d .ActionClientFor (ctx , ext )
504513 if err != nil {
505514 return nil , err
@@ -509,31 +518,34 @@ func (d *HelmInstalledBundleGetter) GetInstalledBundle(ctx context.Context, ext
509518 if err != nil && ! errors .Is (err , driver .ErrReleaseNotFound ) {
510519 return nil , err
511520 }
521+ rs := & RevisionStates {}
512522 if len (relhis ) == 0 {
513- return nil , nil
523+ return rs , nil
514524 }
515525
516526 // relhis[0].Info.Status is the status of the most recent install attempt.
517527 // But we need to look for the most-recent _Deployed_ release
518528 for _ , rel := range relhis {
519529 if rel .Info != nil && rel .Info .Status == release .StatusDeployed {
520- return & InstalledBundle {
530+ rs .Installed = & RevisionMetadata {
531+ Package : rel .Labels [labels .PackageNameKey ],
532+ Image : rel .Labels [labels .BundleReferenceKey ],
521533 BundleMetadata : ocv1.BundleMetadata {
522534 Name : rel .Labels [labels .BundleNameKey ],
523535 Version : rel .Labels [labels .BundleVersionKey ],
524536 },
525- Image : rel . Labels [ labels . BundleReferenceKey ],
526- }, nil
537+ }
538+ break
527539 }
528540 }
529- return nil , nil
541+ return rs , nil
530542}
531543
532- type BoxcutterInstalledBundleGetter struct {
544+ type BoxcutterRevisionStatesGetter struct {
533545 client.Reader
534546}
535547
536- func (d * BoxcutterInstalledBundleGetter ) GetInstalledBundle (ctx context.Context , ext * ocv1.ClusterExtension ) (* InstalledBundle , error ) {
548+ func (d * BoxcutterRevisionStatesGetter ) GetRevisionStates (ctx context.Context , ext * ocv1.ClusterExtension ) (* RevisionStates , error ) {
537549 // TODO: boxcutter applier has a nearly identical bit of code for listing and sorting revisions
538550 // only difference here is that it sorts in reverse order to start iterating with the most
539551 // recent revisions. We should consolidate to avoid code duplication.
@@ -547,28 +559,30 @@ func (d *BoxcutterInstalledBundleGetter) GetInstalledBundle(ctx context.Context,
547559 return cmp .Compare (b .Spec .Revision , a .Spec .Revision )
548560 })
549561
562+ rs := & RevisionStates {}
550563 for _ , rev := range existingRevisionList .Items {
551564 if rev .Spec .LifecycleState == ocv1 .ClusterExtensionRevisionLifecycleStateActive {
552- // TODO: we should make constants for the ClusterExtensionRevision condition types.
553- if installedCondition := apimeta .FindStatusCondition (rev .Status .Conditions , "Succeeded" ); installedCondition == nil || installedCondition .Status != metav1 .ConditionTrue {
554- // TODO: It's not great to return this error in a completely normal situation. This currently cascades
555- // into Installed=False, Failed and Progressing=True, Retrying conditions that give the impression
556- // that something is wrong. We should probably refactor the InstalledBundleGetter interface to better
557- // handle the async nature of ClusterExtensionRevision rollouts.
558- return nil , fmt .Errorf ("most recent active revision %s is still rolling out" , rev .Name )
559- }
560565
561566 // TODO: the setting of these annotations (happens in boxcutter applier when we pass in "storageLabels")
562567 // is fairly decoupled from this code where we get the annotations back out. We may want to co-locate
563568 // the set/get logic a bit better to make it more maintainable and less likely to get out of sync.
564- return & InstalledBundle {
569+ rm := & RevisionMetadata {
570+ Package : rev .Labels [labels .PackageNameKey ],
571+ Image : rev .Annotations [labels .BundleReferenceKey ],
565572 BundleMetadata : ocv1.BundleMetadata {
566573 Name : rev .Annotations [labels .BundleNameKey ],
567574 Version : rev .Annotations [labels .BundleVersionKey ],
568575 },
569- Image : rev .Annotations [labels .BundleReferenceKey ],
570- }, nil
576+ }
577+
578+ // TODO: we should make constants for the ClusterExtensionRevision condition types.
579+ if installedCondition := apimeta .FindStatusCondition (rev .Status .Conditions , "Succeeded" ); installedCondition == nil || installedCondition .Status != metav1 .ConditionTrue {
580+ rs .RollingOut = append (rs .RollingOut , rm )
581+ } else {
582+ rs .Installed = rm
583+ break
584+ }
571585 }
572586 }
573- return nil , nil
587+ return rs , nil
574588}
0 commit comments