diff --git a/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/controller.go b/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/controller.go index 9f69894fbf..075159df83 100644 --- a/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/controller.go +++ b/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/controller.go @@ -116,6 +116,9 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF if opts.DemoteFunc != nil { rec.DemoteFunc = opts.DemoteFunc } + if len(opts.ContextWrappers) != 0 { + rec.ContextWrappers = append(rec.ContextWrappers, opts.ContextWrappers...) + } } rec.Recorder = createRecorder(ctx, agentName) diff --git a/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/reconciler.go b/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/reconciler.go index 61e0b05b68..f9c2f1f159 100644 --- a/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/reconciler.go +++ b/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/reconciler.go @@ -100,6 +100,9 @@ type reconcilerImpl struct { // Kubernetes API. Recorder record.EventRecorder + // ContextWrappers allows to decorate the context passed to the reconcile methods + ContextWrappers []func(context.Context) context.Context + // configStore allows for decorating a context with config maps. // +optional configStore reconciler.ConfigStore @@ -171,6 +174,9 @@ func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client client if opts.DemoteFunc != nil { rec.DemoteFunc = opts.DemoteFunc } + if len(opts.ContextWrappers) != 0 { + rec.ContextWrappers = append(rec.ContextWrappers, opts.ContextWrappers...) + } } return rec @@ -204,6 +210,11 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // Add the recorder to context. ctx = controller.WithEventRecorder(ctx, r.Recorder) + // Decorate the context with additional wrapper functions + for _, fn := range r.ContextWrappers { + ctx = fn(ctx) + } + // Get the resource with this namespace/name. getter := r.Lister diff --git a/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/controller.go b/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/controller.go index fd7ec44044..810ac8d2ac 100644 --- a/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/controller.go +++ b/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/controller.go @@ -116,6 +116,9 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF if opts.DemoteFunc != nil { rec.DemoteFunc = opts.DemoteFunc } + if len(opts.ContextWrappers) != 0 { + rec.ContextWrappers = append(rec.ContextWrappers, opts.ContextWrappers...) + } } rec.Recorder = createRecorder(ctx, agentName) diff --git a/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/reconciler.go b/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/reconciler.go index e470182046..12c8131bc6 100644 --- a/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/reconciler.go +++ b/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/reconciler.go @@ -100,6 +100,9 @@ type reconcilerImpl struct { // Kubernetes API. Recorder record.EventRecorder + // ContextWrappers allows to decorate the context passed to the reconcile methods + ContextWrappers []func(context.Context) context.Context + // configStore allows for decorating a context with config maps. // +optional configStore reconciler.ConfigStore @@ -171,6 +174,9 @@ func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client client if opts.DemoteFunc != nil { rec.DemoteFunc = opts.DemoteFunc } + if len(opts.ContextWrappers) != 0 { + rec.ContextWrappers = append(rec.ContextWrappers, opts.ContextWrappers...) + } } return rec @@ -204,6 +210,11 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // Add the recorder to context. ctx = controller.WithEventRecorder(ctx, r.Recorder) + // Decorate the context with additional wrapper functions + for _, fn := range r.ContextWrappers { + ctx = fn(ctx) + } + // Get the resource with this namespace/name. getter := r.Lister diff --git a/client/injection/kube/reconciler/apps/v1/deployment/controller.go b/client/injection/kube/reconciler/apps/v1/deployment/controller.go index 8e8791fcd0..6f815fd1ac 100644 --- a/client/injection/kube/reconciler/apps/v1/deployment/controller.go +++ b/client/injection/kube/reconciler/apps/v1/deployment/controller.go @@ -114,6 +114,9 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF if opts.DemoteFunc != nil { rec.DemoteFunc = opts.DemoteFunc } + if len(opts.ContextWrappers) != 0 { + rec.ContextWrappers = append(rec.ContextWrappers, opts.ContextWrappers...) + } } rec.Recorder = createRecorder(ctx, agentName) diff --git a/client/injection/kube/reconciler/apps/v1/deployment/reconciler.go b/client/injection/kube/reconciler/apps/v1/deployment/reconciler.go index 43ef90341d..212cb4ae35 100644 --- a/client/injection/kube/reconciler/apps/v1/deployment/reconciler.go +++ b/client/injection/kube/reconciler/apps/v1/deployment/reconciler.go @@ -100,6 +100,9 @@ type reconcilerImpl struct { // Kubernetes API. Recorder record.EventRecorder + // ContextWrappers allows to decorate the context passed to the reconcile methods + ContextWrappers []func(context.Context) context.Context + // configStore allows for decorating a context with config maps. // +optional configStore reconciler.ConfigStore @@ -171,6 +174,9 @@ func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client kubern if opts.DemoteFunc != nil { rec.DemoteFunc = opts.DemoteFunc } + if len(opts.ContextWrappers) != 0 { + rec.ContextWrappers = append(rec.ContextWrappers, opts.ContextWrappers...) + } } return rec @@ -204,6 +210,11 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // Add the recorder to context. ctx = controller.WithEventRecorder(ctx, r.Recorder) + // Decorate the context with additional wrapper functions + for _, fn := range r.ContextWrappers { + ctx = fn(ctx) + } + // Get the resource with this namespace/name. getter := r.Lister.Deployments(s.namespace) diff --git a/client/injection/kube/reconciler/core/v1/namespace/controller.go b/client/injection/kube/reconciler/core/v1/namespace/controller.go index b6844f3a72..35e25d2d15 100644 --- a/client/injection/kube/reconciler/core/v1/namespace/controller.go +++ b/client/injection/kube/reconciler/core/v1/namespace/controller.go @@ -114,6 +114,9 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF if opts.DemoteFunc != nil { rec.DemoteFunc = opts.DemoteFunc } + if len(opts.ContextWrappers) != 0 { + rec.ContextWrappers = append(rec.ContextWrappers, opts.ContextWrappers...) + } } rec.Recorder = createRecorder(ctx, agentName) diff --git a/client/injection/kube/reconciler/core/v1/namespace/reconciler.go b/client/injection/kube/reconciler/core/v1/namespace/reconciler.go index a85973728a..96dd9a69e4 100644 --- a/client/injection/kube/reconciler/core/v1/namespace/reconciler.go +++ b/client/injection/kube/reconciler/core/v1/namespace/reconciler.go @@ -99,6 +99,9 @@ type reconcilerImpl struct { // Kubernetes API. Recorder record.EventRecorder + // ContextWrappers allows to decorate the context passed to the reconcile methods + ContextWrappers []func(context.Context) context.Context + // configStore allows for decorating a context with config maps. // +optional configStore reconciler.ConfigStore @@ -170,6 +173,9 @@ func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client kubern if opts.DemoteFunc != nil { rec.DemoteFunc = opts.DemoteFunc } + if len(opts.ContextWrappers) != 0 { + rec.ContextWrappers = append(rec.ContextWrappers, opts.ContextWrappers...) + } } return rec @@ -203,6 +209,11 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // Add the recorder to context. ctx = controller.WithEventRecorder(ctx, r.Recorder) + // Decorate the context with additional wrapper functions + for _, fn := range r.ContextWrappers { + ctx = fn(ctx) + } + // Get the resource with this namespace/name. getter := r.Lister diff --git a/client/injection/kube/reconciler/core/v1/secret/controller.go b/client/injection/kube/reconciler/core/v1/secret/controller.go index 3f05fc1826..3cd6d7d0ab 100644 --- a/client/injection/kube/reconciler/core/v1/secret/controller.go +++ b/client/injection/kube/reconciler/core/v1/secret/controller.go @@ -111,6 +111,9 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF if opts.DemoteFunc != nil { rec.DemoteFunc = opts.DemoteFunc } + if len(opts.ContextWrappers) != 0 { + rec.ContextWrappers = append(rec.ContextWrappers, opts.ContextWrappers...) + } } rec.Recorder = createRecorder(ctx, agentName) diff --git a/client/injection/kube/reconciler/core/v1/secret/reconciler.go b/client/injection/kube/reconciler/core/v1/secret/reconciler.go index 20e15a8be3..f1de68c271 100644 --- a/client/injection/kube/reconciler/core/v1/secret/reconciler.go +++ b/client/injection/kube/reconciler/core/v1/secret/reconciler.go @@ -97,6 +97,9 @@ type reconcilerImpl struct { // Kubernetes API. Recorder record.EventRecorder + // ContextWrappers allows to decorate the context passed to the reconcile methods + ContextWrappers []func(context.Context) context.Context + // configStore allows for decorating a context with config maps. // +optional configStore reconciler.ConfigStore @@ -161,6 +164,9 @@ func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client kubern if opts.DemoteFunc != nil { rec.DemoteFunc = opts.DemoteFunc } + if len(opts.ContextWrappers) != 0 { + rec.ContextWrappers = append(rec.ContextWrappers, opts.ContextWrappers...) + } } return rec @@ -194,6 +200,11 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // Add the recorder to context. ctx = controller.WithEventRecorder(ctx, r.Recorder) + // Decorate the context with additional wrapper functions + for _, fn := range r.ContextWrappers { + ctx = fn(ctx) + } + // Get the resource with this namespace/name. getter := r.Lister.Secrets(s.namespace) diff --git a/codegen/cmd/injection-gen/generators/reconciler_controller.go b/codegen/cmd/injection-gen/generators/reconciler_controller.go index 9471b5db8a..38f1ef55f7 100644 --- a/codegen/cmd/injection-gen/generators/reconciler_controller.go +++ b/codegen/cmd/injection-gen/generators/reconciler_controller.go @@ -274,6 +274,9 @@ func NewImpl(ctx {{.contextContext|raw}}, r Interface{{if .hasClass}}, classValu if opts.DemoteFunc != nil { rec.DemoteFunc = opts.DemoteFunc } + if len(opts.ContextWrappers) != 0 { + rec.ContextWrappers = append(rec.ContextWrappers, opts.ContextWrappers...) + } } rec.Recorder = createRecorder(ctx, agentName) diff --git a/codegen/cmd/injection-gen/generators/reconciler_reconciler.go b/codegen/cmd/injection-gen/generators/reconciler_reconciler.go index 15fb4259f5..1cda1c3f1d 100644 --- a/codegen/cmd/injection-gen/generators/reconciler_reconciler.go +++ b/codegen/cmd/injection-gen/generators/reconciler_reconciler.go @@ -283,6 +283,9 @@ type reconcilerImpl struct { // Kubernetes API. Recorder {{.recordEventRecorder|raw}} + // ContextWrappers allows to decorate the context passed to the reconcile methods + ContextWrappers []func(context.Context)context.Context + // configStore allows for decorating a context with config maps. // +optional configStore {{.reconcilerConfigStore|raw}} @@ -366,6 +369,9 @@ func NewReconciler(ctx {{.contextContext|raw}}, logger *{{.zapSugaredLogger|raw} if opts.DemoteFunc != nil { rec.DemoteFunc = opts.DemoteFunc } + if len(opts.ContextWrappers) != 0 { + rec.ContextWrappers = append(rec.ContextWrappers, opts.ContextWrappers...) + } } return rec @@ -401,6 +407,11 @@ func (r *reconcilerImpl) Reconcile(ctx {{.contextContext|raw}}, key string) erro // Add the recorder to context. ctx = {{.controllerWithEventRecorder|raw}}(ctx, r.Recorder) + // Decorate the context with additional wrapper functions + for _, fn := range r.ContextWrappers { + ctx = fn(ctx) + } + // Get the resource with this namespace/name. {{if .nonNamespaced}} getter := r.Lister diff --git a/controller/options.go b/controller/options.go index 8d61835e69..2eb9dd2570 100644 --- a/controller/options.go +++ b/controller/options.go @@ -16,7 +16,11 @@ limitations under the License. package controller -import "knative.dev/pkg/reconciler" +import ( + "context" + + "knative.dev/pkg/reconciler" +) // Options is additional resources a Controller might want to use depending // on implementation. @@ -38,9 +42,20 @@ type Options struct { // DemoteFunc configures the demote function this reconciler uses DemoteFunc func(b reconciler.Bucket) + + // ContextWrappers configures the functions to invoke to wrap the context provided to the reconciliation functions + ContextWrappers []func(context.Context) context.Context } // OptionsFn is a callback method signature that accepts an Impl and returns // Options. Used for controllers that need access to the members of Options but // to build Options, integrators need an Impl. type OptionsFn func(impl *Impl) Options + +// WithContextWrapper adds a context wrapper. +// You can add several context wrappers to the same reconciler impl. +func WithContextWrapper(fn func(context.Context) context.Context) OptionsFn { + return func(impl *Impl) Options { + return Options{ContextWrappers: []func(context.Context) context.Context{fn}} + } +} diff --git a/controller/options_test.go b/controller/options_test.go new file mode 100644 index 0000000000..a9b5dd5cd7 --- /dev/null +++ b/controller/options_test.go @@ -0,0 +1,31 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "testing" +) + +func TestWithContextWrapper(t *testing.T) { + options := WithContextWrapper(func(ctx context.Context) context.Context { + return context.Background() + })(nil) + if len(options.ContextWrappers) != 1 { + t.Error("Length of ContextWrappers should be 1") + } +}