diff --git a/pkg/reconciler/reconciler.go b/pkg/reconciler/reconciler.go index b3282b3b..3e2144ac 100644 --- a/pkg/reconciler/reconciler.go +++ b/pkg/reconciler/reconciler.go @@ -40,6 +40,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" @@ -78,6 +79,7 @@ type Reconciler struct { maxConcurrentReconciles int reconcilePeriod time.Duration maxHistory int + builderSetupFuncs []BuilderSetupFunc annotSetupOnce sync.Once annotations map[string]struct{} @@ -86,6 +88,9 @@ type Reconciler struct { uninstallAnnotations map[string]annotation.Uninstall } +// BuilderSetupFunc allows configuring a controller's builder. +type BuilderSetupFunc func(b *builder.Builder) + // New creates a new Reconciler that reconciles custom resources that define a // Helm release. New takes variadic Option arguments that are used to configure // the Reconciler. @@ -133,7 +138,38 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { r.addDefaults(mgr, controllerName) r.setupScheme(mgr) - c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r, MaxConcurrentReconciles: r.maxConcurrentReconciles}) + // Unfortunately the controller builder API requires us to use the For() method for registering + // an object to watch. This object is registered using the standard EnqueueRequestForObject event handler, + // without any way to configure this. + // + // For our object watches we would like to use the sdkhandler.InstrumentedEnqueueRequestForObject event + // handler -- the registration is being done in setupWatches. + // + // Currently I don't see an easy way out here: In order to implement a generic mechanism for configuring + // the controller builder we need to use the controller builder API for creating the controller. + // + // As long as using For() is mandatory we need to provide some object to this method. + // + // As I understand it, we cannot use the actual type we would like to register for watching, because + // it is hard-coded to use an undesired event handler. + // + // Therefore I am registering a dummy type here. :-( + // + // -MC + dummyObj := &unstructured.Unstructured{} + dummyObj.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "dummy", + Version: "0.0", + Kind: "_dummy", + }) + opts := controller.Options{Reconciler: r, MaxConcurrentReconciles: r.maxConcurrentReconciles} + builder := builder.ControllerManagedBy(mgr).Named(controllerName).WithOptions(opts).For(dummyObj) + + for _, f := range r.builderSetupFuncs { + f(builder) + } + + c, err := builder.Build(r) if err != nil { return err } @@ -425,6 +461,14 @@ func WithSelector(s metav1.LabelSelector) Option { } } +// WithBuilderSetupFunc is an Option that allows configuring a controller's builder using a custom function. +func WithBuilderSetupFunc(f BuilderSetupFunc) Option { + return func(r *Reconciler) error { + r.builderSetupFuncs = append(r.builderSetupFuncs, f) + return nil + } +} + // Reconcile reconciles a CR that defines a Helm v3 release. // // - If a release does not exist for this CR, a new release is installed. diff --git a/pkg/reconciler/reconciler_test.go b/pkg/reconciler/reconciler_test.go index 4bb2ee8b..03e1418a 100644 --- a/pkg/reconciler/reconciler_test.go +++ b/pkg/reconciler/reconciler_test.go @@ -41,6 +41,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -1222,6 +1223,42 @@ var _ = Describe("Reconciler", func() { }) }) + var _ = Describe("Test custom controller builder setup", func() { + const customName = "renamed-controller" + + var ( + mgr manager.Manager + r *Reconciler + err error + builderModified bool + ) + setupBuilder := func(b *builder.Builder) { + b.Named(customName) + builderModified = true + } + + It("Registering builder setup function for reconciler works", func() { + mgr = getManagerOrFail() + r, err = New( + WithGroupVersionKind(gvk), + WithChart(chrt), + WithInstallAnnotations(annotation.InstallDescription{}), + WithUpgradeAnnotations(annotation.UpgradeDescription{}), + WithUninstallAnnotations(annotation.UninstallDescription{}), + WithOverrideValues(map[string]string{ + "image.repository": "custom-nginx", + }), + WithBuilderSetupFunc(setupBuilder), + ) + Expect(err).To(BeNil()) + }) + + It("Setting up reconciler with manager causes custom builder setup to be executed", func() { + Expect(r.SetupWithManager(mgr)).To(Succeed()) + Expect(builderModified).To(BeTrue()) + }) + }) + var _ = Describe("Test predicate selector", func() { It("verifying when a valid selector is passed", func() { selectorPass := metav1.LabelSelector{