Skip to content

Commit cd6bab6

Browse files
authored
Refactor ClusterExtension reconciler to use composable step-based pipeline (#2332)
Replaces the monolithic 170-line reconcile() method with a flexible step-based architecture that executes discrete reconciliation phases in sequence. Each phase (`HandleFinalizers`, `RetrieveRevisionStates`, `RetrieveRevisionMetadata`, `UnpackBundle`, `ApplyBundle`) is now a standalone function that can be composed differently for Helm vs Boxcutter workflows. Changes: - Introduce `ReconcileStepFunc` type and `ReconcileSteps` executor - Extract reconcile logic into individual step functions in new file `clusterextension_reconcile_steps.go` - Move `BoxcutterRevisionStatesGetter` to `boxcutter_reconcile_steps.go` alongside `MigrateStorage` step - Configure step pipelines in `main.go` for each applier type - Refactor tests to use functional options pattern for reconciler setup
1 parent 1355ff7 commit cd6bab6

File tree

7 files changed

+638
-439
lines changed

7 files changed

+638
-439
lines changed

cmd/operator-controller/main.go

Lines changed: 103 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import (
5050
crcache "sigs.k8s.io/controller-runtime/pkg/cache"
5151
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
5252
"sigs.k8s.io/controller-runtime/pkg/client"
53-
crcontroller "sigs.k8s.io/controller-runtime/pkg/controller"
5453
crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer"
5554
"sigs.k8s.io/controller-runtime/pkg/healthz"
5655
"sigs.k8s.io/controller-runtime/pkg/log"
@@ -68,6 +67,7 @@ import (
6867
"github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/cache"
6968
catalogclient "github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/client"
7069
"github.com/operator-framework/operator-controller/internal/operator-controller/contentmanager"
70+
cmcache "github.com/operator-framework/operator-controller/internal/operator-controller/contentmanager/cache"
7171
"github.com/operator-framework/operator-controller/internal/operator-controller/controllers"
7272
"github.com/operator-framework/operator-controller/internal/operator-controller/features"
7373
"github.com/operator-framework/operator-controller/internal/operator-controller/finalizers"
@@ -108,6 +108,31 @@ type config struct {
108108
globalPullSecret string
109109
}
110110

111+
type reconcilerConfigurator interface {
112+
Configure(cer *controllers.ClusterExtensionReconciler) error
113+
}
114+
115+
type boxcutterReconcilerConfigurator struct {
116+
mgr manager.Manager
117+
preflights []applier.Preflight
118+
regv1ManifestProvider applier.ManifestProvider
119+
resolver resolve.Resolver
120+
imageCache imageutil.Cache
121+
imagePuller imageutil.Puller
122+
finalizers crfinalizer.Finalizers
123+
}
124+
125+
type helmReconcilerConfigurator struct {
126+
mgr manager.Manager
127+
preflights []applier.Preflight
128+
regv1ManifestProvider applier.ManifestProvider
129+
resolver resolve.Resolver
130+
imageCache imageutil.Cache
131+
imagePuller imageutil.Puller
132+
finalizers crfinalizer.Finalizers
133+
watcher cmcache.Watcher
134+
}
135+
111136
const (
112137
authFilePrefix = "operator-controller-global-pull-secrets"
113138
fieldOwnerPrefix = "olm.operatorframework.io"
@@ -440,11 +465,7 @@ func run() error {
440465
}
441466

442467
ceReconciler := &controllers.ClusterExtensionReconciler{
443-
Client: cl,
444-
Resolver: resolver,
445-
ImageCache: imageCache,
446-
ImagePuller: imagePuller,
447-
Finalizers: clusterExtensionFinalizers,
468+
Client: cl,
448469
}
449470
ceController, err := ceReconciler.SetupWithManager(mgr, ctrlBuilderOpts...)
450471
if err != nil {
@@ -459,13 +480,30 @@ func run() error {
459480
IsWebhookSupportEnabled: certProvider != nil,
460481
IsSingleOwnNamespaceEnabled: features.OperatorControllerFeatureGate.Enabled(features.SingleOwnNamespaceInstallSupport),
461482
}
462-
483+
var cerCfg reconcilerConfigurator
463484
if features.OperatorControllerFeatureGate.Enabled(features.BoxcutterRuntime) {
464-
err = setupBoxcutter(mgr, ceReconciler, preflights, clusterExtensionFinalizers, regv1ManifestProvider)
485+
cerCfg = &boxcutterReconcilerConfigurator{
486+
mgr: mgr,
487+
preflights: preflights,
488+
regv1ManifestProvider: regv1ManifestProvider,
489+
resolver: resolver,
490+
imageCache: imageCache,
491+
imagePuller: imagePuller,
492+
finalizers: clusterExtensionFinalizers,
493+
}
465494
} else {
466-
err = setupHelm(mgr, ceReconciler, preflights, ceController, clusterExtensionFinalizers, regv1ManifestProvider)
495+
cerCfg = &helmReconcilerConfigurator{
496+
mgr: mgr,
497+
preflights: preflights,
498+
regv1ManifestProvider: regv1ManifestProvider,
499+
resolver: resolver,
500+
imageCache: imageCache,
501+
imagePuller: imagePuller,
502+
finalizers: clusterExtensionFinalizers,
503+
watcher: ceController,
504+
}
467505
}
468-
if err != nil {
506+
if err := cerCfg.Configure(ceReconciler); err != nil {
469507
setupLog.Error(err, "unable to setup lifecycler")
470508
return err
471509
}
@@ -524,19 +562,13 @@ func getCertificateProvider() render.CertificateProvider {
524562
return nil
525563
}
526564

527-
func setupBoxcutter(
528-
mgr manager.Manager,
529-
ceReconciler *controllers.ClusterExtensionReconciler,
530-
preflights []applier.Preflight,
531-
clusterExtensionFinalizers crfinalizer.Registerer,
532-
regv1ManifestProvider applier.ManifestProvider,
533-
) error {
534-
coreClient, err := corev1client.NewForConfig(mgr.GetConfig())
565+
func (c *boxcutterReconcilerConfigurator) Configure(ceReconciler *controllers.ClusterExtensionReconciler) error {
566+
coreClient, err := corev1client.NewForConfig(c.mgr.GetConfig())
535567
if err != nil {
536568
return fmt.Errorf("unable to create core client: %w", err)
537569
}
538-
cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(),
539-
helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, mgr.GetAPIReader(), cfg.systemNamespace)),
570+
cfgGetter, err := helmclient.NewActionConfigGetter(c.mgr.GetConfig(), c.mgr.GetRESTMapper(),
571+
helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, c.mgr.GetAPIReader(), cfg.systemNamespace)),
540572
helmclient.ClientNamespaceMapper(func(obj client.Object) (string, error) {
541573
ext := obj.(*ocv1.ClusterExtension)
542574
return ext.Spec.Namespace, nil
@@ -557,7 +589,7 @@ func setupBoxcutter(
557589
// This finalizer was added by the Helm applier for ClusterExtensions created
558590
// before BoxcutterRuntime was enabled. Boxcutter doesn't use contentmanager,
559591
// so we just need to acknowledge the finalizer to allow deletion to proceed.
560-
err = clusterExtensionFinalizers.Register(controllers.ClusterExtensionCleanupContentManagerCacheFinalizer, finalizers.FinalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) {
592+
err = c.finalizers.Register(controllers.ClusterExtensionCleanupContentManagerCacheFinalizer, finalizers.FinalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) {
561593
// No-op: Boxcutter doesn't use contentmanager, so no cleanup is needed
562594
return crfinalizer.Result{}, nil
563595
}))
@@ -568,27 +600,35 @@ func setupBoxcutter(
568600

569601
// TODO: add support for preflight checks
570602
// TODO: better scheme handling - which types do we want to support?
571-
_ = apiextensionsv1.AddToScheme(mgr.GetScheme())
603+
_ = apiextensionsv1.AddToScheme(c.mgr.GetScheme())
572604
rg := &applier.SimpleRevisionGenerator{
573-
Scheme: mgr.GetScheme(),
574-
ManifestProvider: regv1ManifestProvider,
605+
Scheme: c.mgr.GetScheme(),
606+
ManifestProvider: c.regv1ManifestProvider,
575607
}
576-
ceReconciler.Applier = &applier.Boxcutter{
577-
Client: mgr.GetClient(),
578-
Scheme: mgr.GetScheme(),
608+
appl := &applier.Boxcutter{
609+
Client: c.mgr.GetClient(),
610+
Scheme: c.mgr.GetScheme(),
579611
RevisionGenerator: rg,
580-
Preflights: preflights,
612+
Preflights: c.preflights,
581613
FieldOwner: fmt.Sprintf("%s/clusterextension-controller", fieldOwnerPrefix),
582614
}
583-
ceReconciler.RevisionStatesGetter = &controllers.BoxcutterRevisionStatesGetter{Reader: mgr.GetClient()}
584-
ceReconciler.StorageMigrator = &applier.BoxcutterStorageMigrator{
585-
Client: mgr.GetClient(),
586-
Scheme: mgr.GetScheme(),
615+
revisionStatesGetter := &controllers.BoxcutterRevisionStatesGetter{Reader: c.mgr.GetClient()}
616+
storageMigrator := &applier.BoxcutterStorageMigrator{
617+
Client: c.mgr.GetClient(),
618+
Scheme: c.mgr.GetScheme(),
587619
ActionClientGetter: acg,
588620
RevisionGenerator: rg,
589621
}
622+
ceReconciler.ReconcileSteps = []controllers.ReconcileStepFunc{
623+
controllers.HandleFinalizers(c.finalizers),
624+
controllers.MigrateStorage(storageMigrator),
625+
controllers.RetrieveRevisionStates(revisionStatesGetter),
626+
controllers.ResolveBundle(c.resolver),
627+
controllers.UnpackBundle(c.imagePuller, c.imageCache),
628+
controllers.ApplyBundle(appl),
629+
}
590630

591-
baseDiscoveryClient, err := discovery.NewDiscoveryClientForConfig(mgr.GetConfig())
631+
baseDiscoveryClient, err := discovery.NewDiscoveryClientForConfig(c.mgr.GetConfig())
592632
if err != nil {
593633
return fmt.Errorf("unable to create discovery client: %w", err)
594634
}
@@ -598,48 +638,41 @@ func setupBoxcutter(
598638

599639
trackingCache, err := managedcache.NewTrackingCache(
600640
ctrl.Log.WithName("trackingCache"),
601-
mgr.GetConfig(),
641+
c.mgr.GetConfig(),
602642
crcache.Options{
603-
Scheme: mgr.GetScheme(), Mapper: mgr.GetRESTMapper(),
643+
Scheme: c.mgr.GetScheme(), Mapper: c.mgr.GetRESTMapper(),
604644
},
605645
)
606646
if err != nil {
607647
return fmt.Errorf("unable to create boxcutter tracking cache: %v", err)
608648
}
609-
if err := mgr.Add(trackingCache); err != nil {
649+
if err := c.mgr.Add(trackingCache); err != nil {
610650
return fmt.Errorf("unable to add tracking cache to manager: %v", err)
611651
}
612652

613653
if err = (&controllers.ClusterExtensionRevisionReconciler{
614-
Client: mgr.GetClient(),
654+
Client: c.mgr.GetClient(),
615655
RevisionEngine: machinery.NewRevisionEngine(
616656
machinery.NewPhaseEngine(
617657
machinery.NewObjectEngine(
618-
mgr.GetScheme(), trackingCache, mgr.GetClient(),
619-
ownerhandling.NewNative(mgr.GetScheme()),
620-
machinery.NewComparator(ownerhandling.NewNative(mgr.GetScheme()), discoveryClient, mgr.GetScheme(), fieldOwnerPrefix),
658+
c.mgr.GetScheme(), trackingCache, c.mgr.GetClient(),
659+
ownerhandling.NewNative(c.mgr.GetScheme()),
660+
machinery.NewComparator(ownerhandling.NewNative(c.mgr.GetScheme()), discoveryClient, c.mgr.GetScheme(), fieldOwnerPrefix),
621661
fieldOwnerPrefix, fieldOwnerPrefix,
622662
),
623-
validation.NewClusterPhaseValidator(mgr.GetRESTMapper(), mgr.GetClient()),
663+
validation.NewClusterPhaseValidator(c.mgr.GetRESTMapper(), c.mgr.GetClient()),
624664
),
625-
validation.NewRevisionValidator(), mgr.GetClient(),
665+
validation.NewRevisionValidator(), c.mgr.GetClient(),
626666
),
627667
TrackingCache: trackingCache,
628-
}).SetupWithManager(mgr); err != nil {
668+
}).SetupWithManager(c.mgr); err != nil {
629669
return fmt.Errorf("unable to setup ClusterExtensionRevision controller: %w", err)
630670
}
631671
return nil
632672
}
633673

634-
func setupHelm(
635-
mgr manager.Manager,
636-
ceReconciler *controllers.ClusterExtensionReconciler,
637-
preflights []applier.Preflight,
638-
ceController crcontroller.Controller,
639-
clusterExtensionFinalizers crfinalizer.Registerer,
640-
regv1ManifestProvider applier.ManifestProvider,
641-
) error {
642-
coreClient, err := corev1client.NewForConfig(mgr.GetConfig())
674+
func (c *helmReconcilerConfigurator) Configure(ceReconciler *controllers.ClusterExtensionReconciler) error {
675+
coreClient, err := corev1client.NewForConfig(c.mgr.GetConfig())
643676
if err != nil {
644677
return fmt.Errorf("unable to create core client: %w", err)
645678
}
@@ -649,8 +682,8 @@ func setupHelm(
649682
clientRestConfigMapper = action.SyntheticUserRestConfigMapper(clientRestConfigMapper)
650683
}
651684

652-
cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(),
653-
helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, mgr.GetAPIReader(), cfg.systemNamespace)),
685+
cfgGetter, err := helmclient.NewActionConfigGetter(c.mgr.GetConfig(), c.mgr.GetRESTMapper(),
686+
helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, c.mgr.GetAPIReader(), cfg.systemNamespace)),
654687
helmclient.ClientNamespaceMapper(func(obj client.Object) (string, error) {
655688
ext := obj.(*ocv1.ClusterExtension)
656689
return ext.Spec.Namespace, nil
@@ -671,11 +704,11 @@ func setupHelm(
671704
// determine if PreAuthorizer should be enabled based on feature gate
672705
var preAuth authorization.PreAuthorizer
673706
if features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions) {
674-
preAuth = authorization.NewRBACPreAuthorizer(mgr.GetClient())
707+
preAuth = authorization.NewRBACPreAuthorizer(c.mgr.GetClient())
675708
}
676709

677-
cm := contentmanager.NewManager(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper())
678-
err = clusterExtensionFinalizers.Register(controllers.ClusterExtensionCleanupContentManagerCacheFinalizer, finalizers.FinalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) {
710+
cm := contentmanager.NewManager(clientRestConfigMapper, c.mgr.GetConfig(), c.mgr.GetRESTMapper())
711+
err = c.finalizers.Register(controllers.ClusterExtensionCleanupContentManagerCacheFinalizer, finalizers.FinalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) {
679712
ext := obj.(*ocv1.ClusterExtension)
680713
err := cm.Delete(ext)
681714
return crfinalizer.Result{}, err
@@ -686,18 +719,26 @@ func setupHelm(
686719
}
687720

688721
// now initialize the helmApplier, assigning the potentially nil preAuth
689-
ceReconciler.Applier = &applier.Helm{
722+
appl := &applier.Helm{
690723
ActionClientGetter: acg,
691-
Preflights: preflights,
724+
Preflights: c.preflights,
692725
HelmChartProvider: &applier.RegistryV1HelmChartProvider{
693-
ManifestProvider: regv1ManifestProvider,
726+
ManifestProvider: c.regv1ManifestProvider,
694727
},
695728
HelmReleaseToObjectsConverter: &applier.HelmReleaseToObjectsConverter{},
696729
PreAuthorizer: preAuth,
697-
Watcher: ceController,
730+
Watcher: c.watcher,
698731
Manager: cm,
699732
}
700-
ceReconciler.RevisionStatesGetter = &controllers.HelmRevisionStatesGetter{ActionClientGetter: acg}
733+
revisionStatesGetter := &controllers.HelmRevisionStatesGetter{ActionClientGetter: acg}
734+
ceReconciler.ReconcileSteps = []controllers.ReconcileStepFunc{
735+
controllers.HandleFinalizers(c.finalizers),
736+
controllers.RetrieveRevisionStates(revisionStatesGetter),
737+
controllers.ResolveBundle(c.resolver),
738+
controllers.UnpackBundle(c.imagePuller, c.imageCache),
739+
controllers.ApplyBundle(appl),
740+
}
741+
701742
return nil
702743
}
703744

internal/operator-controller/applier/helm.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ import (
2121
apimachyaml "k8s.io/apimachinery/pkg/util/yaml"
2222
"k8s.io/klog/v2"
2323
"sigs.k8s.io/controller-runtime/pkg/client"
24-
crcontroller "sigs.k8s.io/controller-runtime/pkg/controller"
2524

2625
helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client"
2726

2827
ocv1 "github.com/operator-framework/operator-controller/api/v1"
2928
"github.com/operator-framework/operator-controller/internal/operator-controller/authorization"
3029
"github.com/operator-framework/operator-controller/internal/operator-controller/contentmanager"
30+
"github.com/operator-framework/operator-controller/internal/operator-controller/contentmanager/cache"
3131
"github.com/operator-framework/operator-controller/internal/operator-controller/features"
3232
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util"
3333
imageutil "github.com/operator-framework/operator-controller/internal/shared/util/image"
@@ -65,7 +65,7 @@ type Helm struct {
6565
HelmReleaseToObjectsConverter HelmReleaseToObjectsConverterInterface
6666

6767
Manager contentmanager.Manager
68-
Watcher crcontroller.Controller
68+
Watcher cache.Watcher
6969
}
7070

7171
// runPreAuthorizationChecks performs pre-authorization checks for a Helm release

0 commit comments

Comments
 (0)