operatorkit

package
v0.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package operatorkit owns the controller-manager runtime behind the generated App.RunOperators method in forge projects.

Pattern

The generated pkg/app/bootstrap.go used to open-code the controller-runtime manager setup (kubeconfig resolution, leader election, scheme registration, controller setup, manager start). Following the "generated files are tables, not programs" rule, the generated RunOperators is now a single delegation to Run with one dumb Controller row per operator:

func (a *App) RunOperators(ctx context.Context, logger *slog.Logger, healthProbeAddr string) error {
    return operatorkit.Run(ctx, logger, operatorkit.Options{
        LeaderElectionID:       "example.com/myproj-leader",
        HealthProbeBindAddress: healthProbeAddr,
    }, []operatorkit.Controller{
        {Name: "scaler", AddToScheme: scaler.AddToScheme,
            SetupWithManager: a.Operators.Scaler.SetupWithManager},
    })
}

operatorkit lives in its own package (rather than appkit proper) so projects without operators never compile controller-runtime and its Kubernetes dependency tree — the generated import is conditional on the project having operators.

Behavioural fingerprint

All observable strings from the pre-table generated RunOperators are preserved verbatim:

  • warn "operators disabled: no Kubernetes cluster reachable" when kubeconfig resolution fails (vanilla docker-compose dev, fresh laptop, CI without a kind/k3d cluster) — the binary continues without operators rather than crashing, matching how NATS degrades.
  • "creating controller manager: <wrapped error>".
  • "adding <name> scheme: <wrapped error>".
  • "setting up controller %q: <wrapped error>".
  • info "registered operator controller" / "starting controller manager".

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Run

func Run(ctx context.Context, logger *slog.Logger, opts Options, controllers []Controller) error

Run creates a controller manager, registers every controller's scheme and setup, and starts the manager. It blocks until ctx is cancelled or an error occurs; the caller runs it in a goroutine.

When no Kubernetes cluster is reachable, kubeconfig resolution fails and Run logs a warning and returns nil — the process continues without operators instead of crashing.

Types

type Controller

type Controller struct {
	// Name is the operator's forge.yaml name — used in error messages
	// and registration logs.
	Name string
	// AddToScheme registers the operator's CRD types on the manager's
	// scheme. Optional (nil is skipped) for controllers that only watch
	// built-in types.
	AddToScheme func(s *runtime.Scheme) error
	// SetupWithManager registers the controller with the manager.
	SetupWithManager func(mgr ctrl.Manager) error
}

Controller is one generated operator row: the CRD scheme installer and the controller's manager hookup, both referenced straight off the generated operator package / constructed instance.

type Options

type Options struct {
	// LeaderElectionID is the lease name used for leader election —
	// the generated table passes "<module>-leader". The LEADER_ELECTION_ID
	// env var, when set, overrides this so distinct processes can take
	// distinct leases (env > this default).
	LeaderElectionID string

	// HealthProbeBindAddress, when non-empty, binds a /healthz +
	// /readyz listener on that address for the controller-runtime
	// manager. The generated RunOperators forwards it from
	// serverkit.Config.OperatorHealthProbeAddr. Empty leaves the
	// manager without a probe listener (the default — vanilla forge
	// projects don't bind one).
	HealthProbeBindAddress string

	// ByObjectNamespaces scopes the manager cache PER OBJECT TYPE: each entry
	// maps an object example (e.g. &v1alpha1.Workspace{}) to the ONLY
	// namespaces the manager's informers watch/list for that type
	// (controller-runtime cache.ByObject.Namespaces). Types WITHOUT an entry
	// keep the default cluster-wide watch, so a controller can confine its own
	// CRD to the namespace its stack deploys into while still watching
	// cross-namespace workload objects (Pods/PVCs in per-user namespaces)
	// everywhere.
	//
	// Motivation: co-located stacks on one shared cluster (e.g. dev + e2e on
	// one k3d node) each run their own copy of the same operator. With a
	// cluster-wide CR watch, each copy also reconciles the OTHER stack's CRs —
	// a derelict controller from one stack can then stamp its own config
	// (image, env) onto a sibling stack's workloads. Scoping the CR watch to
	// the stack's own namespace makes cross-stack reconciliation structurally
	// impossible.
	//
	// Entries with no namespaces (or only empty strings) are dropped — that
	// type stays cluster-wide, preserving the legacy behavior when the
	// deployment namespace is unknown. The scoped types' GVKs are resolved
	// against the manager scheme at manager construction, so every scoped
	// type MUST be registered by one of the controllers' AddToScheme hooks
	// (Run registers them all before creating the manager).
	ByObjectNamespaces map[client.Object][]string
}

Options carries the per-project manager configuration the generated row table supplies.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL