The builder package provides a fluent API for constructing controllers and webhooks with minimal boilerplate code. It simplifies the process of setting up watches, event handlers, predicates, and other controller configuration.
The builder pattern allows you to:
Import Path: sigs.k8s.io/controller-runtime/pkg/builder
package builder
import (
"github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
)
// Builder builds a Controller
type Builder = TypedBuilder[reconcile.Request]// ControllerManagedBy returns a new controller builder that will be started by the provided Manager
func ControllerManagedBy(m manager.Manager) *Builder// TypedBuilder builds a controller with custom request types
type TypedBuilder[request comparable] struct {
// Has unexported fields
}
// TypedControllerManagedBy returns a new typed controller builder
func TypedControllerManagedBy[request comparable](m manager.Manager) *TypedBuilder[request]// Named sets the name of the controller
func (blder *TypedBuilder[request]) Named(name string) *TypedBuilder[request]// For defines the type of Object being reconciled (the primary resource)
func (blder *TypedBuilder[request]) For(object client.Object, opts ...ForOption) *TypedBuilder[request]// Owns defines types of Objects being generated by the controller (secondary resources)
func (blder *TypedBuilder[request]) Owns(object client.Object, opts ...OwnsOption) *TypedBuilder[request]// Watches watches arbitrary objects and enqueues reconcile requests
func (blder *TypedBuilder[request]) Watches(
object client.Object,
eventHandler handler.TypedEventHandler[client.Object, request],
opts ...WatchesOption,
) *TypedBuilder[request]// WatchesMetadata watches metadata-only objects
func (blder *TypedBuilder[request]) WatchesMetadata(
object client.Object,
eventHandler handler.TypedEventHandler[client.Object, request],
opts ...WatchesOption,
) *TypedBuilder[request]// WatchesRawSource watches a raw source of events
func (blder *TypedBuilder[request]) WatchesRawSource(src source.TypedSource[request]) *TypedBuilder[request]// WithEventFilter sets predicates to filter events before enqueuing the object
func (blder *TypedBuilder[request]) WithEventFilter(p predicate.Predicate) *TypedBuilder[request]// WithOptions overrides the controller options
func (blder *TypedBuilder[request]) WithOptions(options controller.TypedOptions[request]) *TypedBuilder[request]// WithLogConstructor overrides the controller logger constructor
func (blder *TypedBuilder[request]) WithLogConstructor(logConstructor func(*request) logr.Logger) *TypedBuilder[request]// Complete builds the controller and adds it to the manager
func (blder *TypedBuilder[request]) Complete(r reconcile.TypedReconciler[request]) error// Build builds the controller but does not add it to the manager
func (blder *TypedBuilder[request]) Build(r reconcile.TypedReconciler[request]) (controller.TypedController[request], error)// ForInput represents the information set by the For method
type ForInput struct {
// Has unexported fields
}
// ForOption is configuration that modifies options for a For request
type ForOption interface {
ApplyToFor(*ForInput)
}// OwnsInput represents the information set by Owns method
type OwnsInput struct {
// Has unexported fields
}
// OwnsOption is configuration that modifies options for an owns request
type OwnsOption interface {
ApplyToOwns(*OwnsInput)
}// WatchesInput represents the information set by Watches method
type WatchesInput[request comparable] struct {
// Has unexported fields
}
// WatchesOption is configuration that modifies options for a watches request
type WatchesOption interface {
ApplyToWatches(untypedWatchesInput)
}// Predicates filters events with predicates
type Predicates struct {
// Has unexported fields
}
// WithPredicates creates a Predicates option
func WithPredicates(predicates ...predicate.Predicate) Predicates
func (w Predicates) ApplyToFor(opts *ForInput)
func (w Predicates) ApplyToOwns(opts *OwnsInput)
func (w Predicates) ApplyToWatches(opts untypedWatchesInput)var (
// OnlyMetadata tells the controller to only cache metadata
OnlyMetadata projectionOption
// MatchEveryOwner determines whether the watch should be filtered based on controller ownership
MatchEveryOwner ownerMatcher
)package controllers
import (
"context"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
)
type DeploymentReconciler struct {
client.Client
Scheme *runtime.Scheme
}
func (r *DeploymentReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
// Reconciliation logic here
return reconcile.Result{}, nil
}
func (r *DeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
Named("deployment-controller").
For(&appsv1.Deployment{}).
Owns(&corev1.Pod{}).
Watches(
&corev1.ConfigMap{},
handler.EnqueueRequestsFromMapFunc(r.configMapToDeployment),
).
WithEventFilter(predicate.GenerationChangedPredicate{}).
WithOptions(controller.Options{
MaxConcurrentReconciles: 2,
}).
Complete(r)
}
func (r *DeploymentReconciler) configMapToDeployment(ctx context.Context, obj client.Object) []reconcile.Request {
// Map ConfigMap to Deployment reconcile requests
return []reconcile.Request{
{NamespacedName: client.ObjectKey{
Namespace: obj.GetNamespace(),
Name: obj.GetName(),
}},
}
}package controllers
import (
"context"
corev1 "k8s.io/api/core/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
type PodReconciler struct {
client.Client
}
func (r *PodReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
// Reconciliation logic
return reconcile.Result{}, nil
}
func (r *PodReconciler) SetupWithManager(mgr ctrl.Manager) error {
// Custom predicate to only watch Running pods
runningPodPredicate := predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool {
pod := e.Object.(*corev1.Pod)
return pod.Status.Phase == corev1.PodRunning
},
UpdateFunc: func(e event.UpdateEvent) bool {
newPod := e.ObjectNew.(*corev1.Pod)
oldPod := e.ObjectOld.(*corev1.Pod)
return newPod.Status.Phase == corev1.PodRunning &&
oldPod.Status.Phase != corev1.PodRunning
},
}
return ctrl.NewControllerManagedBy(mgr).
For(&corev1.Pod{}, builder.WithPredicates(runningPodPredicate)).
Complete(r)
}package controllers
import (
"context"
corev1 "k8s.io/api/core/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// Custom request type
type PodRequest struct {
Namespace string
Name string
NodeName string
}
type TypedPodReconciler struct {
client.Client
}
func (r *TypedPodReconciler) Reconcile(ctx context.Context, req PodRequest) (reconcile.Result, error) {
// Use custom request fields
log.Info("Reconciling pod", "namespace", req.Namespace, "name", req.Name, "node", req.NodeName)
return reconcile.Result{}, nil
}
func (r *TypedPodReconciler) SetupWithManager(mgr ctrl.Manager) error {
return builder.TypedControllerManagedBy[PodRequest](mgr).
For(&corev1.Pod{}).
Complete(r)
}Import Path: sigs.k8s.io/controller-runtime/pkg/builder
// WebhookBuilder builds a Webhook
type WebhookBuilder struct {
// Has unexported fields
}
// WebhookManagedBy returns a new webhook builder that will be started by the provided Manager
func WebhookManagedBy(m manager.Manager) *WebhookBuilder// For defines the type of object the webhook is for
func (blder *WebhookBuilder) For(apiType runtime.Object) *WebhookBuilder// WithDefaulter sets the webhook to use a CustomDefaulter
func (blder *WebhookBuilder) WithDefaulter(defaulter admission.CustomDefaulter, opts ...admission.DefaulterOption) *WebhookBuilder// WithDefaulterCustomPath sets a custom path for the defaulting webhook
func (blder *WebhookBuilder) WithDefaulterCustomPath(customPath string) *WebhookBuilder// WithValidator sets the webhook to use a CustomValidator
func (blder *WebhookBuilder) WithValidator(validator admission.CustomValidator) *WebhookBuilder// WithValidatorCustomPath sets a custom path for the validating webhook
func (blder *WebhookBuilder) WithValidatorCustomPath(customPath string) *WebhookBuilder// RecoverPanic indicates whether panics caused by the webhook should be recovered
func (blder *WebhookBuilder) RecoverPanic(recoverPanic bool) *WebhookBuilder// WithLogConstructor overrides the webhook logger constructor
func (blder *WebhookBuilder) WithLogConstructor(logConstructor func(base logr.Logger, req *admission.Request) logr.Logger) *WebhookBuilder// Complete builds the webhook and adds it to the manager
func (blder *WebhookBuilder) Complete() error// WithCustomPath sets a custom path for the webhook
// Deprecated: Use WithDefaulterCustomPath or WithValidatorCustomPath instead
func (blder *WebhookBuilder) WithCustomPath(customPath string) *WebhookBuilderpackage webhooks
import (
"context"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
type PodWebhook struct{}
// Default implements admission.CustomDefaulter
func (w *PodWebhook) Default(ctx context.Context, obj runtime.Object) error {
pod := obj.(*corev1.Pod)
// Set default values
if pod.Spec.RestartPolicy == "" {
pod.Spec.RestartPolicy = corev1.RestartPolicyAlways
}
return nil
}
// ValidateCreate implements admission.CustomValidator
func (w *PodWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
pod := obj.(*corev1.Pod)
// Validation logic
if len(pod.Spec.Containers) == 0 {
return nil, fmt.Errorf("pod must have at least one container")
}
return nil, nil
}
// ValidateUpdate implements admission.CustomValidator
func (w *PodWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
newPod := newObj.(*corev1.Pod)
oldPod := oldObj.(*corev1.Pod)
// Prevent changing pod name
if newPod.Name != oldPod.Name {
return nil, fmt.Errorf("pod name cannot be changed")
}
return nil, nil
}
// ValidateDelete implements admission.CustomValidator
func (w *PodWebhook) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
// Deletion validation logic
return nil, nil
}
func (w *PodWebhook) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(&corev1.Pod{}).
WithDefaulter(w).
WithValidator(w).
RecoverPanic(true).
Complete()
}package webhooks
import (
"context"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
)
type PodDefaulter struct{}
func (d *PodDefaulter) Default(ctx context.Context, obj runtime.Object) error {
pod := obj.(*corev1.Pod)
// Set defaults
if pod.Spec.RestartPolicy == "" {
pod.Spec.RestartPolicy = corev1.RestartPolicyAlways
}
return nil
}
type PodValidator struct{}
func (v *PodValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
// Validation logic
return nil, nil
}
func (v *PodValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
// Validation logic
return nil, nil
}
func (v *PodValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
// Validation logic
return nil, nil
}
func SetupWebhooks(mgr ctrl.Manager) error {
// Setup defaulter webhook
if err := ctrl.NewWebhookManagedBy(mgr).
For(&corev1.Pod{}).
WithDefaulter(&PodDefaulter{}).
WithDefaulterCustomPath("/mutate-v1-pod").
Complete(); err != nil {
return err
}
// Setup validator webhook
if err := ctrl.NewWebhookManagedBy(mgr).
For(&corev1.Pod{}).
WithValidator(&PodValidator{}).
WithValidatorCustomPath("/validate-v1-pod").
Complete(); err != nil {
return err
}
return nil
}ctrl.NewControllerManagedBy(mgr).
Named("my-controller"). // Helps with logging and metrics
For(&MyResource{}).
Complete(r)ctrl.NewControllerManagedBy(mgr).
For(&MyResource{}, builder.WithPredicates(
predicate.GenerationChangedPredicate{},
)).
Complete(r)ctrl.NewControllerManagedBy(mgr).
For(&ParentResource{}).
Owns(&ChildResource{}). // Automatically watches children with owner references
Complete(r)ctrl.NewControllerManagedBy(mgr).
For(&MyResource{}).
WithOptions(controller.Options{
MaxConcurrentReconciles: 5,
}).
Complete(r)ctrl.NewControllerManagedBy(mgr).
For(&PrimaryResource{}).
Watches(
&SecondaryResource{},
handler.EnqueueRequestsFromMapFunc(myMappingFunction),
builder.WithPredicates(myPredicate),
).
Complete(r)