Repository: knative/pkg
Pull request #2222 (2021-08-21 01:31:00)

Introduce `NewContext`, deprecate `NewImplFull`.
Our generated `NewImpl` methods have long taken `context.Context`, but despite many iterations the forms we expose from our `controller` package never have.  This change contains several elements:
1. Expose a new `NewContext` method that takes `context.Context` in addition to the current `NewImplFull` signature.
2. Call `NewContext` instead of the deprecated `NewImpl` from our generated controller code.
3. Call `NewContext` from all our webhook reconcilers.
4. Introduce a `tracker.Interface` on `controller.Impl` now that we can extract the tracker lease from `ctx` (for use with https://github.com/knative/eventing/pull/5651, and https://github.com/knative/pkg/pull/2220 downstream)

/kind cleanup

**Release Note**

```release-note
Introduce controller.NewContext and deprecate controller.NewImplFull
```

**Docs**

```docs

```

/assign @n3wscott 

knative-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/pkg/pull/2222#" title="Author self-approved">mattmoor</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fpkg).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/knative/pkg/blob/main/OWNERS)~~ [mattmoor]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
codecov[bot]: # [Codecov](https://codecov.io/gh/knative/pkg/pull/2222?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) Report
> Merging [#2222](https://codecov.io/gh/knative/pkg/pull/2222?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) (3b71df7) into [main](https://codecov.io/gh/knative/pkg/commit/9b9bc2a226b5c56702ac8df47ef34db219aa2f71?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) (9b9bc2a) will **increase** coverage by `0.02%`.
> The diff coverage is `70.00%`.

[![Impacted file tree graph](https://codecov.io/gh/knative/pkg/pull/2222/graphs/tree.svg?width=650&height=150&src=pr&token=rlqpDnfxbO&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative)](https://codecov.io/gh/knative/pkg/pull/2222?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative)

```diff
@@            Coverage Diff             @@
##             main    #2222      +/-   ##
==========================================
+ Coverage   63.91%   63.94%   +0.02%     
==========================================
  Files         220      220              
  Lines        9525     9526       +1     
==========================================
+ Hits         6088     6091       +3     
+ Misses       3165     3164       -1     
+ Partials      272      271       -1     
```


| [Impacted Files](https://codecov.io/gh/knative/pkg/pull/2222?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) | Coverage Δ | |
|---|---|---|
| [.../injection-gen/generators/reconciler\_controller.go](https://codecov.io/gh/knative/pkg/pull/2222/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-Y29kZWdlbi9jbWQvaW5qZWN0aW9uLWdlbi9nZW5lcmF0b3JzL3JlY29uY2lsZXJfY29udHJvbGxlci5nbw==) | `0.00% <0.00%> (ø)` | |
| [controller/controller.go](https://codecov.io/gh/knative/pkg/pull/2222/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-Y29udHJvbGxlci9jb250cm9sbGVyLmdv) | `87.26% <100.00%> (+0.04%)` | :arrow_up: |
| [webhook/certificates/controller.go](https://codecov.io/gh/knative/pkg/pull/2222/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-d2ViaG9vay9jZXJ0aWZpY2F0ZXMvY29udHJvbGxlci5nbw==) | `100.00% <100.00%> (ø)` | |
| [webhook/configmaps/controller.go](https://codecov.io/gh/knative/pkg/pull/2222/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-d2ViaG9vay9jb25maWdtYXBzL2NvbnRyb2xsZXIuZ28=) | `100.00% <100.00%> (ø)` | |
| [webhook/psbinding/controller.go](https://codecov.io/gh/knative/pkg/pull/2222/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-d2ViaG9vay9wc2JpbmRpbmcvY29udHJvbGxlci5nbw==) | `100.00% <100.00%> (ø)` | |
| [webhook/resourcesemantics/conversion/controller.go](https://codecov.io/gh/knative/pkg/pull/2222/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-d2ViaG9vay9yZXNvdXJjZXNlbWFudGljcy9jb252ZXJzaW9uL2NvbnRyb2xsZXIuZ28=) | `83.33% <100.00%> (ø)` | |
| [webhook/resourcesemantics/defaulting/controller.go](https://codecov.io/gh/knative/pkg/pull/2222/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-d2ViaG9vay9yZXNvdXJjZXNlbWFudGljcy9kZWZhdWx0aW5nL2NvbnRyb2xsZXIuZ28=) | `100.00% <100.00%> (ø)` | |
| [webhook/resourcesemantics/validation/controller.go](https://codecov.io/gh/knative/pkg/pull/2222/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-d2ViaG9vay9yZXNvdXJjZXNlbWFudGljcy92YWxpZGF0aW9uL2NvbnRyb2xsZXIuZ28=) | `100.00% <100.00%> (ø)` | |
| [test/gcs/mock/mock.go](https://codecov.io/gh/knative/pkg/pull/2222/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-dGVzdC9nY3MvbW9jay9tb2NrLmdv) | `92.47% <0.00%> (+1.07%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/knative/pkg/pull/2222?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/knative/pkg/pull/2222?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative). Last update [9b9bc2a...3b71df7](https://codecov.io/gh/knative/pkg/pull/2222?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative).

mattmoor: The code style failure is a timeout, the serving downstream failure is a flake, and codecov/project is just always red.  If nothing else fails, then this should be RFAL 👀 
knative-prow-robot: @mattmoor: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-pkg-unit-tests | e2a5591f8f1750c85ccaaf0fcf4a6861811aebb6 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_pkg/2222/pull-knative-pkg-unit-tests/1429108183120482304) | `/test pull-knative-pkg-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/2222). [Your PR dashboard](https://gubernator.knative.dev/pr/mattmoor).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
mattmoor: /retest

MetricsExporter flake...
knative-prow-robot: @mattmoor: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-pkg-unit-tests | e2a5591f8f1750c85ccaaf0fcf4a6861811aebb6 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_pkg/2222/pull-knative-pkg-unit-tests/1429159216915943424) | `/test pull-knative-pkg-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/2222). [Your PR dashboard](https://gubernator.knative.dev/pr/mattmoor).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
mattmoor: Same test 🙃 
/retest

Patch:
diff --git a/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/controller.go b/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/controller.go
index 7704ad01c..f2ade58b5 100644
--- a/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/controller.go
+++ b/client/injection/apiextensions/reconciler/apiextensions/v1/customresourcedefinition/controller.go
@@ -50,7 +50,7 @@ const (
 // NewImpl returns a controller.Impl that handles queuing and feeding work from
 // the queue through an implementation of controller.Reconciler, delegating to
 // the provided Interface and optional Finalizer methods. OptionsFn is used to return
-// controller.Options to be used by the internal reconciler.
+// controller.ControllerOptions to be used by the internal reconciler.
 func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsFn) *controller.Impl {
 	logger := logging.FromContext(ctx)
 
@@ -101,7 +101,7 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF
 		zap.String(logkey.Kind, "apiextensions.k8s.io.CustomResourceDefinition"),
 	)
 
-	impl := controller.NewImpl(rec, logger, ctrTypeName)
+	impl := controller.NewContext(ctx, rec, controller.ControllerOptions{WorkQueueName: ctrTypeName, Logger: logger})
 	agentName := defaultControllerAgentName
 
 	// Pass impl to the options. Save any optional results.
diff --git a/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/controller.go b/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/controller.go
index d7ecc4dba..beb94fb1b 100644
--- a/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/controller.go
+++ b/client/injection/apiextensions/reconciler/apiextensions/v1beta1/customresourcedefinition/controller.go
@@ -50,7 +50,7 @@ const (
 // NewImpl returns a controller.Impl that handles queuing and feeding work from
 // the queue through an implementation of controller.Reconciler, delegating to
 // the provided Interface and optional Finalizer methods. OptionsFn is used to return
-// controller.Options to be used by the internal reconciler.
+// controller.ControllerOptions to be used by the internal reconciler.
 func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsFn) *controller.Impl {
 	logger := logging.FromContext(ctx)
 
@@ -101,7 +101,7 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF
 		zap.String(logkey.Kind, "apiextensions.k8s.io.CustomResourceDefinition"),
 	)
 
-	impl := controller.NewImpl(rec, logger, ctrTypeName)
+	impl := controller.NewContext(ctx, rec, controller.ControllerOptions{WorkQueueName: ctrTypeName, Logger: logger})
 	agentName := defaultControllerAgentName
 
 	// Pass impl to the options. Save any optional results.
diff --git a/client/injection/kube/reconciler/apps/v1/deployment/controller.go b/client/injection/kube/reconciler/apps/v1/deployment/controller.go
index b274520b6..57a0eb90a 100644
--- a/client/injection/kube/reconciler/apps/v1/deployment/controller.go
+++ b/client/injection/kube/reconciler/apps/v1/deployment/controller.go
@@ -48,7 +48,7 @@ const (
 // NewImpl returns a controller.Impl that handles queuing and feeding work from
 // the queue through an implementation of controller.Reconciler, delegating to
 // the provided Interface and optional Finalizer methods. OptionsFn is used to return
-// controller.Options to be used by the internal reconciler.
+// controller.ControllerOptions to be used by the internal reconciler.
 func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsFn) *controller.Impl {
 	logger := logging.FromContext(ctx)
 
@@ -99,7 +99,7 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF
 		zap.String(logkey.Kind, "apps.Deployment"),
 	)
 
-	impl := controller.NewImpl(rec, logger, ctrTypeName)
+	impl := controller.NewContext(ctx, rec, controller.ControllerOptions{WorkQueueName: ctrTypeName, Logger: logger})
 	agentName := defaultControllerAgentName
 
 	// Pass impl to the options. Save any optional results.
diff --git a/client/injection/kube/reconciler/apps/v1beta1/deployment/controller.go b/client/injection/kube/reconciler/apps/v1beta1/deployment/controller.go
index cf4be642f..0cab8e6de 100644
--- a/client/injection/kube/reconciler/apps/v1beta1/deployment/controller.go
+++ b/client/injection/kube/reconciler/apps/v1beta1/deployment/controller.go
@@ -48,7 +48,7 @@ const (
 // NewImpl returns a controller.Impl that handles queuing and feeding work from
 // the queue through an implementation of controller.Reconciler, delegating to
 // the provided Interface and optional Finalizer methods. OptionsFn is used to return
-// controller.Options to be used by the internal reconciler.
+// controller.ControllerOptions to be used by the internal reconciler.
 func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsFn) *controller.Impl {
 	logger := logging.FromContext(ctx)
 
@@ -99,7 +99,7 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF
 		zap.String(logkey.Kind, "apps.Deployment"),
 	)
 
-	impl := controller.NewImpl(rec, logger, ctrTypeName)
+	impl := controller.NewContext(ctx, rec, controller.ControllerOptions{WorkQueueName: ctrTypeName, Logger: logger})
 	agentName := defaultControllerAgentName
 
 	// Pass impl to the options. Save any optional results.
diff --git a/client/injection/kube/reconciler/apps/v1beta2/deployment/controller.go b/client/injection/kube/reconciler/apps/v1beta2/deployment/controller.go
index 45a92eebf..25368ba1d 100644
--- a/client/injection/kube/reconciler/apps/v1beta2/deployment/controller.go
+++ b/client/injection/kube/reconciler/apps/v1beta2/deployment/controller.go
@@ -48,7 +48,7 @@ const (
 // NewImpl returns a controller.Impl that handles queuing and feeding work from
 // the queue through an implementation of controller.Reconciler, delegating to
 // the provided Interface and optional Finalizer methods. OptionsFn is used to return
-// controller.Options to be used by the internal reconciler.
+// controller.ControllerOptions to be used by the internal reconciler.
 func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsFn) *controller.Impl {
 	logger := logging.FromContext(ctx)
 
@@ -99,7 +99,7 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF
 		zap.String(logkey.Kind, "apps.Deployment"),
 	)
 
-	impl := controller.NewImpl(rec, logger, ctrTypeName)
+	impl := controller.NewContext(ctx, rec, controller.ControllerOptions{WorkQueueName: ctrTypeName, Logger: logger})
 	agentName := defaultControllerAgentName
 
 	// Pass impl to the options. Save any optional results.
diff --git a/client/injection/kube/reconciler/core/v1/namespace/controller.go b/client/injection/kube/reconciler/core/v1/namespace/controller.go
index bb7b5667f..550469c7b 100644
--- a/client/injection/kube/reconciler/core/v1/namespace/controller.go
+++ b/client/injection/kube/reconciler/core/v1/namespace/controller.go
@@ -48,7 +48,7 @@ const (
 // NewImpl returns a controller.Impl that handles queuing and feeding work from
 // the queue through an implementation of controller.Reconciler, delegating to
 // the provided Interface and optional Finalizer methods. OptionsFn is used to return
-// controller.Options to be used by the internal reconciler.
+// controller.ControllerOptions to be used by the internal reconciler.
 func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsFn) *controller.Impl {
 	logger := logging.FromContext(ctx)
 
@@ -99,7 +99,7 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF
 		zap.String(logkey.Kind, "core.Namespace"),
 	)
 
-	impl := controller.NewImpl(rec, logger, ctrTypeName)
+	impl := controller.NewContext(ctx, rec, controller.ControllerOptions{WorkQueueName: ctrTypeName, Logger: logger})
 	agentName := defaultControllerAgentName
 
 	// Pass impl to the options. Save any optional results.
diff --git a/client/injection/kube/reconciler/core/v1/secret/controller.go b/client/injection/kube/reconciler/core/v1/secret/controller.go
index fba67486b..0df152eba 100644
--- a/client/injection/kube/reconciler/core/v1/secret/controller.go
+++ b/client/injection/kube/reconciler/core/v1/secret/controller.go
@@ -48,7 +48,7 @@ const (
 // NewImpl returns a controller.Impl that handles queuing and feeding work from
 // the queue through an implementation of controller.Reconciler, delegating to
 // the provided Interface and optional Finalizer methods. OptionsFn is used to return
-// controller.Options to be used by the internal reconciler.
+// controller.ControllerOptions to be used by the internal reconciler.
 func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsFn) *controller.Impl {
 	logger := logging.FromContext(ctx)
 
@@ -99,7 +99,7 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF
 		zap.String(logkey.Kind, "core.Secret"),
 	)
 
-	impl := controller.NewImpl(rec, logger, ctrTypeName)
+	impl := controller.NewContext(ctx, rec, controller.ControllerOptions{WorkQueueName: ctrTypeName, Logger: logger})
 	agentName := defaultControllerAgentName
 
 	// Pass impl to the options. Save any optional results.
diff --git a/client/injection/kube/reconciler/extensions/v1beta1/deployment/controller.go b/client/injection/kube/reconciler/extensions/v1beta1/deployment/controller.go
index c8228d16b..7179f7147 100644
--- a/client/injection/kube/reconciler/extensions/v1beta1/deployment/controller.go
+++ b/client/injection/kube/reconciler/extensions/v1beta1/deployment/controller.go
@@ -48,7 +48,7 @@ const (
 // NewImpl returns a controller.Impl that handles queuing and feeding work from
 // the queue through an implementation of controller.Reconciler, delegating to
 // the provided Interface and optional Finalizer methods. OptionsFn is used to return
-// controller.Options to be used by the internal reconciler.
+// controller.ControllerOptions to be used by the internal reconciler.
 func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsFn) *controller.Impl {
 	logger := logging.FromContext(ctx)
 
@@ -99,7 +99,7 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF
 		zap.String(logkey.Kind, "extensions.Deployment"),
 	)
 
-	impl := controller.NewImpl(rec, logger, ctrTypeName)
+	impl := controller.NewContext(ctx, rec, controller.ControllerOptions{WorkQueueName: ctrTypeName, Logger: logger})
 	agentName := defaultControllerAgentName
 
 	// Pass impl to the options. Save any optional results.
diff --git a/codegen/cmd/injection-gen/generators/reconciler_controller.go b/codegen/cmd/injection-gen/generators/reconciler_controller.go
index c97f8108d..a624275a5 100644
--- a/codegen/cmd/injection-gen/generators/reconciler_controller.go
+++ b/codegen/cmd/injection-gen/generators/reconciler_controller.go
@@ -80,9 +80,9 @@ func (g *reconcilerControllerGenerator) GenerateType(c *generator.Context, t *ty
 			Package: "knative.dev/pkg/controller",
 			Name:    "Reconciler",
 		}),
-		"controllerNewImpl": c.Universe.Function(types.Name{
+		"controllerNewContext": c.Universe.Function(types.Name{
 			Package: "knative.dev/pkg/controller",
-			Name:    "NewImpl",
+			Name:    "NewContext",
 		}),
 		"loggingFromContext": c.Universe.Function(types.Name{
 			Package: "knative.dev/pkg/logging",
@@ -134,7 +134,7 @@ func (g *reconcilerControllerGenerator) GenerateType(c *generator.Context, t *ty
 		}),
 		"controllerOptions": c.Universe.Type(types.Name{
 			Package: "knative.dev/pkg/controller",
-			Name:    "Options",
+			Name:    "ControllerOptions",
 		}),
 		"controllerOptionsFn": c.Universe.Type(types.Name{
 			Package: "knative.dev/pkg/controller",
@@ -257,7 +257,7 @@ func NewImpl(ctx {{.contextContext|raw}}, r Interface{{if .hasClass}}, classValu
 	)
 
 
-	impl := {{.controllerNewImpl|raw}}(rec, logger, ctrTypeName)
+	impl := {{.controllerNewContext|raw}}(ctx, rec, {{ .controllerOptions|raw }}{WorkQueueName: ctrTypeName, Logger: logger})
 	agentName := defaultControllerAgentName
 
 	// Pass impl to the options. Save any optional results.
diff --git a/controller/controller.go b/controller/controller.go
index d3cec2ddb..957e3e1a9 100644
--- a/controller/controller.go
+++ b/controller/controller.go
@@ -41,6 +41,7 @@ import (
 	"knative.dev/pkg/logging"
 	"knative.dev/pkg/logging/logkey"
 	"knative.dev/pkg/reconciler"
+	"knative.dev/pkg/tracker"
 )
 
 const (
@@ -216,6 +217,10 @@ type Impl struct {
 
 	// StatsReporter is used to send common controller metrics.
 	statsReporter StatsReporter
+
+	// Tracker allows reconcilers to associate a reference with particular key,
+	// such that when the reference changes the key is queued for reconciliation.
+	Tracker tracker.Interface
 }
 
 // ControllerOptions encapsulates options for creating a new controller,
@@ -242,7 +247,14 @@ func NewImplWithStats(r Reconciler, logger *zap.SugaredLogger, workQueueName str
 }
 
 // NewImplFull accepts the full set of options available to all controllers.
+// Deprecated: use NewContext instead.
 func NewImplFull(r Reconciler, options ControllerOptions) *Impl {
+	return NewContext(context.TODO(), r, options)
+}
+
+// NewContext instantiates an instance of our controller that will feed work to the
+// provided Reconciler as it is enqueued.
+func NewContext(ctx context.Context, r Reconciler, options ControllerOptions) *Impl {
 	if options.RateLimiter == nil {
 		options.RateLimiter = workqueue.DefaultControllerRateLimiter()
 	}
@@ -252,7 +264,7 @@ func NewImplFull(r Reconciler, options ControllerOptions) *Impl {
 	if options.Concurrency == 0 {
 		options.Concurrency = DefaultThreadsPerController
 	}
-	return &Impl{
+	i := &Impl{
 		Name:          options.WorkQueueName,
 		Reconciler:    r,
 		workQueue:     newTwoLaneWorkQueue(options.WorkQueueName, options.RateLimiter),
@@ -260,6 +272,14 @@ func NewImplFull(r Reconciler, options ControllerOptions) *Impl {
 		statsReporter: options.Reporter,
 		Concurrency:   options.Concurrency,
 	}
+
+	if t := GetTracker(ctx); t != nil {
+		i.Tracker = t
+	} else {
+		i.Tracker = tracker.New(i.EnqueueKey, GetTrackerLease(ctx))
+	}
+
+	return i
 }
 
 // WorkQueue permits direct access to the work queue.
@@ -816,6 +836,25 @@ func GetTrackerLease(ctx context.Context) time.Duration {
 	return 3 * GetResyncPeriod(ctx)
 }
 
+// trackerKey is used to associate tracker.Interface with contexts.
+type trackerKey struct{}
+
+// WithTracker attaches the given tracker.Interface to the provided context
+// in the returned context.
+func WithTracker(ctx context.Context, t tracker.Interface) context.Context {
+	return context.WithValue(ctx, trackerKey{}, t)
+}
+
+// GetTracker attempts to look up the tracker.Interface on a given context.
+// It may return null if none is found.
+func GetTracker(ctx context.Context) tracker.Interface {
+	untyped := ctx.Value(trackerKey{})
+	if untyped == nil {
+		return nil
+	}
+	return untyped.(tracker.Interface)
+}
+
 // erKey is used to associate record.EventRecorders with contexts.
 type erKey struct{}
 
diff --git a/injection/README.md b/injection/README.md
index 0373d53a2..d7c80adae 100644
--- a/injection/README.md
+++ b/injection/README.md
@@ -58,7 +58,10 @@ Update `NewController` as follows:
 ```go
 "knative.dev/pkg/controller"
 ...
-impl := controller.NewImpl(c, logger, "NameOfController")
+impl := controller.NewContext(ctx, c, controller.ControllerOptions{
+	Logger: logger,
+	WorkQueueName: "NameOfController",
+})
 ```
 
 becomes
@@ -139,7 +142,10 @@ func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl
 		ServiceLister: svcInformer.Lister(),
 	}
 	logger = logger.Named("NameOfController")
-	impl := controller.NewImpl(c, logger, "NameOfController")
+	impl := controller.NewContext(ctx, c, controller.ControllerOptions{
+		Logger: logger,
+		WorkQueueName: "NameOfController",
+	})
 
 	// Set up event handlers.
 	svcInformer.Informer().AddEventHandler(...)
@@ -421,7 +427,7 @@ kindreconciler "knative.dev/<repo>/pkg/client/injection/reconciler/<clientgroup>
 Controller related artifacts:
 
 - `NewImpl` - gets an injection based client and lister for `<kind>`, sets up
-  Kubernetes Event recorders, and delegates to `controller.NewImpl` for queue
+  Kubernetes Event recorders, and delegates to `controller.NewContext` for queue
   management.
 
 ```go
diff --git a/resolver/addressable_resolver.go b/resolver/addressable_resolver.go
index efd878f14..01443cf6e 100644
--- a/resolver/addressable_resolver.go
+++ b/resolver/addressable_resolver.go
@@ -44,12 +44,17 @@ type URIResolver struct {
 	listerFactory func(schema.GroupVersionResource) (cache.GenericLister, error)
 }
 
-// NewURIResolver constructs a new URIResolver with context and a callback
-// for a given listableType (Listable) passed to the URIResolver's tracker.
+// NewURIResolver constructs a new URIResolver with context and a tracker.
+// Deprecated: use NewURIResolverFromTracker instead.
 func NewURIResolver(ctx context.Context, callback func(types.NamespacedName)) *URIResolver {
-	ret := &URIResolver{}
+	return NewURIResolverFromTracker(ctx, tracker.New(callback, controller.GetTrackerLease(ctx)))
+}
 
-	ret.tracker = tracker.New(callback, controller.GetTrackerLease(ctx))
+// NewURIResolverFromTracker constructs a new URIResolver with context and a tracker.
+func NewURIResolverFromTracker(ctx context.Context, t tracker.Interface) *URIResolver {
+	ret := &URIResolver{
+		tracker: t,
+	}
 
 	informerFactory := &pkgapisduck.CachedInformerFactory{
 		Delegate: &pkgapisduck.EnqueueInformerFactory{
diff --git a/webhook/certificates/controller.go b/webhook/certificates/controller.go
index f6dedb722..1932e28c2 100644
--- a/webhook/certificates/controller.go
+++ b/webhook/certificates/controller.go
@@ -67,7 +67,7 @@ func NewController(
 	}
 
 	const queueName = "WebhookCertificates"
-	c := controller.NewImpl(wh, logging.FromContext(ctx).Named(queueName), queueName)
+	c := controller.NewContext(ctx, wh, controller.ControllerOptions{WorkQueueName: queueName, Logger: logging.FromContext(ctx).Named(queueName)})
 
 	// Reconcile when the cert bundle changes.
 	secretInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
diff --git a/webhook/configmaps/controller.go b/webhook/configmaps/controller.go
index f499e1f8e..c2d71eb03 100644
--- a/webhook/configmaps/controller.go
+++ b/webhook/configmaps/controller.go
@@ -74,7 +74,7 @@ func NewAdmissionController(
 	}
 
 	const queueName = "ConfigMapWebhook"
-	c := controller.NewImpl(wh, logging.FromContext(ctx).Named(queueName), queueName)
+	c := controller.NewContext(ctx, wh, controller.ControllerOptions{WorkQueueName: queueName, Logger: logging.FromContext(ctx).Named(queueName)})
 
 	// Reconcile when the named ValidatingWebhookConfiguration changes.
 	vwhInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
diff --git a/webhook/psbinding/README.md b/webhook/psbinding/README.md
index 4715a3e67..0cadae08f 100644
--- a/webhook/psbinding/README.md
+++ b/webhook/psbinding/README.md
@@ -143,7 +143,7 @@ func NewController(
 			scheme.Scheme, corev1.EventSource{Component: controllerAgentName}),
 	}
 	logger = logger.Named("GithubBindings")
-	impl := controller.NewImpl(c, logger, "GithubBindings")
+	impl := controller.NewContext(ctx, wh, controller.ControllerOptions{WorkQueueName: "GithubBinding", Logger: logger})
 
 	logger.Info("Setting up event handlers")
 
diff --git a/webhook/psbinding/controller.go b/webhook/psbinding/controller.go
index 1d671d072..bceffcb4f 100644
--- a/webhook/psbinding/controller.go
+++ b/webhook/psbinding/controller.go
@@ -87,7 +87,7 @@ func NewAdmissionController(
 
 	// Construct the reconciler for the mutating webhook configuration.
 	wh := NewReconciler(name, path, options.SecretName, client, mwhInformer.Lister(), secretInformer.Lister(), withContext, reconcilerOptions...)
-	c := controller.NewImpl(wh, logging.FromContext(ctx).Named(name), name)
+	c := controller.NewContext(ctx, wh, controller.ControllerOptions{WorkQueueName: name, Logger: logging.FromContext(ctx).Named(name)})
 
 	// Enqueue a sentinel when we become leader.
 	wh.PromoteFunc = func(bkt pkgreconciler.Bucket, enq func(pkgreconciler.Bucket, types.NamespacedName)) error {
diff --git a/webhook/resourcesemantics/conversion/controller.go b/webhook/resourcesemantics/conversion/controller.go
index 7ab4b158b..5804e2fa9 100644
--- a/webhook/resourcesemantics/conversion/controller.go
+++ b/webhook/resourcesemantics/conversion/controller.go
@@ -119,7 +119,7 @@ func NewConversionController(
 
 	const queueName = "ConversionWebhook"
 	logger := logging.FromContext(ctx)
-	c := controller.NewImpl(r, logger.Named(queueName), queueName)
+	c := controller.NewContext(ctx, r, controller.ControllerOptions{WorkQueueName: queueName, Logger: logger.Named(queueName)})
 
 	// Reconciler when the named CRDs change.
 	for _, gkc := range kinds {
diff --git a/webhook/resourcesemantics/defaulting/controller.go b/webhook/resourcesemantics/defaulting/controller.go
index 7a3f22e34..a4a41d0be 100644
--- a/webhook/resourcesemantics/defaulting/controller.go
+++ b/webhook/resourcesemantics/defaulting/controller.go
@@ -75,7 +75,7 @@ func NewAdmissionController(
 
 	logger := logging.FromContext(ctx)
 	const queueName = "DefaultingWebhook"
-	c := controller.NewImpl(wh, logger.Named(queueName), queueName)
+	c := controller.NewContext(ctx, wh, controller.ControllerOptions{WorkQueueName: queueName, Logger: logger.Named(queueName)})
 
 	// Reconcile when the named MutatingWebhookConfiguration changes.
 	mwhInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
diff --git a/webhook/resourcesemantics/validation/controller.go b/webhook/resourcesemantics/validation/controller.go
index 942da7079..02e47032b 100644
--- a/webhook/resourcesemantics/validation/controller.go
+++ b/webhook/resourcesemantics/validation/controller.go
@@ -90,7 +90,7 @@ func NewAdmissionController(
 
 	logger := logging.FromContext(ctx)
 	const queueName = "ValidationWebhook"
-	c := controller.NewImpl(wh, logger.Named(queueName), queueName)
+	c := controller.NewContext(ctx, wh, controller.ControllerOptions{WorkQueueName: queueName, Logger: logger.Named(queueName)})
 
 	// Reconcile when the named ValidatingWebhookConfiguration changes.
 	vwhInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{


Test patch:
diff --git a/resolver/addressable_resolver_test.go b/resolver/addressable_resolver_test.go
index 8cc88ec32..83b8fa6e8 100644
--- a/resolver/addressable_resolver_test.go
+++ b/resolver/addressable_resolver_test.go
@@ -35,6 +35,7 @@ import (
 	"knative.dev/pkg/client/injection/ducks/duck/v1/addressable"
 	fakedynamicclient "knative.dev/pkg/injection/clients/dynamicclient/fake"
 	"knative.dev/pkg/resolver"
+	"knative.dev/pkg/tracker"
 )
 
 const (
@@ -353,7 +354,7 @@ func TestGetURIDestinationV1Beta1(t *testing.T) {
 		t.Run(n, func(t *testing.T) {
 			ctx, _ := fakedynamicclient.With(context.Background(), scheme.Scheme, tc.objects...)
 			ctx = addressable.WithDuck(ctx)
-			r := resolver.NewURIResolver(ctx, func(types.NamespacedName) {})
+			r := resolver.NewURIResolverFromTracker(ctx, tracker.New(func(types.NamespacedName) {}, 0))
 
 			// Run it twice since this should be idempotent. URI Resolver should
 			// not modify the cache's copy.
@@ -538,7 +539,7 @@ func TestGetURIDestinationV1(t *testing.T) {
 		t.Run(n, func(t *testing.T) {
 			ctx, _ := fakedynamicclient.With(context.Background(), scheme.Scheme, tc.objects...)
 			ctx = addressable.WithDuck(ctx)
-			r := resolver.NewURIResolver(ctx, func(types.NamespacedName) {})
+			r := resolver.NewURIResolverFromTracker(ctx, tracker.New(func(types.NamespacedName) {}, 0))
 
 			// Run it twice since this should be idempotent. URI Resolver should
 			// not modify the cache's copy.
@@ -580,7 +581,7 @@ func TestURIFromObjectReferenceErrors(t *testing.T) {
 		t.Run(n, func(t *testing.T) {
 			ctx, _ := fakedynamicclient.With(context.Background(), scheme.Scheme, tc.objects...)
 			ctx = addressable.WithDuck(ctx)
-			r := resolver.NewURIResolver(ctx, func(types.NamespacedName) {})
+			r := resolver.NewURIResolverFromTracker(ctx, tracker.New(func(types.NamespacedName) {}, 0))
 
 			// Run it twice since this should be idempotent. URI Resolver should
 			// not modify the cache's copy.