builder
These implementation notes and diagrams target controller-runtime v0.25.1, pinned in the repository's go.mod. Use the signatures and call paths below for this version.
Overview
The main role of Builder is:
- Create a Controller from the given Reconciler
- Configure target resources for the controller
- Register the controller to the Manager
About how the registered controllers are triggered, you can study in Manager. The controller is registered in the Manager's LeaderElection group by default. Controllers opting out of leader election are in Others. Both are started by Manager.Start() after cache readiness.
Types
Builder
type Builder = TypedBuilder[reconcile.Request]
type TypedBuilder[request comparable] struct {
forInput ForInput
ownsInput []OwnsInput
rawSources []source.TypedSource[request]
watchesInput []WatchesInput[request]
mgr manager.Manager
globalPredicates []predicate.Predicate
ctrl controller.TypedController[request]
ctrlOptions controller.TypedOptions[request]
name string
newController func(name string, mgr manager.Manager, options controller.TypedOptions[request]) (controller.TypedController[request], error)
}
ControllerManagedBy: Initialize Builder with a Manager
Initialize a Builder with the specified manager.
func ControllerManagedBy(m manager.Manager) *Builder {
return TypedControllerManagedBy[reconcile.Request](m)
}
For, Owns, and Watches: Define what object to watch
For(object client.Object, opts ...ForOption) *Builder: only one resource can be configured. Same asWatches(&corev1.Pod{}, &handler.EnqueueRequestForObject{})Owns(object client.Object, opts ...OwnsOption) *Builder: Owns defines types of Objects being generated by the ControllerManagedBy, and configures the ControllerManagedBy to respond to create / delete / update events by reconciling the owner object. Same as the following code:EnqueueRequestForOwner: Extract owner object from ownerReferences and enqueue it to the queue.Watches(object, handler.EnqueueRequestForOwner(mgr.GetScheme(), mgr.GetRESTMapper(), ownerType, handler.OnlyControllerOwner()))Watches(object client.Object, eventhandler handler.TypedEventHandler[client.Object, request], opts ...WatchesOption) *TypedBuilder[request]: Watches exposes the lower-level ControllerManagedBy Watches functions through the builder. Consider using Owns or For instead of Watches directly.
Example:
err = builder.
ControllerManagedBy(mgr). // Create the ControllerManagedBy
For(&alpha1v1.Foo{}). // Foo is the Application API
Owns(&corev1.Pod{}). // Foo owns Pods created by it
Complete(&FooReconciler{})
Complete: Receive reconciler and build a controller
Receive reconcile.Reconciler and call Build:
func (blder *TypedBuilder[request]) Complete(r reconcile.TypedReconciler[request]) error {
_, err := blder.Build(r)
return err
}
Build: Create a controller and return the controller.
func (blder *TypedBuilder[request]) Build(r reconcile.TypedReconciler[request]) (controller.TypedController[request], error) {
if r == nil {
return nil, fmt.Errorf("must provide a non-nil Reconciler")
}
if blder.mgr == nil {
return nil, fmt.Errorf("must provide a non-nil Manager")
}
if blder.forInput.err != nil {
return nil, blder.forInput.err
}
if err := blder.doController(r); err != nil {
return nil, err
}
if err := blder.doWatch(); err != nil {
return nil, err
}
return blder.ctrl, nil
}
- bldr.doController to construct the controller and register it with the Manager
- Create a new controller.
blder.ctrl, err = controller.NewTyped(controllerName, blder.mgr, ctrlOptions) - the controller is added to the appropriate Manager runnable group by
Manager.Add(Runnable)innewController. (controller)
- Create a new controller.
- bldr.doWatch to register watches for the target resources configured by
For,Owns, andWatches.- The actual implementation of
Watchfunction is in the controller
- The actual implementation of
Convert client.Object to Source
- Controller.Watch needs
Sourceas the first argument.Watch(src source.TypedSource[request]) error client.Objectis set inForInput,OwnsInput, andWatchesInputforFor,Owns, andWatchesrespectively.-
Before calling
Controller.Watch, theclient.Objectneeds to be projected into Source based onobjectProjection:projectAsNormal: Use the object as it is. (In most cases)projectAsMetadata: Extract only metadata.
typeForSrc, err := blder.project(blder.forInput.object, blder.forInput.objectProjection) if err != nil { return err } hdlr := &handler.EnqueueRequestForObject{} src := source.Kind(blder.mgr.GetCache(), typeForSrc, hdlr, allPredicates...) if err := blder.ctrl.Watch(src); err != nil { return err }source.Kindconstructs an internal Kind, which implements the Source interface.type Kind[object client.Object, request comparable] struct { Type object Cache cache.Cache Handler handler.TypedEventHandler[object, request] Predicates []predicate.TypedPredicate[object] startedErr chan error startCancel func() }
For uses the object's own key; Owns resolves ownerReferences and enqueues the owner's key; Watches uses your supplied mapping handler. Merely matching labels does not establish ownership. Predicates run before the handler, so filtering a Delete or an initial Add can suppress a needed reconciliation.
WatchesRawSource accepts an already configured source, such as a Channel, and does not automatically apply the builder's global event filter. Metadata projection watches PartialObjectMetadata: use that same representation for reads if you intend to avoid starting an additional typed informer.
Complete returns only the construction error; Build also returns the Controller. Both must be checked. The example's Foo type and FooReconciler stand for your own API and implementation.