Skip to content

Commit 7bfb7c7

Browse files
committed
InstalledBundleGetter -> RevisionStatesGetter
This change accommodates the possibility of a revision that is currently rolling out, which is possible for appliers that perform rollouts asynchronously. Signed-off-by: Joe Lanford <[email protected]>
1 parent 9032b04 commit 7bfb7c7

File tree

5 files changed

+115
-101
lines changed

5 files changed

+115
-101
lines changed

cmd/operator-controller/main.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -442,9 +442,9 @@ func run() error {
442442

443443
// create applier
444444
var (
445-
ctrlBuilderOpts []controllers.ControllerBuilderOption
446-
extApplier controllers.Applier
447-
installedBundleGetter controllers.InstalledBundleGetter
445+
ctrlBuilderOpts []controllers.ControllerBuilderOption
446+
extApplier controllers.Applier
447+
revisionStatesGetter controllers.RevisionStatesGetter
448448
)
449449
certProvider := getCertificateProvider()
450450
if features.OperatorControllerFeatureGate.Enabled(features.BoxcutterRuntime) {
@@ -463,7 +463,7 @@ func run() error {
463463
},
464464
Preflights: preflights,
465465
}
466-
installedBundleGetter = &controllers.BoxcutterInstalledBundleGetter{Reader: mgr.GetClient()}
466+
revisionStatesGetter = &controllers.BoxcutterRevisionStatesGetter{Reader: mgr.GetClient()}
467467
ctrlBuilderOpts = append(ctrlBuilderOpts, controllers.WithOwns(&ocv1.ClusterExtensionRevision{}))
468468
} else {
469469
// now initialize the helmApplier, assigning the potentially nil preAuth
@@ -478,7 +478,7 @@ func run() error {
478478
PreAuthorizer: preAuth,
479479
HelmReleaseToObjectsConverter: &applier.HelmReleaseToObjectsConverter{},
480480
}
481-
installedBundleGetter = &controllers.HelmInstalledBundleGetter{ActionClientGetter: acg}
481+
revisionStatesGetter = &controllers.HelmRevisionStatesGetter{ActionClientGetter: acg}
482482
}
483483

484484
cm := contentmanager.NewManager(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper())
@@ -493,14 +493,14 @@ func run() error {
493493
}
494494

495495
if err = (&controllers.ClusterExtensionReconciler{
496-
Client: cl,
497-
Resolver: resolver,
498-
ImageCache: imageCache,
499-
ImagePuller: imagePuller,
500-
Applier: extApplier,
501-
InstalledBundleGetter: installedBundleGetter,
502-
Finalizers: clusterExtensionFinalizers,
503-
Manager: cm,
496+
Client: cl,
497+
Resolver: resolver,
498+
ImageCache: imageCache,
499+
ImagePuller: imagePuller,
500+
Applier: extApplier,
501+
RevisionStatesGetter: revisionStatesGetter,
502+
Finalizers: clusterExtensionFinalizers,
503+
Manager: cm,
504504
}).SetupWithManager(mgr, ctrlBuilderOpts...); err != nil {
505505
setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension")
506506
return err

internal/operator-controller/controllers/clusterextension_controller.go

Lines changed: 93 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -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

8484
type 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
}

internal/operator-controller/controllers/clusterextension_controller_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ func TestClusterExtensionApplierFailsWithBundleInstalled(t *testing.T) {
452452
cache: &MockManagedContentCache{},
453453
}
454454
reconciler.InstalledBundleGetter = &MockInstalledBundleGetter{
455-
bundle: &controllers.InstalledBundle{
455+
bundle: &controllers.RevisionMetadata{
456456
BundleMetadata: ocv1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"},
457457
Image: "quay.io/operatorhubio/[email protected]",
458458
},
@@ -788,7 +788,7 @@ func TestClusterExtensionDeleteFinalizerFails(t *testing.T) {
788788
cache: &MockManagedContentCache{},
789789
}
790790
reconciler.InstalledBundleGetter = &MockInstalledBundleGetter{
791-
bundle: &controllers.InstalledBundle{
791+
bundle: &controllers.RevisionMetadata{
792792
BundleMetadata: ocv1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"},
793793
Image: "quay.io/operatorhubio/[email protected]",
794794
},
@@ -1451,7 +1451,7 @@ type MockActionGetter struct {
14511451
description string
14521452
rels []*release.Release
14531453
err error
1454-
expectedBundle *controllers.InstalledBundle
1454+
expectedBundle *controllers.RevisionMetadata
14551455
expectedError error
14561456
}
14571457

@@ -1525,7 +1525,7 @@ func TestGetInstalledBundleHistory(t *testing.T) {
15251525
},
15261526
},
15271527
nil,
1528-
&controllers.InstalledBundle{
1528+
&controllers.RevisionMetadata{
15291529
BundleMetadata: ocv1.BundleMetadata{
15301530
Name: "test-ext",
15311531
Version: "1.0",
@@ -1560,7 +1560,7 @@ func TestGetInstalledBundleHistory(t *testing.T) {
15601560
},
15611561
},
15621562
nil,
1563-
&controllers.InstalledBundle{
1563+
&controllers.RevisionMetadata{
15641564
BundleMetadata: ocv1.BundleMetadata{
15651565
Name: "test-ext",
15661566
Version: "1.0",

internal/operator-controller/controllers/common_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
)
2929

3030
// setInstalledStatusFromBundle sets the installed status based on the given installedBundle.
31-
func setInstalledStatusFromBundle(ext *ocv1.ClusterExtension, installedBundle *InstalledBundle) {
31+
func setInstalledStatusFromBundle(ext *ocv1.ClusterExtension, installedBundle *RevisionMetadata) {
3232
// Nothing is installed
3333
if installedBundle == nil {
3434
setInstallStatus(ext, nil)

0 commit comments

Comments
 (0)