Repository: knative/pkg
Pull request #2223 (2021-08-21 01:35:01)

Remove long deprecated NewImplWithStats
/kind cleanup

**Release Note**

```release-note
Remove deprecated controller.NewImplWithStats constructor
```

**Docs**

```docs

```


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

This pull-request has been approved by: *<a href="https://github.com/knative/pkg/pull/2223#" 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:

- ~~[controller/OWNERS](https://github.com/knative/pkg/blob/main/controller/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":[]} -->
mattmoor: Let's see what the downstream tests have to say 🍿 

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 | abf08e652ae875852a53dba95cdb82f3369e178e | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_pkg/2223/pull-knative-pkg-unit-tests/1428893389524307968) | `/test pull-knative-pkg-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/2223). [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 -->
codecov[bot]: # [Codecov](https://codecov.io/gh/knative/pkg/pull/2223?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) Report
> Merging [#2223](https://codecov.io/gh/knative/pkg/pull/2223?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) (5353d4a) 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.01%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/knative/pkg/pull/2223/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/2223?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative)

```diff
@@            Coverage Diff             @@
##             main    #2223      +/-   ##
==========================================
+ Coverage   63.91%   63.93%   +0.01%     
==========================================
  Files         220      220              
  Lines        9525     9524       -1     
==========================================
+ Hits         6088     6089       +1     
+ Misses       3165     3164       -1     
+ Partials      272      271       -1     
```


| [Impacted Files](https://codecov.io/gh/knative/pkg/pull/2223?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative) | Coverage Δ | |
|---|---|---|
| [controller/controller.go](https://codecov.io/gh/knative/pkg/pull/2223/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative#diff-Y29udHJvbGxlci9jb250cm9sbGVyLmdv) | `87.16% <ø> (-0.05%)` | :arrow_down: |
| [test/gcs/mock/mock.go](https://codecov.io/gh/knative/pkg/pull/2223/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/2223?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/2223?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative). Last update [9b9bc2a...5353d4a](https://codecov.io/gh/knative/pkg/pull/2223?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).


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..dc7881ee7 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,
@@ -235,14 +240,15 @@ func NewImpl(r Reconciler, logger *zap.SugaredLogger, workQueueName string) *Imp
 	return NewImplFull(r, ControllerOptions{WorkQueueName: workQueueName, Logger: logger})
 }
 
-// NewImplWithStats creates a controller.Impl with stats reporter.
-// Deprecated: use NewImplFull.
-func NewImplWithStats(r Reconciler, logger *zap.SugaredLogger, workQueueName string, reporter StatsReporter) *Impl {
-	return NewImplFull(r, ControllerOptions{WorkQueueName: workQueueName, Logger: logger, Reporter: reporter})
-}
-
 // 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 +258,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 +266,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 +830,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/controller/controller_test.go b/controller/controller_test.go
index 7937b16e9..3b46d30af 100644
--- a/controller/controller_test.go
+++ b/controller/controller_test.go
@@ -726,20 +726,6 @@ func TestEnqueue(t *testing.T) {
 			}
 		})
 	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			impl := NewImplWithStats(&nopReconciler{}, TestLogger(t), "Testing", &FakeStatsReporter{})
-			test.work(impl)
-
-			impl.WorkQueue().ShutDown()
-			gotQueue := drainWorkQueue(impl.WorkQueue())
-
-			if diff := cmp.Diff(test.wantQueue, gotQueue); diff != "" {
-				t.Error("unexpected queue (-want +got):", diff)
-			}
-		})
-	}
 }
 
 const (
@@ -765,7 +751,12 @@ func pollQ(q workqueue.RateLimitingInterface, sig chan int) func() (bool, error)
 }
 
 func TestEnqueueAfter(t *testing.T) {
-	impl := NewImplWithStats(&nopReconciler{}, TestLogger(t), "Testing", &FakeStatsReporter{})
+	impl := NewImplFull(&nopReconciler{}, ControllerOptions{
+		Logger:        TestLogger(t),
+		WorkQueueName: "Testing",
+		Reporter:      &FakeStatsReporter{},
+	})
+
 	t.Cleanup(func() {
 		impl.WorkQueue().ShutDown()
 	})
@@ -827,7 +818,11 @@ func TestEnqueueAfter(t *testing.T) {
 }
 
 func TestEnqueueKeyAfter(t *testing.T) {
-	impl := NewImplWithStats(&nopReconciler{}, TestLogger(t), "Testing", &FakeStatsReporter{})
+	impl := NewImplFull(&nopReconciler{}, ControllerOptions{
+		Logger:        TestLogger(t),
+		WorkQueueName: "Testing",
+		Reporter:      &FakeStatsReporter{},
+	})
 	t.Cleanup(func() {
 		impl.WorkQueue().ShutDown()
 	})
@@ -885,7 +880,11 @@ func (cr *CountingReconciler) Reconcile(context.Context, string) error {
 
 func TestStartAndShutdown(t *testing.T) {
 	r := &CountingReconciler{}
-	impl := NewImplWithStats(r, TestLogger(t), "Testing", &FakeStatsReporter{})
+	impl := NewImplFull(&nopReconciler{}, ControllerOptions{
+		Logger:        TestLogger(t),
+		WorkQueueName: "Testing",
+		Reporter:      &FakeStatsReporter{},
+	})
 
 	ctx, cancel := context.WithCancel(context.Background())
 	doneCh := make(chan struct{})
@@ -951,7 +950,11 @@ func TestStartAndShutdownWithLeaderAwareNoElection(t *testing.T) {
 			},
 		},
 	}
-	impl := NewImplWithStats(r, TestLogger(t), "Testing", &FakeStatsReporter{})
+	impl := NewImplFull(r, ControllerOptions{
+		Logger:        TestLogger(t),
+		WorkQueueName: "Testing",
+		Reporter:      &FakeStatsReporter{},
+	})
 
 	ctx, cancel := context.WithCancel(context.Background())
 	doneCh := make(chan struct{})
@@ -1019,7 +1022,11 @@ func TestStartAndShutdownWithLeaderAwareWithLostElection(t *testing.T) {
 		},
 	)
 
-	impl := NewImplWithStats(r, TestLogger(t), "Testing", &FakeStatsReporter{})
+	impl := NewImplFull(&nopReconciler{}, ControllerOptions{
+		Logger:        TestLogger(t),
+		WorkQueueName: "Testing",
+		Reporter:      &FakeStatsReporter{},
+	})
 
 	ctx, cancel := context.WithCancel(context.Background())
 	ctx = leaderelection.WithStandardLeaderElectorBuilder(ctx, kc, cc)
@@ -1059,7 +1066,11 @@ func TestStartAndShutdownWithLeaderAwareWithLostElection(t *testing.T) {
 func TestStartAndShutdownWithWork(t *testing.T) {
 	r := &CountingReconciler{}
 	reporter := &FakeStatsReporter{}
-	impl := NewImplWithStats(r, TestLogger(t), "Testing", reporter)
+	impl := NewImplFull(r, ControllerOptions{
+		Logger:        TestLogger(t),
+		WorkQueueName: "Testing",
+		Reporter:      reporter,
+	})
 
 	ctx, cancel := context.WithCancel(context.Background())
 	doneCh := make(chan struct{})
@@ -1159,7 +1170,11 @@ func TestStartAndShutdownWithErroringWork(t *testing.T) {
 
 	item := types.NamespacedName{Namespace: "", Name: "bar"}
 
-	impl := NewImplWithStats(&errorReconciler{}, TestLogger(t), "Testing", &FakeStatsReporter{})
+	impl := NewImplFull(&errorReconciler{}, ControllerOptions{
+		Logger:        TestLogger(t),
+		WorkQueueName: "Testing",
+		Reporter:      &FakeStatsReporter{},
+	})
 	impl.EnqueueKey(item)
 
 	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
@@ -1214,7 +1229,11 @@ func (er *permanentErrorReconciler) Reconcile(context.Context, string) error {
 func TestStartAndShutdownWithPermanentErroringWork(t *testing.T) {
 	r := &permanentErrorReconciler{}
 	reporter := &FakeStatsReporter{}
-	impl := NewImplWithStats(r, TestLogger(t), "Testing", reporter)
+	impl := NewImplFull(r, ControllerOptions{
+		Logger:        TestLogger(t),
+		WorkQueueName: "Testing",
+		Reporter:      reporter,
+	})
 
 	ctx, cancel := context.WithCancel(context.Background())
 	doneCh := make(chan struct{})
@@ -1278,7 +1297,11 @@ func TestStartAndShutdownWithRequeuingWork(t *testing.T) {
 		t.Run(test.name, func(t *testing.T) {
 			r := &requeueAfterReconciler{duration: test.duration}
 			reporter := &FakeStatsReporter{}
-			impl := NewImplWithStats(r, TestLogger(t), "Testing", reporter)
+			impl := NewImplFull(r, ControllerOptions{
+				Logger:        TestLogger(t),
+				WorkQueueName: "Testing",
+				Reporter:      reporter,
+			})
 
 			ctx, cancel := context.WithCancel(context.Background())
 			doneCh := make(chan struct{})
@@ -1373,7 +1396,11 @@ func (*fakeStore) List() []interface{} {
 
 func TestImplGlobalResync(t *testing.T) {
 	r := &CountingReconciler{}
-	impl := NewImplWithStats(r, TestLogger(t), "Testing", &FakeStatsReporter{})
+	impl := NewImplFull(r, ControllerOptions{
+		Logger:        TestLogger(t),
+		WorkQueueName: "Testing",
+		Reporter:      &FakeStatsReporter{},
+	})
 
 	ctx, cancel := context.WithCancel(context.Background())
 	doneCh := make(chan struct{})
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.