or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

builder.mdclient-cache.mdcore-framework.mdevent-handling.mdindex.mdsupporting-services.mdtesting.mdutilities.mdwebhooks.md
tile.json

builder.mddocs/

Builder API

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.

Overview

The builder pattern allows you to:

  • Create controllers with a simple, chainable API
  • Set up watches for primary resources and owned resources
  • Configure event handlers and predicates
  • Build webhook servers with minimal code
  • Use generic typed builders for custom request types

Builder Package

Import Path: sigs.k8s.io/controller-runtime/pkg/builder

Controller 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]

Creating a Controller Builder

// ControllerManagedBy returns a new controller builder that will be started by the provided Manager
func ControllerManagedBy(m manager.Manager) *Builder

TypedBuilder

// 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]

Builder Methods

Named

// Named sets the name of the controller
func (blder *TypedBuilder[request]) Named(name string) *TypedBuilder[request]

For

// For defines the type of Object being reconciled (the primary resource)
func (blder *TypedBuilder[request]) For(object client.Object, opts ...ForOption) *TypedBuilder[request]

Owns

// 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 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

// WatchesMetadata watches metadata-only objects
func (blder *TypedBuilder[request]) WatchesMetadata(
    object client.Object,
    eventHandler handler.TypedEventHandler[client.Object, request],
    opts ...WatchesOption,
) *TypedBuilder[request]

WatchesRawSource

// WatchesRawSource watches a raw source of events
func (blder *TypedBuilder[request]) WatchesRawSource(src source.TypedSource[request]) *TypedBuilder[request]

WithEventFilter

// WithEventFilter sets predicates to filter events before enqueuing the object
func (blder *TypedBuilder[request]) WithEventFilter(p predicate.Predicate) *TypedBuilder[request]

WithOptions

// WithOptions overrides the controller options
func (blder *TypedBuilder[request]) WithOptions(options controller.TypedOptions[request]) *TypedBuilder[request]

WithLogConstructor

// WithLogConstructor overrides the controller logger constructor
func (blder *TypedBuilder[request]) WithLogConstructor(logConstructor func(*request) logr.Logger) *TypedBuilder[request]

Complete

// Complete builds the controller and adds it to the manager
func (blder *TypedBuilder[request]) Complete(r reconcile.TypedReconciler[request]) error

Build

// 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)

For Options

// 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)
}

Owns Options

// 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)
}

Watches Options

// 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)
}

Common Options

WithPredicates

// 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)

Special Variables

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
)

Controller Builder Example

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(),
        }},
    }
}

Advanced Builder Example

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)
}

Typed Builder Example

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)
}

Webhook Builder

Import Path: sigs.k8s.io/controller-runtime/pkg/builder

WebhookBuilder

// 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

Webhook Builder Methods

For

// For defines the type of object the webhook is for
func (blder *WebhookBuilder) For(apiType runtime.Object) *WebhookBuilder

WithDefaulter

// WithDefaulter sets the webhook to use a CustomDefaulter
func (blder *WebhookBuilder) WithDefaulter(defaulter admission.CustomDefaulter, opts ...admission.DefaulterOption) *WebhookBuilder

WithDefaulterCustomPath

// WithDefaulterCustomPath sets a custom path for the defaulting webhook
func (blder *WebhookBuilder) WithDefaulterCustomPath(customPath string) *WebhookBuilder

WithValidator

// WithValidator sets the webhook to use a CustomValidator
func (blder *WebhookBuilder) WithValidator(validator admission.CustomValidator) *WebhookBuilder

WithValidatorCustomPath

// WithValidatorCustomPath sets a custom path for the validating webhook
func (blder *WebhookBuilder) WithValidatorCustomPath(customPath string) *WebhookBuilder

RecoverPanic

// RecoverPanic indicates whether panics caused by the webhook should be recovered
func (blder *WebhookBuilder) RecoverPanic(recoverPanic bool) *WebhookBuilder

WithLogConstructor

// WithLogConstructor overrides the webhook logger constructor
func (blder *WebhookBuilder) WithLogConstructor(logConstructor func(base logr.Logger, req *admission.Request) logr.Logger) *WebhookBuilder

Complete

// Complete builds the webhook and adds it to the manager
func (blder *WebhookBuilder) Complete() error

Deprecated Methods

// WithCustomPath sets a custom path for the webhook
// Deprecated: Use WithDefaulterCustomPath or WithValidatorCustomPath instead
func (blder *WebhookBuilder) WithCustomPath(customPath string) *WebhookBuilder

Webhook Builder Example

package 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()
}

Multiple Webhooks Example

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
}

Builder Best Practices

1. Use Named Controllers

ctrl.NewControllerManagedBy(mgr).
    Named("my-controller"). // Helps with logging and metrics
    For(&MyResource{}).
    Complete(r)

2. Apply Predicates to Reduce Reconciliations

ctrl.NewControllerManagedBy(mgr).
    For(&MyResource{}, builder.WithPredicates(
        predicate.GenerationChangedPredicate{},
    )).
    Complete(r)

3. Use Owner References for Related Resources

ctrl.NewControllerManagedBy(mgr).
    For(&ParentResource{}).
    Owns(&ChildResource{}). // Automatically watches children with owner references
    Complete(r)

4. Configure Concurrency for Performance

ctrl.NewControllerManagedBy(mgr).
    For(&MyResource{}).
    WithOptions(controller.Options{
        MaxConcurrentReconciles: 5,
    }).
    Complete(r)

5. Use Custom Event Handlers for Complex Mappings

ctrl.NewControllerManagedBy(mgr).
    For(&PrimaryResource{}).
    Watches(
        &SecondaryResource{},
        handler.EnqueueRequestsFromMapFunc(myMappingFunction),
        builder.WithPredicates(myPredicate),
    ).
    Complete(r)