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

utilities.mddocs/

Utilities

Utility packages for common operations including finalizer management, scheme building, configuration, certificate watching for webhook servers, and controller utilities.

Overview

Controller-runtime provides several utility packages:

  1. Finalizers - Manage object finalizers for cleanup logic
  2. Controller Utilities - Owner references, Create-or-Update patterns
  3. Scheme Building - Construct runtime schemes
  4. Configuration - Controller configuration options
  5. Certificate Watching - Auto-reload TLS certificates for webhooks
  6. Priority Queue - Priority-based work queues

Finalizer Package

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

The finalizer package provides structured finalizer management.

Types

package finalizer

import (
    "context"

    "sigs.k8s.io/controller-runtime/pkg/client"
)

// Finalizer manages adding and removing finalizers
type Finalizer interface {
    // Finalize adds or removes a finalizer based on deletion timestamp
    Finalize(context.Context, client.Object) (Result, error)
}

// Registerer registers finalizers
type Registerer interface {
    // Register registers a finalizer by key
    Register(key string, f Finalizer) error
}

// Finalizers combines Finalizer and Registerer
type Finalizers interface {
    Registerer
    Finalizer
}

// NewFinalizers creates a new Finalizers instance
func NewFinalizers() Finalizers

Result

// Result holds information about what was updated by finalizers
type Result struct {
    // Updated indicates if the object was updated
    Updated bool

    // StatusUpdated indicates if the status was updated
    StatusUpdated bool
}

Finalizer Usage Example

package controllers

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/client"
    "sigs.k8s.io/controller-runtime/pkg/finalizer"
    "sigs.k8s.io/controller-runtime/pkg/reconcile"
)

type MyReconciler struct {
    client.Client
    Scheme     *runtime.Scheme
    Finalizers finalizer.Finalizers
}

type myFinalizer struct {
    client.Client
}

func (f *myFinalizer) Finalize(ctx context.Context, obj client.Object) (finalizer.Result, error) {
    pod := obj.(*corev1.Pod)

    // Perform cleanup logic
    log.Info("cleaning up resources for pod", "name", pod.Name)

    // Clean up external resources
    if err := cleanupExternalResources(ctx, pod); err != nil {
        return finalizer.Result{}, err
    }

    return finalizer.Result{Updated: true}, nil
}

func (r *MyReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
    var pod corev1.Pod
    if err := r.Get(ctx, req.NamespacedName, &pod); err != nil {
        return reconcile.Result{}, client.IgnoreNotFound(err)
    }

    // Run finalizers
    result, err := r.Finalizers.Finalize(ctx, &pod)
    if err != nil {
        return reconcile.Result{}, err
    }

    if result.Updated {
        // Update the object if finalizers modified it
        if err := r.Update(ctx, &pod); err != nil {
            return reconcile.Result{}, err
        }
    }

    return reconcile.Result{}, nil
}

func (r *MyReconciler) SetupWithManager(mgr ctrl.Manager) error {
    // Create and register finalizers
    r.Finalizers = finalizer.NewFinalizers()
    if err := r.Finalizers.Register("my-finalizer", &myFinalizer{Client: r.Client}); err != nil {
        return err
    }

    return ctrl.NewControllerManagedBy(mgr).
        For(&corev1.Pod{}).
        Complete(r)
}

ControllerUtil Package

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

Utilities for common controller operations.

Finalizer Functions

package controllerutil

import (
    "context"

    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "sigs.k8s.io/controller-runtime/pkg/client"
)

// AddFinalizer adds a finalizer to the object
func AddFinalizer(o client.Object, finalizer string) (finalizersUpdated bool)

// RemoveFinalizer removes a finalizer from the object
func RemoveFinalizer(o client.Object, finalizer string) (finalizersUpdated bool)

// ContainsFinalizer checks if the object has a specific finalizer
func ContainsFinalizer(o client.Object, finalizer string) bool

Owner Reference Functions

// SetControllerReference sets the controller owner reference
func SetControllerReference(owner, controlled metav1.Object, scheme *runtime.Scheme, opts ...OwnerReferenceOption) error

// SetOwnerReference sets an owner reference (not necessarily controller)
func SetOwnerReference(owner, object metav1.Object, scheme *runtime.Scheme, opts ...OwnerReferenceOption) error

// HasControllerReference checks if an object has a controller reference
func HasControllerReference(object metav1.Object) bool

// HasOwnerReference checks if an object has a specific owner reference
func HasOwnerReference(ownerRefs []metav1.OwnerReference, obj client.Object, scheme *runtime.Scheme) (bool, error)

// RemoveControllerReference removes the controller owner reference
func RemoveControllerReference(owner, object metav1.Object, scheme *runtime.Scheme) error

// RemoveOwnerReference removes an owner reference
func RemoveOwnerReference(owner, object metav1.Object, scheme *runtime.Scheme) error

Owner Reference Options

// OwnerReferenceOption modifies OwnerReference behavior
type OwnerReferenceOption func(*metav1.OwnerReference)

// WithBlockOwnerDeletion sets the BlockOwnerDeletion field
func WithBlockOwnerDeletion(blockOwnerDeletion bool) OwnerReferenceOption

Create or Update Functions

// CreateOrUpdate creates or updates the given object in the Kubernetes cluster
func CreateOrUpdate(ctx context.Context, c client.Client, obj client.Object, f MutateFn) (OperationResult, error)

// CreateOrPatch creates or patches the given object in the Kubernetes cluster
func CreateOrPatch(ctx context.Context, c client.Client, obj client.Object, f MutateFn) (OperationResult, error)

// MutateFn is a function that modifies an object
type MutateFn func() error

Operation Result

// OperationResult is the result of a CreateOrUpdate or CreateOrPatch operation
type OperationResult string

const (
    OperationResultNone              OperationResult = "unchanged"
    OperationResultCreated           OperationResult = "created"
    OperationResultUpdated           OperationResult = "updated"
    OperationResultUpdatedStatus     OperationResult = "updatedStatus"
    OperationResultUpdatedStatusOnly OperationResult = "updatedStatusOnly"
)

Errors

// AlreadyOwnedError is returned when an object is already owned by another controller
type AlreadyOwnedError struct {
    Object metav1.Object
    Owner  metav1.OwnerReference
}

func (e *AlreadyOwnedError) Error() string

ControllerUtil Usage Example

package controllers

import (
    "context"

    appsv1 "k8s.io/api/apps/v1"
    corev1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/client"
    "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    "sigs.k8s.io/controller-runtime/pkg/reconcile"
)

type DeploymentReconciler struct {
    client.Client
    Scheme *runtime.Scheme
}

func (r *DeploymentReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
    var deployment appsv1.Deployment
    if err := r.Get(ctx, req.NamespacedName, &deployment); err != nil {
        return reconcile.Result{}, client.IgnoreNotFound(err)
    }

    // Add finalizer
    if !controllerutil.ContainsFinalizer(&deployment, "my-finalizer") {
        controllerutil.AddFinalizer(&deployment, "my-finalizer")
        if err := r.Update(ctx, &deployment); err != nil {
            return reconcile.Result{}, err
        }
    }

    // Check if being deleted
    if !deployment.DeletionTimestamp.IsZero() {
        // Perform cleanup
        if controllerutil.ContainsFinalizer(&deployment, "my-finalizer") {
            // Do cleanup work
            if err := r.cleanupResources(ctx, &deployment); err != nil {
                return reconcile.Result{}, err
            }

            // Remove finalizer
            controllerutil.RemoveFinalizer(&deployment, "my-finalizer")
            if err := r.Update(ctx, &deployment); err != nil {
                return reconcile.Result{}, err
            }
        }
        return reconcile.Result{}, nil
    }

    // Create or update a ConfigMap owned by this Deployment
    configMap := &corev1.ConfigMap{
        ObjectMeta: metav1.ObjectMeta{
            Name:      deployment.Name + "-config",
            Namespace: deployment.Namespace,
        },
    }

    result, err := controllerutil.CreateOrUpdate(ctx, r.Client, configMap, func() error {
        // Set desired state
        configMap.Data = map[string]string{
            "key": "value",
        }

        // Set owner reference
        return controllerutil.SetControllerReference(&deployment, configMap, r.Scheme)
    })
    if err != nil {
        return reconcile.Result{}, err
    }

    log.Info("ConfigMap operation", "result", result)

    return reconcile.Result{}, nil
}

func (r *DeploymentReconciler) cleanupResources(ctx context.Context, deployment *appsv1.Deployment) error {
    // Cleanup logic
    return nil
}

Scheme Package

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

Utilities for building runtime schemes.

Builder

package scheme

import (
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/runtime/schema"
)

// Builder builds a Scheme for mapping Go types to Kubernetes GroupVersionKinds
type Builder struct {
    GroupVersion schema.GroupVersion
    runtime.SchemeBuilder
}

// Register registers objects with the scheme builder
func (bld *Builder) Register(object ...runtime.Object) *Builder

// RegisterAll registers all objects from another builder
func (bld *Builder) RegisterAll(b *Builder) *Builder

// AddToScheme adds all registered types to the scheme
func (bld *Builder) AddToScheme(s *runtime.Scheme) error

// Build creates a new scheme with all registered types
func (bld *Builder) Build() (*runtime.Scheme, error)

Scheme Usage Example

package api

import (
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "sigs.k8s.io/controller-runtime/pkg/scheme"
)

var (
    // GroupVersion is group version used to register these objects
    GroupVersion = schema.GroupVersion{Group: "myapp.example.com", Version: "v1"}

    // SchemeBuilder is used to add go types to the GroupVersionKind scheme
    SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}

    // AddToScheme adds the types in this group-version to the given scheme
    AddToScheme = SchemeBuilder.AddToScheme
)

func init() {
    SchemeBuilder.Register(&MyResource{}, &MyResourceList{})
}

// Usage in main.go
func main() {
    scheme := runtime.NewScheme()

    // Add built-in types
    _ = clientgoscheme.AddToScheme(scheme)

    // Add custom types
    _ = api.AddToScheme(scheme)

    mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
        Scheme: scheme,
    })
    // ...
}

Config Package

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

Configuration options for controllers.

Controller Configuration

package config

import (
    "time"

    "github.com/go-logr/logr"
)

// Controller contains configuration options for controllers
type Controller struct {
    // SkipNameValidation allows skipping controller name validation
    SkipNameValidation *bool

    // GroupKindConcurrency maps GroupKind to the number of concurrent reconciles
    GroupKindConcurrency map[string]int

    // MaxConcurrentReconciles is the maximum number of concurrent reconciles
    MaxConcurrentReconciles int

    // CacheSyncTimeout is the timeout for waiting for caches to sync
    CacheSyncTimeout time.Duration

    // RecoverPanic indicates whether panics should be recovered
    RecoverPanic *bool

    // NeedLeaderElection indicates whether this controller needs leader election
    NeedLeaderElection *bool

    // EnableWarmup enables controller warmup on startup
    EnableWarmup *bool

    // UsePriorityQueue indicates whether to use a priority queue
    UsePriorityQueue *bool

    // Logger is the logger for this controller
    Logger logr.Logger

    // ReconciliationTimeout is the maximum duration for a single reconcile
    ReconciliationTimeout time.Duration
}

CertWatcher Package

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

Certificate watcher for automatically reloading TLS certificates.

CertWatcher

package certwatcher

import (
    "context"
    "crypto/tls"
    "sync"
    "time"
)

// CertWatcher watches certificate and key files for changes
type CertWatcher struct {
    sync.RWMutex
    // Has unexported fields
}

// New creates a new CertWatcher
func New(certPath, keyPath string) (*CertWatcher, error)

CertWatcher Methods

// Start starts the certificate watcher
func (cw *CertWatcher) Start(ctx context.Context) error

// GetCertificate returns the current certificate
func (cw *CertWatcher) GetCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate, error)

// ReadCertificate reads and parses the certificate from disk
func (cw *CertWatcher) ReadCertificate() error

// Watch watches for certificate changes and reloads
func (cw *CertWatcher) Watch()

// RegisterCallback registers a callback to be called when the certificate changes
func (cw *CertWatcher) RegisterCallback(callback func(tls.Certificate))

// WithWatchInterval sets the watch interval
func (cw *CertWatcher) WithWatchInterval(interval time.Duration) *CertWatcher

// NeedLeaderElection returns false (cert watcher doesn't need leader election)
func (cw *CertWatcher) NeedLeaderElection() bool

CertWatcher Metrics

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

package metrics

import (
    "github.com/prometheus/client_golang/prometheus"
)

var (
    // ReadCertificateTotal is the total number of certificate reads
    ReadCertificateTotal = prometheus.NewCounter(prometheus.CounterOpts{
        Name: "certwatcher_read_certificate_total",
        Help: "Total number of certificate reads",
    })

    // ReadCertificateErrors is the total number of certificate read errors
    ReadCertificateErrors = prometheus.NewCounter(prometheus.CounterOpts{
        Name: "certwatcher_read_certificate_errors_total",
        Help: "Total number of certificate read errors",
    })
)

CertWatcher Usage Example

package main

import (
    "context"
    "crypto/tls"
    "net/http"

    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/certwatcher"
    "sigs.k8s.io/controller-runtime/pkg/webhook"
)

func main() {
    // Create certificate watcher
    certWatcher, err := certwatcher.New(
        "/tmp/certs/tls.crt",
        "/tmp/certs/tls.key",
    )
    if err != nil {
        panic(err)
    }

    mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
        WebhookServer: webhook.NewServer(webhook.Options{
            Port: 9443,
            TLSOpts: []func(*tls.Config){
                func(config *tls.Config) {
                    config.GetCertificate = certWatcher.GetCertificate
                },
            },
        }),
    })
    if err != nil {
        panic(err)
    }

    // Add cert watcher to manager
    if err := mgr.Add(certWatcher); err != nil {
        panic(err)
    }

    // Register callback for certificate changes
    certWatcher.RegisterCallback(func(cert tls.Certificate) {
        log.Info("certificate reloaded")
    })

    if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
        panic(err)
    }
}

PriorityQueue Package

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

Priority-based work queues for controllers.

Types

package priorityqueue

import (
    "time"

    "github.com/go-logr/logr"
    "k8s.io/client-go/util/workqueue"
)

// PriorityQueue is a priority queue interface
type PriorityQueue[T comparable] interface {
    workqueue.TypedRateLimitingInterface[T]

    // AddWithOpts adds items with specific options
    AddWithOpts(o AddOpts, Items ...T)

    // GetWithPriority retrieves an item with its priority
    GetWithPriority() (item T, priority int, shutdown bool)
}

// New creates a new priority queue
func New[T comparable](name string, o ...Opt[T]) PriorityQueue[T]

Options

// AddOpts contains options for adding items
type AddOpts struct {
    // After is the delay before adding the item
    After time.Duration

    // RateLimited indicates if the item should be rate limited
    RateLimited bool

    // Priority is the priority of the item (higher = more important)
    Priority *int
}
// Opt is an option for configuring a PriorityQueue
type Opt[T comparable] func(*Opts[T])

type Opts[T comparable] struct {
    // RateLimiter is the rate limiter to use
    RateLimiter workqueue.TypedRateLimiter[T]

    // MetricProvider provides metrics for the queue
    MetricProvider workqueue.MetricsProvider

    // Log is the logger to use
    Log logr.Logger
}

PriorityQueue Usage Example

package controllers

import (
    "context"

    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/controller"
    "sigs.k8s.io/controller-runtime/pkg/controller/priorityqueue"
    "sigs.k8s.io/controller-runtime/pkg/reconcile"
)

func SetupControllerWithPriorityQueue(mgr ctrl.Manager) error {
    // Create custom queue factory
    newQueue := func(name string, rateLimiter workqueue.RateLimiter) workqueue.RateLimitingInterface {
        return priorityqueue.New[reconcile.Request](name)
    }

    return ctrl.NewControllerManagedBy(mgr).
        Named("priority-controller").
        For(&MyResource{}).
        WithOptions(controller.Options{
            UsePriorityQueue: ptr(true),
            NewQueue:         newQueue,
        }).
        Complete(reconciler)
}

func ptr[T any](v T) *T {
    return &v
}

Conversion Package

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

Interfaces for API version conversion.

Types

package conversion

import (
    "k8s.io/apimachinery/pkg/runtime"
)

// Hub marks that a type is the hub type for conversion
type Hub interface {
    runtime.Object
    Hub()
}

// Convertible defines capability of a type to be convertible
type Convertible interface {
    runtime.Object

    // ConvertTo converts this version to the Hub version
    ConvertTo(dst Hub) error

    // ConvertFrom converts from the Hub version to this version
    ConvertFrom(src Hub) error
}

Conversion Usage Example

See the conversion example in the Webhooks documentation.

Complete Utilities Example

package controllers

import (
    "context"
    "time"

    corev1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/client"
    "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    "sigs.k8s.io/controller-runtime/pkg/reconcile"
)

type ResourceReconciler struct {
    client.Client
    Scheme *runtime.Scheme
}

const finalizerName = "myapp.example.com/finalizer"

func (r *ResourceReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
    log := ctrl.LoggerFrom(ctx)

    var resource corev1.ConfigMap
    if err := r.Get(ctx, req.NamespacedName, &resource); err != nil {
        return reconcile.Result{}, client.IgnoreNotFound(err)
    }

    // Handle deletion
    if !resource.DeletionTimestamp.IsZero() {
        if controllerutil.ContainsFinalizer(&resource, finalizerName) {
            log.Info("performing cleanup")

            // Cleanup external resources
            if err := r.cleanupExternalResources(ctx, &resource); err != nil {
                return reconcile.Result{}, err
            }

            // Remove finalizer
            controllerutil.RemoveFinalizer(&resource, finalizerName)
            if err := r.Update(ctx, &resource); err != nil {
                return reconcile.Result{}, err
            }
        }
        return reconcile.Result{}, nil
    }

    // Add finalizer if not present
    if !controllerutil.ContainsFinalizer(&resource, finalizerName) {
        controllerutil.AddFinalizer(&resource, finalizerName)
        if err := r.Update(ctx, &resource); err != nil {
            return reconcile.Result{}, err
        }
    }

    // Create or update a child resource
    childResource := &corev1.Secret{
        ObjectMeta: metav1.ObjectMeta{
            Name:      resource.Name + "-secret",
            Namespace: resource.Namespace,
        },
    }

    result, err := controllerutil.CreateOrUpdate(ctx, r.Client, childResource, func() error {
        // Set desired state
        childResource.StringData = map[string]string{
            "key": "value",
        }

        // Set owner reference so child is deleted when parent is deleted
        return controllerutil.SetControllerReference(&resource, childResource, r.Scheme)
    })
    if err != nil {
        return reconcile.Result{}, err
    }

    log.Info("child resource operation completed", "result", result)

    // Requeue after 5 minutes
    return reconcile.Result{RequeueAfter: 5 * time.Minute}, nil
}

func (r *ResourceReconciler) cleanupExternalResources(ctx context.Context, resource *corev1.ConfigMap) error {
    // Implement cleanup logic for external resources
    return nil
}

func (r *ResourceReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&corev1.ConfigMap{}).
        Owns(&corev1.Secret{}).
        Complete(r)
}