controller

package
v0.2.0-alpha.1 Latest Latest
Warning

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

Go to latest
Published: Jul 5, 2026 License: Apache-2.0 Imports: 78 Imported by: 0

README

pkg/controller

Event-driven coordination layer for HAPTIC. This package is the only one in the tree that knows about the EventBus — it wraps the pure libraries (pkg/templating, pkg/k8s, pkg/dataplane, pkg/stores, pkg/webhook, pkg/httpstore) in event adapters and orchestrates startup, reconciliation, and shutdown.

Most day-to-day work in this package does not need this README; pkg/controller/CLAUDE.md has the detailed developer context and the source is the authoritative interface. This page is a short map for newcomers.

Mental Model

Three layers:

Pure library          Event adapter                         EventBus consumers
(pkg/templating,      (pkg/controller/renderer,             (commentator, metrics,
 pkg/dataplane,        pkg/controller/validator,             debug, pipeline)
 pkg/k8s, ...)         pkg/controller/deployer, ...)
  • Pure libraries expose plain Go APIs with no event dependencies.
  • Event adapters embed *component.Base (the shared event-loop scaffold) and translate events to pure-library calls and back.
  • Consumers (EventCommentator, metrics adapter, debug server) subscribe without publishing — they observe.

The sub-package tree in this directory mirrors those three layers. For the canonical list see docs/controller/docs/development/design/package-structure.md.

Startup Sequence

The controller runs a reinitialization loop (iteration.go). Each iteration:

  1. Fetch and validate the HAProxyTemplateConfig CRD and its credentials Secret.
  2. Build a fresh EventBus and register every component via pkg/lifecycle.
  3. Start resource watchers and wait for initial sync.
  4. EventBus.Start() releases buffered events.
  5. Start reconciliation components (renderer, validator, deployer scheduler, drift monitor, metrics adapter, commentator).
  6. Wait for a config change or context cancellation. On config change the iteration context is cancelled and the loop restarts with the new config.

This is why the docs consistently say "no pod restart on config change" — the CRD watcher triggers a fresh iteration inside the same process.

Key Sub-Packages

Purpose Package
Shared event-loop scaffold (embedded by nearly every component) component/
Observability (domain-aware logs, ring-buffered event history) commentator/, debug/, metrics/
Configuration ingestion (CRD + Secret loading) configloader/, credentialsloader/, resourceloader/
Reconciliation pipeline (debounce → render → validate → publish) reconciler/, renderer/, validator/, pipeline/, rendercontext/
Deployment orchestration (scheduler, executor, drift prevention) deployer/, discovery/, configpublisher/, statusapplier/
Webhook & validation bridges webhook/, dryrunvalidator/, proposalvalidator/, testrunner/
Leader election + leader-only gating leaderelection/, leadership/
Store management and overlay handling resourcestore/, resourcewatcher/, indextracker/, currentconfigstore/
Event catalogue (≈50 domain events) events/

component/ is the biggest reusable abstraction: new components embed *component.Base, implement HandleEvent(event), and get subscribe-on-construction, single-flight dispatch, panic recovery, and ready/done signalling for free. See component/base.go and the existing consumers for examples.

Event Patterns

Two coordination modes via pkg/events:

  • Publish/Subscribe — fire-and-forget, buffered per subscriber. Used for everything on the main reconciliation path (resource index updates → reconciliation trigger → rendered → validated → deployed).
  • Request/Response (scatter-gather) — synchronous with timeout and expected-responder list. Used for admission-time validation, where multiple validators must independently approve a proposed config.

Domain event types live in pkg/controller/events. pkg/events itself is domain-agnostic.

Writing a New Component

The short version:

  1. Decide whether it's a pure library (goes to a top-level pkg/<name>) or coordination (goes here as an event adapter).
  2. If it's coordination, embed *component.Base and implement HandleEvent.
  3. Subscribe to the events you need in the constructor — not in Start() — so buffered events aren't lost when EventBus.Start() releases them. Exception: leader-only components subscribe in Start() using SubscribeTypesLeaderOnly(), which suppresses the late-subscriber warning. The lifecycle registry only invokes Start() for those after leadership is held; all-replica components replay their last state on BecameLeaderEvent so the late-subscribing leader-only components still see current state. See reconciler/coordinator.go and configpublisher/component.go for the canonical leader-only pattern, and LEADER_ONLY_COMPONENTS.md for the contract.
  4. Register with pkg/lifecycle (mark leader-only, declare dependencies, add a health source).
  5. Add a log case to commentator/ for every new event type so it lands in the ring-buffered history.

pkg/controller/CLAUDE.md has the long version plus leadership-transition patterns (state replay on BecameLeaderEvent, cleanup on LostLeadershipEvent) that every new leader-only component must implement.

Testing

go test ./pkg/controller/...             # unit + adapter tests
go test ./pkg/controller/... -race       # race detector

Event adapters are typically tested by wiring up a real EventBus, publishing a trigger event, and asserting on the resulting events. See pkg/controller/configloader/loader_test.go for the canonical pattern (subscribes to a CRD-change event, publishes a parsed-config event); component.Base has its own unit tests in component/base_test.go.

End-to-end tests that actually spin up controllers against a Kind cluster live under tests/integration (build-tagged //go:build integration), not here. Run them with make test-integration or go test -tags=integration ./tests/integration/....

See Also

  • pkg/events — EventBus infrastructure
  • pkg/lifecycle — component registry, dependency ordering, leader-only gating
  • pkg/controller/CLAUDE.md — developer context, leadership-transition patterns, pitfalls
  • pkg/controller/LEADER_ONLY_COMPONENTS.md — checklist for leader-only components
  • docs/controller/docs/development/design/package-structure.md — whole-repo orientation
  • docs/controller/docs/development/design/sequence-diagrams.md — reconciliation and validation flows

License

Apache-2.0 — see root LICENSE.

Documentation

Overview

Package controller provides the main controller orchestration for the HAProxy template ingress controller.

The controller follows an event-driven architecture with a reinitialization loop: 1. Fetch and validate initial configuration 2. Create EventBus and components 3. Start components and watchers 4. Wait for configuration changes 5. Reinitialize on valid config changes

Index

Constants

View Source
const (
	// RetryDelay is the duration to wait before retrying after an iteration failure.
	RetryDelay = 5 * time.Second
	// ConfigPollInterval is the interval for polling HAProxyTemplateConfig availability.
	ConfigPollInterval = 5 * time.Second
	// DebugEventBufferSize is the size of the event buffer for debug/introspection.
	DebugEventBufferSize = busevents.DebugSubscriberBuffer
	// ShutdownTimeout is the maximum time to wait for goroutines to finish during shutdown.
	// Set to 25s to allow clean exit before Kubernetes' default 30s terminationGracePeriodSeconds.
	ShutdownTimeout = 25 * time.Second
	// ShutdownProgressInterval is how often to log progress during shutdown.
	ShutdownProgressInterval = 5 * time.Second
)
View Source
const ReinitGraceWindow = 90 * time.Second

ReinitGraceWindow is how long after a voluntary iteration restart /healthz keeps reporting healthy while components re-initialize. Sized to cover a slow reinit (embedded validationTests + watcher re-sync + leader re-acquisition) while staying well below the fresh-pod startup budget the liveness restart would fall back to.

Variables

This section is empty.

Functions

func Run

func Run(ctx context.Context, k8sClient *client.Client, crdName, secretName, webhookCertDir string, debugPort int) error

Run is the main entry point for the controller.

It performs initial configuration fetching and validation, then enters a reinitialization loop where it responds to configuration changes by restarting with the new configuration.

The controller uses an event-driven architecture:

  • EventBus coordinates all components
  • SingleWatcher monitors HAProxyTemplateConfig CRD and Secret
  • Components react to events and publish results
  • ConfigChangeHandler detects validated config changes and signals reinitialization

Parameters:

  • ctx: Context for cancellation (SIGTERM, SIGINT, etc.)
  • k8sClient: Kubernetes client for API access
  • crdName: Name of the HAProxyTemplateConfig CRD
  • secretName: Name of the Secret containing HAProxy Dataplane API credentials
  • webhookCertDir: Directory holding the webhook TLS cert (tls.crt/tls.key); empty disables the webhook
  • debugPort: Port for debug HTTP server (0 to disable)

Returns:

  • Error if the controller cannot start or encounters a fatal error
  • nil if the context is cancelled (graceful shutdown)

func SetBuildInfo

func SetBuildInfo(version, haproxyVersion string)

SetBuildInfo configures version information exposed via the haptic_build_info Prometheus metric. Must be called before Run() to take effect.

Parameters:

  • version: Controller version (e.g., "0.1.0-alpha.10")
  • haproxyVersion: HAProxy version the controller was built for (e.g., "3.2")

Types

type InitialConfigBundle

type InitialConfigBundle struct {
	Config             *coreconfig.Config
	CRD                *v1alpha1.HAProxyTemplateConfig
	Credentials        *coreconfig.Credentials
	CredentialsVersion string
}

fetchAndValidateInitialConfig fetches, parses, and validates the initial HAProxyTemplateConfig CRD and credentials Secret.

Returns the validated configuration and credentials, or an error if any step fails. InitialConfigBundle holds the parsed initial CRD / Secret values plus the resource versions of the underlying Secrets, so the iteration startup can wire bootstrap-version filtering for ConfigChangeHandler.

type StateCache

type StateCache struct {
	*component.Base
	// contains filtered or unexported fields
}

StateCache caches controller state by subscribing to events.

This component implements the debug.StateProvider interface and provides thread-safe access to the controller's internal state for debug purposes.

It subscribes to key events and updates its cached state accordingly:

  • ConfigValidatedEvent → updates config cache
  • CredentialsUpdatedEvent → updates credentials cache
  • TemplateRenderedEvent → updates rendered config cache
  • ReconciliationTriggeredEvent → updates pipeline trigger state
  • ValidationCompletedEvent/FailedEvent → updates validation state
  • DeploymentStartedEvent/CompletedEvent → updates deployment state
  • InstanceDeploymentFailedEvent → tracks failed endpoints

func NewStateCache

func NewStateCache(eventBus *busevents.EventBus, resourceWatcher *resourcewatcher.ResourceWatcherComponent, logger *slog.Logger) *StateCache

NewStateCache creates a new state cache component.

The StateCache subscribes to the EventBus in the constructor (before EventBus.Start()) to ensure proper startup synchronization and receive all buffered startup events.

Usage:

stateCache := NewStateCache(eventBus, resourceWatcher, logger)
go stateCache.Start(ctx)  // Process events in background
eventBus.Start()          // Release buffered events

func (*StateCache) GetAuxiliaryFiles

func (sc *StateCache) GetAuxiliaryFiles() (*dataplane.AuxiliaryFiles, time.Time, error)

GetAuxiliaryFiles implements debug.StateProvider.

func (*StateCache) GetConfig

func (sc *StateCache) GetConfig() (*coreconfig.Config, string, error)

GetConfig implements debug.StateProvider.

func (*StateCache) GetCredentials

func (sc *StateCache) GetCredentials() (*coreconfig.Credentials, string, error)

GetCredentials implements debug.StateProvider.

func (*StateCache) GetErrors

func (sc *StateCache) GetErrors() (*debug.ErrorSummary, error)

GetErrors implements debug.StateProvider.

func (*StateCache) GetPipelineStatus

func (sc *StateCache) GetPipelineStatus() (*debug.PipelineStatus, error)

GetPipelineStatus implements debug.StateProvider.

func (*StateCache) GetRenderedConfig

func (sc *StateCache) GetRenderedConfig() (string, time.Time, error)

GetRenderedConfig implements debug.StateProvider.

func (*StateCache) GetResourceCounts

func (sc *StateCache) GetResourceCounts() (map[string]int, error)

GetResourceCounts implements debug.StateProvider.

func (*StateCache) GetResourcesByType

func (sc *StateCache) GetResourcesByType(resourceType string) ([]any, error)

GetResourcesByType implements debug.StateProvider.

For `store: on-demand` types the result is the warm-cache subset only, not the full tracked set (see the partial-result contract on the interface declaration and listResources below). GetResourceCounts() remains the authoritative total.

func (*StateCache) GetValidatedConfig

func (sc *StateCache) GetValidatedConfig() (*debug.ValidatedConfigInfo, error)

GetValidatedConfig implements debug.StateProvider.

func (*StateCache) HandleEvent

func (sc *StateCache) HandleEvent(event busevents.Event)

HandleEvent implements component.EventHandler: it processes events and updates cached state.

Directories

Path Synopsis
Package buffers provides fixed buffer sizes for event subscriptions.
Package buffers provides fixed buffer sizes for event subscriptions.
Package coalesce provides utilities for event coalescing in controller components.
Package coalesce provides utilities for event coalescing in controller components.
Package commentator provides the Event Commentator pattern for domain-aware logging.
Package commentator provides the Event Commentator pattern for domain-aware logging.
Package component provides a shared event-loop scaffold for controller components that subscribe on construction and dispatch one event at a time.
Package component provides a shared event-loop scaffold for controller components that subscribe on construction and dispatch one event at a time.
Package configtest runs a HAProxyTemplateConfig's embedded validationTests against an already-built engine, bounded by a timeout.
Package configtest runs a HAProxyTemplateConfig's embedded validationTests against an already-built engine, bounded by a timeout.
Package crdwatch reinitializes the controller when the CustomResourceDefinitions backing watched resources change.
Package crdwatch reinitializes the controller when the CustomResourceDefinitions backing watched resources change.
Package currentconfigstore provides a utility component for caching the parsed current HAProxy configuration from the HAProxyCfg CRD.
Package currentconfigstore provides a utility component for caching the parsed current HAProxy configuration from the HAProxyCfg CRD.
Package debug provides controller-specific debug variable implementations.
Package debug provides controller-specific debug variable implementations.
Package deployer implements the Deployer component that deploys validated HAProxy configurations to discovered HAProxy pod endpoints.
Package deployer implements the Deployer component that deploys validated HAProxy configurations to discovered HAProxy pod endpoints.
Package discovery provides the Discovery event adapter component.
Package discovery provides the Discovery event adapter component.
Package dryrunvalidator implements the DryRunValidator that performs dry-run reconciliation for webhook validation.
Package dryrunvalidator implements the DryRunValidator that performs dry-run reconciliation for webhook validation.
Package events contains all domain event type definitions for the HAPTIC controller.
Package events contains all domain event type definitions for the HAPTIC controller.
Package helpers provides shared utility functions for the controller layer.
Package helpers provides shared utility functions for the controller layer.
Package httpstore provides the event adapter for HTTP resource fetching.
Package httpstore provides the event adapter for HTTP resource fetching.
Package indextracker provides the IndexSynchronizationTracker that monitors resource watcher synchronization and publishes an event when all are synced.
Package indextracker provides the IndexSynchronizationTracker that monitors resource watcher synchronization and publishes an event when all are synced.
Package leadership provides utilities for handling leadership transitions in event-driven components.
Package leadership provides utilities for handling leadership transitions in event-driven components.
Package names provides well-known string constants used across the controller layer.
Package names provides well-known string constants used across the controller layer.
Package pipeline provides the render-validate pipeline for HAProxy configuration.
Package pipeline provides the render-validate pipeline for HAProxy configuration.
Package pluggablevalidator implements the controller-side client for the pluggable-validator-sidecar wire protocol.
Package pluggablevalidator implements the controller-side client for the pluggable-validator-sidecar wire protocol.
testutil
Package testutil provides a fixture unix-socket server for exercising the pluggablevalidator client without spinning up a real haproxy-spoa-hub sidecar.
Package testutil provides a fixture unix-socket server for exercising the pluggablevalidator client without spinning up a real haproxy-spoa-hub sidecar.
Package proposalvalidator provides validation of hypothetical configuration changes.
Package proposalvalidator provides validation of hypothetical configuration changes.
Package reconciler implements the Reconciler component that triggers reconciliation events on resource changes.
Package reconciler implements the Reconciler component that triggers reconciliation events on resource changes.
Package rendercontext provides a centralized builder for template rendering contexts.
Package rendercontext provides a centralized builder for template rendering contexts.
Package resourceapplier reconciles template-declared Kubernetes resources via Server-Side Apply (SSA).
Package resourceapplier reconciles template-declared Kubernetes resources via Server-Side Apply (SSA).
Package resourceloader provides shared event loop infrastructure for loader components that watch a single resource type and parse/transform its data.
Package resourceloader provides shared event loop infrastructure for loader components that watch a single resource type and parse/transform its data.
Package resourcewatcher provides the ResourceWatcherComponent that creates and manages watchers for all Kubernetes resources defined in the controller configuration.
Package resourcewatcher provides the ResourceWatcherComponent that creates and manages watchers for all Kubernetes resources defined in the controller configuration.
Package statusapplier applies template-driven status patches to Kubernetes resources.
Package statusapplier applies template-driven status patches to Kubernetes resources.
Package testrunner implements validation test execution for HAProxyTemplateConfig.
Package testrunner implements validation test execution for HAProxyTemplateConfig.
Package testutil provides shared test helpers for controller package tests.
Package testutil provides shared test helpers for controller package tests.
Package throttle provides leading-edge refractory throttle helpers used by controller components that bound their write rate to slow downstream systems (e.g.
Package throttle provides leading-edge refractory throttle helpers used by controller components that bound their write rate to slow downstream systems (e.g.
Package timeouts provides shared timeout constants used across controller sub-packages.
Package timeouts provides shared timeout constants used across controller sub-packages.
Package timers provides safe timer management for event-driven controller components.
Package timers provides safe timer management for event-driven controller components.
Package typebootstrap orchestrates the typed-watched-resources pipeline at controller startup.
Package typebootstrap orchestrates the typed-watched-resources pipeline at controller startup.
Package validation provides pure validation services for HAProxy configuration.
Package validation provides pure validation services for HAProxy configuration.
Package webhook provides the webhook adapter component that bridges the pure webhook library to the event-driven controller architecture.
Package webhook provides the webhook adapter component that bridges the pure webhook library to the event-driven controller architecture.

Jump to

Keyboard shortcuts

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