caching/vendor/knative.dev/pkg/injection
Knative Automation 41aa68a551
upgrade to latest dependencies (#921)
bumping knative.dev/pkg c43477f...efddeac:
  > efddeac Update community files (# 3143)
  > e5aa25f Bump google.golang.org/grpc from 1.69.4 to 1.70.0 (# 3142)
  > 7fca699 Bump google.golang.org/protobuf from 1.36.3 to 1.36.4 (# 3141)
  > dcf1593 Update community files (# 3140)
  > 3386f37 Bump google.golang.org/protobuf from 1.36.2 to 1.36.3 (# 3139)
  > 1ca59d1 Bump google.golang.org/grpc from 1.69.2 to 1.69.4 (# 3138)
  > a37a847 drop use of code-generator/generate-groups.sh (# 3136)
bumping golang.org/x/oauth2 3e64809...22134a4:
  > 22134a4 README: don't recommend go get
bumping google.golang.org/genproto/googleapis/api 796eee8...19429a9:
  > 19429a9 chore(all): update all (# 1171)
  > e639e21 chore(all): update all (# 1170)
  > 65e8d21 Fix: GitHub workflow script injection (# 1169)
  > e0fbfb7 chore(all): update all (# 1168)
  > dd2ea8e chore(all): update all (# 1166)
  > 324edc3 chore(all): update all (# 1165)
bumping google.golang.org/protobuf 12c6ebd...259e665:
  > 259e665 all: release v1.36.4
  > 5f93d99 internal_gengo: avoid allocations in rawDescGZIP() accessors
  > 2005adb reflect/protodesc: fix panic when working with dynamicpb
  > aee8a9c internal_gengo: switch back from string literal to hex byte slice
  > 0c3cc2f internal_gengo: use unsafe.StringData() to avoid a descriptor copy
  > cc8d1c2 internal_gengo: store raw descriptor in .rodata section
  > 132f042 all: start v1.36.3-devel
  > 54ef969 all: release v1.36.3
  > 7cbd915 reflect/protodesc: fix panic when working with dynamicpb
  > 2f60868 proto: add example for GetExtension, SetExtension
  > de043b9 runtime/protolazy: replace internal doc link with external link
  > 42e0fa9 all: split flags.ProtoLegacyWeak out of flags.ProtoLegacy
  > 5fee2a7 internal/impl: remove unused exporter parameter
  > 84924f7 internal/impl: switch to reflect.Value.IsZero
  > fe8430d cmd/protoc-gen-go: remove json struct tags from unexported fields
  > 84f7738 internal/impl: clean up unneeded Go<1.12 MapRange() alternative
  > 9acc8f2 types/dynamicpb: switch atomicExtFiles to atomic.Uint64 type
  > ad89419 all: start v1.36.2-devel
bumping knative.dev/hack c142b48...5f7f0f5:
  > 5f7f0f5 Update community files (# 412)
  > b38a2ca Update community files (# 411)
bumping google.golang.org/genproto/googleapis/rpc 796eee8...19429a9:
  > 19429a9 chore(all): update all (# 1171)
  > e639e21 chore(all): update all (# 1170)
  > 65e8d21 Fix: GitHub workflow script injection (# 1169)
  > e0fbfb7 chore(all): update all (# 1168)
  > dd2ea8e chore(all): update all (# 1166)
  > 324edc3 chore(all): update all (# 1165)
bumping google.golang.org/grpc b615b35...98a0092:
  > 98a0092 Change version to 1.70.0 (# 7984)
  > bf380de Cherrypick # 7998,  # 8011, # 8010 into 1.70.x (# 8028)
  > 54b3eb9 experimental/credentials: Add credentials that don't enforce ALPN (# 7980) (# 8012)
  > 62b9185 clustetresolver: Copy endpoints.Addresses slice from DNS updates to avoid data races (# 7991) (# 8004)
  > 724f450 examples/features/csm_observability: use helloworld client and server instead of echo client and server (# 7945)
  > e8d5feb rbac: add method name to :path in headers (# 7965)
  > e912015 cleanup: Fix usages of non-constant format strings (# 7959)
  > 681334a cleanup: replace dial with newclient (# 7943)
  > 063d352 internal/resolver: introduce a new resolver to handle target URI and proxy address resolution (# 7857)
  > 10c7e13 outlierdetection: Support health listener for ejection updates (# 7908)
  > bce0535 test: Add a test for decompression exceeding max receive message size (# 7938)
  > f32168c envconfig: enable xDS client fallback by default (# 7949)
  > e957825 test: Workaround slow SRV lookups in flaking test (# 7957)
  > e5a4eb0 deps: update crypto dependency to resolve CVE-2024-45337 (# 7956)
  > 56a14ba cleanup: replace dial with newclient (# 7920)
  > b3bdacb test: switching to stubserver in tests instead of testservice (# 7925)
  > e8055ea grpcs: update `WithContextDialer` documentation to include using passthrough resolver (# 7916)
  > d0716f9 examples/features/csm_observability: Make CSM Observability example server listen on an IPV4 address (# 7933)
  > cc161de xds: Add support for multiple addresses per endpoint (# 7858)
  > 3f76275 xdsclient: stop caching xdsChannels for potential reuse, after all references are released (# 7924)
  > 7ee073d experimental/stats: re-add type aliases for migration (# 7929)
  > 38a8b9a health, grpc: Deliver health service updates through the health listener (# 7900)
  > c1b6b37 Update README.md (# 7921)
  > e4d084a examples: replace printf with print for log message in gracefulstop (# 7917)
  > b1f70ce test: replace grpc.Dial with grpc.NewClient
  > 0027558 internal/transport: replace integer status codes with http constants (# 7910)
  > 66ba4b2 examples/features/gracefulstop: add example to demonstrate server graceful stop (# 7865)
  > adad26d test/kokoro: Add psm-fallback build config (# 7899)
  > f53724d serviceconfig: Return errors instead of skipping invalid retry policy config (# 7905)
  > 645aadf deps: update dependencies for all modules (# 7904)
  > d7286fb Change version to 1.70.0-dev (# 7903)

Signed-off-by: Knative Automation <automation@knative.team>
2025-01-29 14:27:55 +00:00
..
README.md upgrade to latest dependencies (#921) 2025-01-29 14:27:55 +00:00
clients.go upgrade to latest dependencies (#755) 2023-05-31 15:32:37 +00:00
config.go upgrade to latest dependencies (#590) 2022-01-11 19:47:50 -08:00
context.go upgrade to latest dependencies (#550) 2021-10-26 07:44:21 -07:00
doc.go upgrade to latest dependencies (#700) 2022-11-04 14:40:07 +00:00
ducks.go Auto-update dependencies (#140) 2019-11-26 10:26:22 -08:00
factories.go Migrate to knative.dev/pkg (#35) 2019-06-26 13:56:06 -07:00
health_check.go upgrade to latest dependencies (#735) 2023-03-30 12:16:02 +00:00
informers.go upgrade to latest dependencies (#877) 2024-06-26 23:36:42 +00:00
injection.go upgrade to latest dependencies (#700) 2022-11-04 14:40:07 +00:00
interface.go upgrade to latest dependencies (#755) 2023-05-31 15:32:37 +00:00

README.md

Knative Dependency Injection

This library supports the production of controller processes with minimal boilerplate outside of the reconciler implementation.

Building Controllers

To adopt this model of controller construction, implementations should start with the following controller constructor:

import (
	"context"

	"knative.dev/pkg/configmap"
	"knative.dev/pkg/controller"
	"knative.dev/pkg/logging"
	kindreconciler "knative.dev/<repo>/pkg/client/injection/reconciler/<clientgroup>/<version>/<resource>"
)

func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
	logger := logging.FromContext(ctx)

	// TODO(you): Access informers

	r := &Reconciler{
		// TODO(you): Pass listers, clients, and other stuff.
	}
	impl := kindreconciler.NewImpl(ctx, r)

	// TODO(you): Set up event handlers.

	return impl
}

Generated Reconcilers

A code generator is available for simple subset of reconciliation requirements. A label above the API type will signal to the injection code generator to generate a strongly typed reconciler. Use +genreconciler to generate the reconcilers.

// +genclient
// +genreconciler
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type ExampleType struct {
	...
}

+genreconciler will produce a helper method to get a controller impl.

Update NewController as follows:

"knative.dev/pkg/controller"
...
impl := controller.NewContext(ctx, c, controller.ControllerOptions{
	Logger: logger,
	WorkQueueName: "NameOfController",
})

becomes

kindreconciler "knative.dev/<repo>/pkg/client/injection/reconciler/<clientgroup>/<version>/<resource>"
...
impl := kindreconciler.NewImpl(ctx, c)

See Generated Reconciler Responsibilities for more information.

Implementing Reconcilers

Type Reconciler is expected to implement Reconcile:

func (r *Reconciler) Reconcile(ctx context.Context, key string) error {
	...
}

Generated Reconcilers

If generated reconcilers are used, Type Reconciler is expected to implement ReconcileKind:

func (r *Reconciler) ReconcileKind(ctx context.Context, o *samplesv1alpha1.AddressableService) reconciler.Event {
    ...
}

And if finalizers are required,

func (r *Reconciler) FinalizeKind(ctx context.Context, o *samplesv1alpha1.AddressableService) reconciler.Event {
    ...
}

See Generated Reconciler Responsibilities for more information.

Consuming Informers

Knative controllers use "informers" to set up the various event hooks needed to queue work, and pass the "listers" fed by the informers' caches to the nested "Reconciler" for accessing objects.

Our controller constructor is passed a context.Context onto which we inject any informers we access. The accessors for these informers are in little stub libraries, which we have hand rolled for Kubernetes (more on how to generate these below).

import (
	// These are how you access a client or informer off of the "ctx" passed
	// to set up the controller.
	"knative.dev/pkg/client/injection/kube/client"
	svcinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/service"

	// Other imports ...
)

func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
	logger := logging.FromContext(ctx)

	// Access informers
	svcInformer := svcinformer.Get(ctx)

	c := &Reconciler{
		// Pass the lister and client to the Reconciler.
		Client:        kubeclient.Get(ctx),
		ServiceLister: svcInformer.Lister(),
	}
	logger = logger.Named("NameOfController")
	impl := controller.NewContext(ctx, c, controller.ControllerOptions{
		Logger: logger,
		WorkQueueName: "NameOfController",
	})

	// Set up event handlers.
	svcInformer.Informer().AddEventHandler(...)

	return impl
}

How it works: by importing the accessor for a client or informer we link it and trigger the init() method for its package to run at startup. Each of these libraries registers themselves similar to our init() and controller processes can leverage this to setup and inject all of the registered things onto a context to pass to your NewController().

Testing Controllers

Similar to injection.Default, we also have injection.Fake. While linking the normal accessors sets up the former, linking their fakes set up the latter.

import (
	"testing"

	// Link the fakes for any informers our controller accesses.
	_ "knative.dev/pkg/client/injection/kube/informers/core/v1/service/fake"

	"k8s.io/client-go/rest"
	"knative.dev/pkg/injection"
	logtesting "knative.dev/pkg/logging/testing"
)

func TestFoo(t *testing.T) {
	ctx := logtesting.TestContextWithLogger(t)

	// Setup a context from all of the injected fakes.
	ctx, _ = injection.Fake.SetupInformers(ctx, &rest.Config{})
	cmw := configmap.NewStaticWatcher(...)
	ctrl := NewController(ctx, cmw)

	// Test the controller process.
}

The fake clients also support manually setting up contexts seeded with objects:

import (
	"testing"

	fakekubeclient "knative.dev/pkg/client/injection/kube/client/fake"

	"k8s.io/client-go/rest"
	"knative.dev/pkg/injection"
	logtesting "knative.dev/pkg/logging/testing"
)

func TestFoo(t *testing.T) {
	ctx := logtesting.TestContextWithLogger(t)

	objs := []runtime.Object{
		// Some list of initial objects in the client.
	}

	ctx, kubeClient := fakekubeclient.With(ctx, objs...)

	// The fake clients returned by our library are the actual fake type,
	// which enables us to access test-specific methods, e.g.
	kubeClient.AppendReactor(...)

	c := &Reconciler{
		Client: kubeClient,
	}

	// Test the reconciler...
}

Starting controllers

All we do is import the controller packages and pass their constructors along with a component name (single word) to our shared main. Then our shared main method sets it all up and runs our controllers.

package main

import (
	// The set of controllers this process will run.
	"github.com/knative/foo/pkg/reconciler/bar"
	"github.com/knative/baz/pkg/reconciler/blah"

	// This defines the shared main for injected controllers.
	"knative.dev/pkg/injection/sharedmain"
)

func main() {
	sharedmain.Main("componentname",
       bar.NewController,
       blah.NewController,
    )
}

Generating Injection Stubs.

To make generating stubs simple, we have harnessed the Kubernetes code-generation tooling to produce injection-gen. Similar to how you might ordinarily run the other foo-gen processed:

To run injection-gen you run the following (replacing the import path and api group):


KNATIVE_CODEGEN_PKG=${KNATIVE_CODEGEN_PKG:-$(cd ${REPO_ROOT}; ls -d -1 ./vendor/knative.dev/pkg 2>/dev/null || echo ../pkg)}

${KNATIVE_CODEGEN_PKG}/hack/generate-knative.sh "injection" \
  github.com/knative/sample-controller/pkg/client github.com/knative/sample-controller/pkg/apis \
  "samples:v1alpha1" \
  --go-header-file ${REPO_ROOT}/hack/boilerplate/boilerplate.go.txt

To ensure the appropriate tooling is vendored, add the following to Gopkg.toml:

required = [
  "knative.dev/pkg/codegen/cmd/injection-gen",
]

# .. Constraints

# Keeps things like the generate-knative.sh script
[[prune.project]]
  name = "knative.dev/pkg"
  unused-packages = false
  non-go = false

Generated Reconciler Responsibilities

The goal of generating the reconcilers is to provide the controller implementer a strongly typed interface, and ensure correct reconciler behaviour around status updates, Kubernetes event creation, and queue management.

We have already helped the queue management with libraries in this repo. But there was a gap in support and standards around how status updates (and retries) are performed, and when Kubernetes events are created for the resource.

The general flow with generated reconcilers looks like the following:

[k8s] -> [watches] -> [reconciler enqeueue] -> [Reconcile(key)] -> [ReconcileKind(resource)]
            ^-- you set up.                          ^-- generated       ^-- stubbed and you customize

Optionally, support for finalizers:

[Reconcile(key)] -> <resource deleted?> - no -> [ReconcileKind(resource)]
                          `
                      (optional)
                            `- yes -> [FinalizeKind(resource)]
  • ReconcileKind is only called if the resource's deletion timestamp is empty.
  • FinalizeKind is optional, and if implemented by the reconciler will be called when the resource's deletion timestamp is set.

The responsibility and consequences of using the generated ReconcileKind(resource) method are as follows:

  • In NewController, set up watches and reconciler enqueue requests as before.
  • Implementing ReconcileKind(ctx, resource) to handle active resources.
  • Implementing FinalizeKind(ctx, resource) to finalize deleting active resources.
    • NOTE: Implementing FinalizeKind will result in the reconciler using finalizers on the resource.
  • Resulting changes from Reconcile calling ReconcileKind(ctx, resource):
    • DO NOT edit the spec of resource, it will be ignored.
    • DO NOT edit the metadata of resource, it will be ignored.
    • If resource.status is changed, Reconcile will synchronize it back to the API Server.
      • Note: the watches setup for resource.Kind will see the update to status and cause another reconciliation.
  • ReconcileKind(ctx, resource) returns a reconciler.Event results in:
  • If event is an error (reconciler.Event extends error internally), Reconciler will produce a Warning kubernetes event with reason InternalError and the body of the error as the message.
    • Additionally, the error will be returned from Reconciler and key will requeue back into the reconciler key queue.
  • If event is a reconciler.Event, Reconciler will log a typed and reasoned Kubernetes Event based on the contents of event.
    • event is not considered an error for requeue and nil is returned from Reconciler.
  • If additional events are required to be produced, an implementation can pull a recorder from the context: recorder := controller.GetEventRecorder(ctx).

Future features to be considered:

  • Document how we leverage configStore and specifically ctx = r.configStore.ToContext(ctx) inside Reconcile.
  • Adjust +genreconciler to allow for generated reconcilers to be made without annotating the type struct.
  • Add class-based annotation filtering.

ConfigStore

Config store is used to decorate the context with a snapshot of configmaps to be used in a reconciler method.

To add this feature to the generated reconciler, it will have to be passed in on reconciler<kind>.NewImpl like so:

kindreconciler "knative.dev/<repo>/pkg/client/injection/reconciler/<clientgroup>/<version>/<resource>"
...
impl := kindreconciler.NewImpl(ctx, c, func(impl *controller.Impl) controller.Options {
	// Setup options that require access to a controller.Impl.
	configsToResync := []interface{}{
		&some.Config{},
	}
	resyncOnConfigChange := configmap.TypeFilter(configsToResync...)(func(string, interface{}) {
		impl.FilteredGlobalResync(myFilterFunc, kindInformer.Informer())
	})
	configStore := config.NewStore(c.Logger.Named("config-store"), resyncOnConfigChange)
	configStore.WatchConfigs(cmw)

	// Return the controller options.
	return controller.Options{
		ConfigStore: configStore,
	}
})

Filtering on controller promotion

The generated controllers implement the LeaderAwareFuncs interface to support HA controller deployments. When a generated controller is promoted, by default it will trigger a re-reconcile of every resource it manages. To filter which objects get reconciled, pass a PromoteFilterFunc to the controller's constructor:

kindreconciler "knative.dev/<repo>/pkg/client/injection/reconciler/<clientgroup>/<version>/<resource>"
pkgreconciler "knative.dev/pkg/reconciler"
...
impl := kindreconciler.NewImpl(ctx, c, func(impl *controller.Impl) controller.Options {
	return controller.Options{
		PromoteFilterFunc: pkgreconciler.LabelFilterFunc("mylabel", "myvalue", false),
	}
})

Artifacts

The artifacts are targeted to the configured client/injection directory:

kindreconciler "knative.dev/<repo>/pkg/client/injection/reconciler/<clientgroup>/<version>/<kind>"

Controller related artifacts:

  • NewImpl - gets an injection based client and lister for <kind>, sets up Kubernetes Event recorders, and delegates to controller.NewContext for queue management.
impl := reconciler.NewImpl(ctx, reconcilerInstance)

Reconciler related artifacts:

  • Interface - defines the strongly typed interfaces to be implemented by a controller reconciling <kind>.
// Check that our Reconciler implements Interface
var _ addressableservicereconciler.Interface = (*Reconciler)(nil)
  • Finalizer - defines the strongly typed interfaces to be implemented by a controller finalizing <kind>.
// Check that our Reconciler implements Interface
var _ addressableservicereconciler.Finalizer = (*Reconciler)(nil)

Annotation based class filters

Sometimes a reconciler only wants to reconcile a class of resource identified by a special annotation on the Custom Resource.

This behavior can be enabled in the generators by adding the annotation class key to the type struct:

// +genreconciler:class=example.com/filter.class

The genreconciler generator code will now have the addition of classValue string to NewImpl and NewReconciler (for tests):

NewImpl(ctx context.Context, r Interface, classValue string, optionsFns ...controller.OptionsFn) *controller.Impl
NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister pubv1alpha1.BarLister, recorder record.EventRecorder, r Interface, classValue string, options ...controller.Options) controller.Reconciler

ReconcileKind and FinalizeKind will NOT be called for resources that DO NOT have the provided +genreconciler:class=<key> key annotation. Additionally the value of the <key> annotation on a resource must match the value provided to NewImpl (or NewReconcile) for ReconcileKind or FinalizeKind to be called for that resource.

Annotation based common logic

krshapedlogic=false may be used to omit common reconciler logic

Reconcilers can handle common logic for resources that conform to the KRShaped interface. This allows the generated code to automatically increment ObservedGeneration.

// +genreconciler

Setting this annotation will emit the following in the generated reconciler.

reconciler.PreProcessReconcile(ctx, resource)

reconcileEvent = r.reconciler.ReconcileKind(ctx, resource)

reconciler.PostProcessReconcile(ctx, resource, oldResource)

Stubs

To enable stubs generation, add the stubs flag:

// +genreconciler:stubs

Or with the class annotation:

// +genreconciler:class=example.com/filter.class,stubs

The stubs are intended to be used to get started, or to use as reference. It is intended to be copied out of the client dir.

knative.dev/<repo>/pkg/client/injection/reconciler/<clientgroup>/<version>/<kind>/stubs/controller.go

  • A basic implementation of NewController.

knative.dev/<repo>/pkg/client/injection/reconciler/<clientgroup>/<version>/<kind>/stubs/reconciler.go

  • A basic implementation of type Reconciler struct {} and Reconciler.ReconcileKind.
  • A commented out example of a basic implementation of Reconciler.FinalizeKind.
  • An example reconciler.Event: newReconciledNormal

Examples

Please look at sample-controller or sample-source for working integrations of the generated geconciler code.