config

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: 6 Imported by: 0

README

pkg/core/config

Defines the internal Config / Credentials structs and the pure functions that load and validate them. No Kubernetes client calls, no event bus — everything here operates on already-materialised bytes or strings.

Upstream in the pipeline: pkg/controller/conversion.ParseCRD converts a HAProxyTemplateConfig CRD into the *Config this package defines.

Public API

// Fill in defaults (mutates in place)
func SetDefaults(cfg *Config)

// Required fields, port ranges, enum values
func ValidateStructure(cfg *Config) error

// Secret data → Credentials
func LoadCredentials(secretData map[string][]byte) (*Credentials, error)
func ValidateCredentials(creds *Credentials) error

// Helpers
func ParseSecretData(raw map[string]any) (map[string][]byte, error)

The Go fields are PodSelector, Controller, Logging, Dataplane, TemplatingSettings, WatchedResources, WatchedResourcesIgnoreFields, Validators, TemplateSnippets, Maps, Files, SSLCertificates, K8sResources, CRTLists, HAProxyConfig, ValidationTests. Three serialisation forms exist for the same struct, and they don't all agree:

  • Go field names — PascalCase (PodSelector).
  • YAML keys (yaml: struct tags) — snake_case at the top level (pod_selector, templating_settings, watched_resources, haproxy_config); a few nested fields use camelCase (httpResources, currentConfig, extraContext, minHAProxyVersion). types.go's yaml: tags are authoritative.
  • CRD JSON keys (kubectl, ParseCRD) — camelCase, per Kubernetes convention. The controller goes through pkg/controller/conversion.ParseCRD which deserialises into the typed CRD first and then maps it onto *Config field-by-field.

Use snake_case in YAML files; use camelCase in CRD manifests. The types.go source is the authoritative schema for either.

Validation Layers

This package only does structural validation:

  • Required fields present
  • int fields in range (ports 1–65535, non-negative counters)
  • Enum values from the allowed set
  • Non-empty strings where semantically required
  • time.Duration strings parse

It deliberately does not:

  • Validate template syntax → pkg/templating.ValidateTemplates
  • Validate JSONPath expressions → pkg/k8s/indexer.ValidateJSONPath
  • Validate rendered HAProxy config → pkg/dataplane.ValidateConfiguration
  • Apply cross-field business rules → pkg/controller/validator

Those run via scatter-gather in the controller so each validator can evolve independently.

Key Defaults (SetDefaults)

Authoritative list is defaults.go. Ones operators commonly look up:

  • dataplane.port: 5555
  • dataplane.minDeploymentInterval: 2s
  • dataplane.driftPreventionInterval: 60s
  • dataplane.deploymentTimeout: 30s
  • dataplane.{mapsDir,sslCertsDir,generalStorageDir,configFile}: /etc/haproxy/...
  • controller.leaderElection.{leaseName,leaseDuration,renewDeadline,retryPeriod}: haptic-leader, 15s, 10s, 2s (matches Kubernetes' recommended fast-failover triplet — defaults.go:54-60)
  • controller.configPublishing.compressionThreshold: 1 MiB
  • templatingSettings.engine: scriggo

Credentials Schema

LoadCredentials expects two non-empty string keys in the Secret data:

  • dataplane_username
  • dataplane_password

These are used to authenticate against the production HAProxy pods' Dataplane API instances. The controller's local haproxy -c validation step does not need credentials — it shells out to the binary directly with the rendered config and auxiliary files. ValidateCredentials rejects empty strings after base64 decode. No String() / GoString() methods are defined on Credentials — helps prevent accidental password leaks via %v or log.Info("…", creds).

See Also

License

Apache-2.0 — see root LICENSE.

Documentation

Overview

Package config provides configuration loading and validation.

Package config provides data models for the controller configuration.

These models represent the structure of the configuration YAML produced by pkg/controller/conversion.ParseCRD from a HAProxyTemplateConfig CRD, plus the credentials loaded from the referenced Secret.

Index

Constants

View Source
const (
	// DefaultLevel is the default log level.
	// Empty string means use LOG_LEVEL env var or default to INFO.
	DefaultLevel = ""

	// DefaultDataplanePort is the default Dataplane API port for production HAProxy pods.
	DefaultDataplanePort = 5555

	// DefaultEnableValidationWebhook is the default webhook setting for resources.
	DefaultEnableValidationWebhook = false

	// DefaultMinDeploymentInterval is the default minimum time between consecutive deployments.
	DefaultMinDeploymentInterval = 2 * time.Second

	// DefaultDriftPreventionInterval is the default interval for periodic drift prevention deployments.
	DefaultDriftPreventionInterval = 60 * time.Second

	// DefaultDeploymentTimeout is the maximum time to wait for a deployment to complete.
	// If exceeded, the scheduler assumes the deployment was lost and retries.
	DefaultDeploymentTimeout = 30 * time.Second

	// DefaultDataplaneMapsDir is the default directory for HAProxy map files.
	DefaultDataplaneMapsDir = "/etc/haproxy/maps"

	// DefaultConfigPublishInterval throttles HAProxyCfg CRD updates to coalesce
	// rapid renders during endpoint churn. Bounds the observable gap between
	// pod status.deployedToPods[].checksum and spec.checksum.
	DefaultConfigPublishInterval = 10 * time.Second

	// DefaultDataplaneSSLCertsDir is the Go-side fallback directory for SSL
	// certificates, used when neither the user nor the chart sets sslCertsDir.
	// The Helm chart explicitly sets sslCertsDir to /etc/haproxy/ssl in its
	// values.yaml, so chart-deployed controllers see /etc/haproxy/ssl rather
	// than this fallback. The two diverge intentionally: the chart's "ssl"
	// matches conventional HAProxy directory naming, while this constant
	// preserves the historical "certs" name for non-chart users (mostly
	// integration tests under tests/integration/).
	DefaultDataplaneSSLCertsDir = "/etc/haproxy/certs"

	// DefaultDataplaneGeneralStorageDir is the default directory for general files.
	DefaultDataplaneGeneralStorageDir = "/etc/haproxy/general"

	// DefaultDataplaneConfigFile is the default path to the main HAProxy config file.
	DefaultDataplaneConfigFile = "/etc/haproxy/haproxy.cfg"

	// DefaultLeaderElectionEnabled is the default leader election enabled setting.
	DefaultLeaderElectionEnabled = true

	// DefaultLeaderElectionLeaseName is the default name for the leader election lease.
	DefaultLeaderElectionLeaseName = "haptic-leader"

	// DefaultLeaderElectionLeaseDuration is the default lease duration.
	DefaultLeaderElectionLeaseDuration = 30 * time.Second

	// DefaultLeaderElectionRenewDeadline is the default renew deadline.
	DefaultLeaderElectionRenewDeadline = 20 * time.Second

	// DefaultLeaderElectionRetryPeriod is the default retry period.
	DefaultLeaderElectionRetryPeriod = 5 * time.Second

	// DefaultReloadVerificationTimeout is the default maximum time the Dataplane
	// sync waits for a graceful HAProxy reload to be reported as completed.
	DefaultReloadVerificationTimeout = 10 * time.Second

	// DefaultSyncTimeout is the default overall timeout for one Dataplane sync
	// to a single HAProxy endpoint.
	DefaultSyncTimeout = 2 * time.Minute
)

Default values for configuration fields.

View Source
const DebounceImmediate time.Duration = -1

DebounceImmediate mirrors pkg/k8s/types.DebounceImmediate (-1) — the sentinel value for "no debounce, fire on every event." arch-go.yml forbids pkg/core/config from importing pkg/k8s/types, so the constant is duplicated. tests/defaults_consistency_test.go pins the equality; change both together.

View Source
const DefaultCompressionThreshold int64 = 1048576 // 1 MiB

DefaultCompressionThreshold is the default minimum size in bytes at which configs are compressed. This matches the CRD kubebuilder default annotation.

View Source
const DefaultRenderTimeout = 30 * time.Second

DefaultRenderTimeout is the default maximum duration for rendering a single template.

Variables

This section is empty.

Functions

func ParseSecretData

func ParseSecretData(dataRaw map[string]any) (map[string][]byte, error)

ParseSecretData extracts and base64-decodes Secret data from an unstructured map.

Kubernetes Secrets store data as base64-encoded strings in the API. When accessed through unstructured.Unstructured, the values remain base64-encoded and must be decoded before use.

Parameters:

  • dataRaw: The raw Secret data map from unstructured.NestedMap(resource.Object, "data")

Returns:

  • map[string][]byte: Decoded secret data ready for use
  • error: If any value is not a string or fails base64 decoding

func ResolveEffective

func ResolveEffective(cfg *Config, served ServedVersionChecker, fields SchemaFieldChecker) (*Config, *Resolution, error)

ResolveEffective resolves every watched resource to the first candidate version the checker reports as served and returns the EFFECTIVE config the rest of the controller consumes:

  • each surviving WatchedResources entry has APIVersion set to the resolved version and APIVersions cleared, so every downstream consumer of the literal APIVersion (informer GVR, stores, schema fetch, webhook registration, dry-run mapping, fixture defaulting) transparently uses the resolved version;
  • an optional resource with no served candidate is removed, together with every TemplateSnippet / ValidationTest whose Requires names it;
  • a ValidationTest whose RequiresFields names a field absent from the resolved schema generation (probed via the SchemaFieldChecker) is removed — same feature-absence semantics as Requires, one level finer;
  • a required resource with no served candidate is an error (fail fast — the alternative is an informer that never syncs and a controller that never becomes Ready, with nothing in the logs naming the cause).

fields may be nil for callers without a schema source; RequiresFields entries are then not probed and never strip (the offline validate path applies the same leniency when --schema-dir is absent).

The input config is not mutated: the returned config shares everything except the three affected maps. The transformation is resource-agnostic — it consumes only candidate lists, plural names, and Requires / RequiresFields declarations from the configuration.

func SetDefaults

func SetDefaults(cfg *Config)

SetDefaults applies default values to unset configuration fields. This modifies the config in-place and should be called after parsing the configuration and before validation.

Port Handling Strategy:

  • A value of 0 for production ports (healthz, metrics, dataplane) means "uninitialized" and will be replaced with the default value
  • Debug ports may intentionally be 0 to indicate "disabled" (see cmd/controller/main.go)
  • After defaults are applied, production ports MUST NOT be 0 (validation will catch this)

func ValidateCredentials

func ValidateCredentials(creds *Credentials) error

ValidateCredentials ensures all required credential fields are present and non-empty.

func ValidateStructure

func ValidateStructure(cfg *Config) error

ValidateStructure performs basic structural validation on the configuration. Validates required fields, value ranges, and non-empty slices. Does NOT validate template syntax or JSONPath expressions.

Types

type CRTListFile

type CRTListFile struct {
	// Template is the template content that generates the crt-list file.
	Template string `yaml:"template"`

	// PostProcessing defines optional post-processors to apply after rendering.
	// Post-processors are applied in order to transform the rendered output.
	PostProcessing []PostProcessorConfig `yaml:"post_processing,omitempty"`
}

CRTListFile is a crt-list file template.

CRT-list files contain entries that map SSL certificates to specific SNI patterns with per-certificate options. This enables advanced SSL certificate management with features like SNI routing, OCSP stapling, and per-certificate TLS settings.

type Config

type Config struct {
	// PodSelector identifies HAProxy pods to configure.
	PodSelector PodSelector `yaml:"pod_selector"`

	// Controller contains controller-level settings (ports, etc.).
	Controller ControllerConfig `yaml:"controller"`

	// Logging configures logging behavior.
	Logging LoggingConfig `yaml:"logging"`

	// Dataplane configures the Dataplane API for production HAProxy instances.
	Dataplane DataplaneConfig `yaml:"dataplane"`

	// TemplatingSettings configures template rendering behavior and custom variables.
	TemplatingSettings TemplatingSettings `yaml:"templating_settings" json:"templatingSettings"`

	// WatchedResourcesIgnoreFields specifies JSONPath expressions for fields
	// to remove from all watched resources to reduce memory usage.
	//
	// Example: ["metadata.managedFields"]
	WatchedResourcesIgnoreFields []string `yaml:"watched_resources_ignore_fields"`

	// WatchedResources maps resource type names to their watch configuration.
	//
	// Example:
	//   ingresses:
	//     api_version: networking.k8s.io/v1
	//     kind: Ingress
	//     index_by: ["metadata.namespace", "metadata.name"]
	WatchedResources map[string]WatchedResource `yaml:"watched_resources"`

	// Validators lists pluggable validator sidecars consulted by the
	// admission webhook before admitting changes that affect plugin
	// configuration. See ValidatorConfig for per-entry semantics.
	//
	// An empty list (the default) leaves admission validation at
	// template + HAProxy syntax dry-run only.
	Validators []ValidatorConfig `yaml:"validators,omitempty"`

	// TemplateSnippets maps snippet names to reusable template fragments.
	//
	// Snippets can be included in other templates using {{ render "name" }}.
	TemplateSnippets map[string]TemplateSnippet `yaml:"template_snippets"`

	// Maps maps map file names to their template definitions.
	//
	// These generate HAProxy map files for backend routing and other features.
	Maps map[string]MapFile `yaml:"maps"`

	// Files maps file names to their template definitions.
	//
	// These generate auxiliary files like custom error pages.
	Files map[string]GeneralFile `yaml:"files"`

	// SSLCertificates maps certificate names to their template definitions.
	//
	// These generate SSL certificate files for HAProxy.
	SSLCertificates map[string]SSLCertificate `yaml:"ssl_certificates"`

	// K8sResources maps resource template names to their template
	// definitions. Each entry's rendered output is parsed as one or
	// more Kubernetes resources (multi-doc YAML separated by `---`)
	// and applied via Server-Side Apply by the controller.
	K8sResources map[string]K8sResource `yaml:"k8s_resources"`

	// CRTLists maps crt-list file names to their template definitions.
	//
	// These generate crt-list files for SSL certificate lists with per-certificate options.
	CRTLists map[string]CRTListFile `yaml:"crt_lists"`

	// HAProxyConfig contains the main HAProxy configuration template.
	HAProxyConfig HAProxyConfig `yaml:"haproxy_config"`

	// ValidationTests contains embedded tests for validating template rendering.
	// These tests are used both in CLI validation and webhook admission validation.
	// The map key is the test name, which must be unique.
	ValidationTests map[string]ValidationTest `yaml:"validation_tests"`
}

Config is the root configuration structure that pkg/controller/conversion.ParseCRD produces (it converts a HAProxyTemplateConfig CRD into this internal shape).

type ConfigPublishingConfig

type ConfigPublishingConfig struct {
	// CompressionThreshold is the minimum size in bytes at which configs are compressed.
	// Default: 1 MiB (1048576 bytes) via CRD kubebuilder default.
	// Set to 0 or negative to disable compression.
	CompressionThreshold int64 `yaml:"compression_threshold"`
}

ConfigPublishingConfig configures how rendered configs are stored in CRDs.

type ControllerConfig

type ControllerConfig struct {
	// LeaderElection configures leader election for high availability.
	LeaderElection LeaderElectionConfig `yaml:"leader_election"`

	// ConfigPublishing configures how rendered configs are stored in CRDs.
	ConfigPublishing ConfigPublishingConfig `yaml:"config_publishing"`
}

ControllerConfig contains controller-level configuration.

type Credentials

type Credentials struct {
	// DataplaneUsername is the username for production HAProxy instances.
	DataplaneUsername string

	// DataplanePassword is the password for production HAProxy instances.
	DataplanePassword string
}

Credentials contains HAProxy Dataplane API credentials.

Loaded by pkg/core/config.LoadCredentials from the Secret referenced by spec.credentialsSecretRef on the HAProxyTemplateConfig CRD — kept out of the CRD itself so the CRD can be stored in Git / Helm values without leaking secrets.

func LoadCredentials

func LoadCredentials(secretData map[string][]byte) (*Credentials, error)

LoadCredentials parses Secret data into a Credentials struct. This is a pure function that extracts credentials from Secret data. It does not load from Kubernetes or perform validation.

Expected Secret keys: dataplane_username, dataplane_password.

type DataplaneConfig

type DataplaneConfig struct {
	// Port is the Dataplane API port for production HAProxy pods.
	// A value of 0 means "uninitialized" and will be replaced with the default.
	// This is a production port and MUST NOT remain 0 after defaults are applied.
	// Default: 5555
	Port int `yaml:"port"`

	// MinDeploymentInterval enforces minimum time between consecutive deployments.
	// This prevents rapid-fire deployments from hammering HAProxy instances.
	// Format: Go duration string (e.g., "2s", "500ms")
	// Default: 2s
	MinDeploymentInterval string `yaml:"min_deployment_interval"`

	// DriftPreventionInterval triggers periodic deployments to prevent configuration drift.
	// A deployment is automatically triggered if no deployment has occurred within this interval.
	// This detects and corrects drift caused by external Dataplane API clients.
	// Format: Go duration string (e.g., "60s", "5m")
	// Default: 60s
	DriftPreventionInterval string `yaml:"drift_prevention_interval"`

	// DeploymentTimeout is the maximum time to wait for a deployment to complete.
	// If exceeded, the scheduler assumes the deployment was lost and retries.
	// This is a safety net for race conditions during leadership transitions.
	// Format: Go duration string (e.g., "30s", "1m")
	// Default: 30s
	DeploymentTimeout string `yaml:"deployment_timeout"`

	// MapsDir is the directory for HAProxy map files.
	// Used for both validation and deployment.
	// Default: /etc/haproxy/maps
	MapsDir string `yaml:"maps_dir"`

	// SSLCertsDir is the directory for SSL certificates.
	// Used for both validation and deployment.
	// Default: /etc/haproxy/ssl
	SSLCertsDir string `yaml:"ssl_certs_dir"`

	// GeneralStorageDir is the directory for general files (error pages, etc.).
	// Used for both validation and deployment.
	// Default: /etc/haproxy/general
	GeneralStorageDir string `yaml:"general_storage_dir"`

	// ConfigFile is the path to the main HAProxy configuration file.
	// Used for validation.
	// Default: /etc/haproxy/haproxy.cfg
	ConfigFile string `yaml:"config_file"`

	// ConfigPublishInterval throttles how often the HAProxyCfg CRD is updated.
	// During endpoint churn the rendered config changes frequently, but each CRD update
	// writes ~500 KB to etcd. This interval limits CRD publishes while deployments to
	// HAProxy pods (via events) remain unthrottled.
	// Uses leading-edge triggering: first change publishes immediately, subsequent
	// changes within the interval are buffered and published when the interval expires.
	// Format: Go duration string (e.g., "30s", "1m")
	// Default: 30s
	ConfigPublishInterval string `yaml:"config_publish_interval"`

	// ReloadVerificationTimeout bounds how long the Dataplane sync waits for a
	// graceful HAProxy reload to be reported as completed before failing the sync.
	// Format: Go duration string (e.g., "10s", "30s")
	// Default: 10s
	ReloadVerificationTimeout string `yaml:"reload_verification_timeout"`

	// SyncTimeout is the overall timeout for one Dataplane sync to a single
	// HAProxy endpoint (parse + diff + raw-push apply + optional reload-verify).
	// Format: Go duration string (e.g., "2m", "30s")
	// Default: 2m
	SyncTimeout string `yaml:"sync_timeout"`
}

DataplaneConfig configures the Dataplane API for production HAProxy instances.

func (*DataplaneConfig) GetConfigPublishInterval

func (d *DataplaneConfig) GetConfigPublishInterval() time.Duration

GetConfigPublishInterval returns the configured CRD publish throttle interval or the default if not specified or invalid.

func (*DataplaneConfig) GetDeploymentTimeout

func (d *DataplaneConfig) GetDeploymentTimeout() time.Duration

GetDeploymentTimeout returns the configured deployment timeout or the default if not specified or invalid.

func (*DataplaneConfig) GetDriftPreventionInterval

func (d *DataplaneConfig) GetDriftPreventionInterval() time.Duration

GetDriftPreventionInterval returns the configured drift prevention interval or the default if not specified or invalid.

func (*DataplaneConfig) GetMinDeploymentInterval

func (d *DataplaneConfig) GetMinDeploymentInterval() time.Duration

GetMinDeploymentInterval returns the configured minimum deployment interval or the default if not specified or invalid.

func (*DataplaneConfig) GetReloadVerificationTimeout

func (d *DataplaneConfig) GetReloadVerificationTimeout() time.Duration

GetReloadVerificationTimeout returns the configured reload-verification timeout or the default if not specified or invalid.

func (*DataplaneConfig) GetSyncTimeout

func (d *DataplaneConfig) GetSyncTimeout() time.Duration

GetSyncTimeout returns the configured per-endpoint sync timeout or the default if not specified or invalid.

type GeneralFile

type GeneralFile struct {
	// Template is the template content that generates the file.
	Template string `yaml:"template"`

	// PostProcessing defines optional post-processors to apply after rendering.
	// Post-processors are applied in order to transform the rendered output.
	PostProcessing []PostProcessorConfig `yaml:"post_processing,omitempty"`
}

GeneralFile is a general-purpose auxiliary file template.

type HAProxyConfig

type HAProxyConfig struct {
	// Template is the template content that generates haproxy.cfg.
	Template string `yaml:"template"`

	// PostProcessing defines optional post-processors to apply after rendering.
	// Post-processors are applied in order to transform the rendered output.
	// Common use case: indentation normalization with regex_replace.
	//
	// Example:
	//   post_processing:
	//     - type: regex_replace
	//       params:
	//         pattern: "^[ ]+"
	//         replace: "  "
	PostProcessing []PostProcessorConfig `yaml:"post_processing,omitempty"`
}

HAProxyConfig is the main HAProxy configuration template.

type HTTPResourceFixture

type HTTPResourceFixture struct {
	// URL is the HTTP URL that will be matched.
	// When a template calls http.Fetch() with this URL, the fixture content is returned.
	URL string `yaml:"url" json:"url"`

	// Content is the response body to return when this URL is fetched.
	Content string `yaml:"content" json:"content"`
}

HTTPResourceFixture defines mock HTTP content for validation tests.

This allows templates that use http.Fetch() to receive pre-defined content during validation tests without making actual HTTP requests.

type K8sResource

type K8sResource struct {
	// Template is the template content that generates the resource YAML.
	Template string `yaml:"template"`

	// PostProcessing defines optional post-processors to apply after rendering.
	// Post-processors are applied in order to transform the rendered output.
	PostProcessing []PostProcessorConfig `yaml:"post_processing,omitempty"`
}

K8sResource is a Kubernetes-resource-emitting template. The rendered output is parsed as one or more YAML documents and applied via SSA by the resourceapplier component.

type LeaderElectionConfig

type LeaderElectionConfig struct {
	// Enabled determines whether leader election is active.
	// If false, the controller assumes it is the sole instance (single-replica mode).
	// Default: true
	Enabled bool `yaml:"enabled"`

	// LeaseName is the name of the Lease resource used for coordination.
	// Default: haptic-leader
	LeaseName string `yaml:"lease_name"`

	// LeaseDuration is the duration that non-leader candidates will wait
	// to force acquire leadership (measured against time of last observed ack).
	// Format: Go duration string (e.g., "60s", "1m")
	// Default: 60s
	// Minimum: 15s
	LeaseDuration string `yaml:"lease_duration"`

	// RenewDeadline is the duration that the acting leader will retry
	// refreshing leadership before giving up.
	// Format: Go duration string (e.g., "15s")
	// Default: 15s
	// Must be less than LeaseDuration
	RenewDeadline string `yaml:"renew_deadline"`

	// RetryPeriod is the duration the LeaderElector clients should wait
	// between tries of actions.
	// Format: Go duration string (e.g., "5s")
	// Default: 5s
	// Must be less than RenewDeadline
	RetryPeriod string `yaml:"retry_period"`
}

LeaderElectionConfig configures leader election for running multiple replicas.

func (*LeaderElectionConfig) GetLeaseDuration

func (le *LeaderElectionConfig) GetLeaseDuration() time.Duration

GetLeaseDuration returns the configured lease duration or the default if not specified or invalid.

func (*LeaderElectionConfig) GetRenewDeadline

func (le *LeaderElectionConfig) GetRenewDeadline() time.Duration

GetRenewDeadline returns the configured renew deadline or the default if not specified or invalid.

func (*LeaderElectionConfig) GetRetryPeriod

func (le *LeaderElectionConfig) GetRetryPeriod() time.Duration

GetRetryPeriod returns the configured retry period or the default if not specified or invalid.

type LoggingConfig

type LoggingConfig struct {
	// Level controls the log level: TRACE, DEBUG, INFO, WARN, ERROR (case-insensitive)
	// Empty string means use LOG_LEVEL env var or default (INFO)
	Level string `yaml:"level"`
}

LoggingConfig configures logging behavior.

type MapFile

type MapFile struct {
	// Template is the template content that generates the map file.
	Template string `yaml:"template"`

	// PostProcessing defines optional post-processors to apply after rendering.
	// Post-processors are applied in order to transform the rendered output.
	PostProcessing []PostProcessorConfig `yaml:"post_processing,omitempty"`
}

MapFile is a HAProxy map file template.

type PodSelector

type PodSelector struct {
	// MatchLabels are the labels to match HAProxy pods.
	//
	// Example:
	//   app: haproxy
	//   component: loadbalancer
	MatchLabels map[string]string `yaml:"match_labels"`
}

PodSelector identifies which HAProxy pods to configure.

type PostProcessorConfig

type PostProcessorConfig struct {
	// Type specifies the post-processor type.
	// Supported values: "regex_replace"
	Type string `yaml:"type"`

	// Params contains type-specific configuration parameters.
	// For regex_replace:
	//   - pattern: Regular expression pattern to match (required)
	//   - replace: Replacement string (required)
	Params map[string]string `yaml:"params"`
}

PostProcessorConfig defines a post-processor to apply to rendered template output.

type Resolution

type Resolution struct {
	// ResolvedVersions maps watched-resource names to the apiVersion the
	// controller actually watches (the first served candidate).
	ResolvedVersions map[string]string

	// Unavailable lists optional watched-resource names with no served
	// candidate version, sorted alphabetically.
	Unavailable []string

	// StrippedSnippets and StrippedTests list the names of templateSnippets /
	// validationTests removed because a resource they require is unavailable,
	// sorted alphabetically.
	StrippedSnippets []string
	StrippedTests    []string

	// StrippedFieldTests lists the names of validationTests removed because
	// a field named in their RequiresFields is absent from the resolved
	// schema generation, sorted alphabetically. Disjoint from StrippedTests:
	// resource-level stripping wins when both apply. Unlike the other
	// stripped lists this is NOT derived from Unavailable — an in-place CRD
	// upgrade can change it while every resolved version stays the same, so
	// Equal compares it explicitly (the CRD watch relies on that to reload).
	StrippedFieldTests []string
}

Resolution describes the outcome of ResolveEffective: which version each watched resource resolved to, which optional resources are unavailable, and which config elements were stripped as a consequence.

func (*Resolution) Equal

func (r *Resolution) Equal(other *Resolution) bool

Equal reports whether two resolutions describe the same outcome — the same resolved version per resource, the same unavailable set, and the same field-stripped test set. Used by the CRD watch to decide whether a CRD change actually alters the effective config. StrippedSnippets and StrippedTests are derived from Unavailable and need no comparison of their own; StrippedFieldTests is derived from schema CONTENTS and must be compared — an in-place CRD upgrade that adds the missing fields changes it while every resolved version stays identical, and that difference is what makes the CRD watch reload and un-strip the tests.

type SSLCertificate

type SSLCertificate struct {
	// Template is the template content that generates the certificate file.
	Template string `yaml:"template"`

	// PostProcessing defines optional post-processors to apply after rendering.
	// Post-processors are applied in order to transform the rendered output.
	PostProcessing []PostProcessorConfig `yaml:"post_processing,omitempty"`
}

SSLCertificate is an SSL certificate file template.

type SchemaFieldChecker

type SchemaFieldChecker interface {
	// FieldServed returns true when the schema served for the plural
	// resource at the given group/version contains the dot-separated
	// field path (descending into array items transparently). Any
	// error is treated as transient and fails the whole resolution —
	// silently stripping on a schema-fetch blip would disable features
	// spuriously.
	FieldServed(apiVersion, resources, fieldPath string) (bool, error)
}

SchemaFieldChecker reports whether the RESOLVED schema of a served resource contains a specific field path. It backs the RequiresFields stripping on validation tests: a cluster may serve a resource at the same version string as newer releases while its schema generation lacks individual fields (e.g. Gateway API v1.1 serves httproutes at "v1" without the CORS filter). Implementations walk the resource's OpenAPI schema (live from the apiserver, or from --schema-dir offline); this package stays free of Kubernetes imports.

type ServedVersionChecker

type ServedVersionChecker interface {
	// IsServed returns true when the plural resource is served at the given
	// group/version (e.g. "gateway.networking.k8s.io/v1", "tcproutes").
	IsServed(apiVersion, resources string) bool
}

ServedVersionChecker reports whether the cluster (or an offline schema bundle) serves a resource at an exact API version. Implementations live in the coordination layer (live discovery) and the offline validate path (schema-dir CRD manifests); this package stays free of Kubernetes imports.

type TemplateSnippet

type TemplateSnippet struct {
	// Name is the snippet identifier for {{ render "name" }}.
	// This is derived from the map key in the configuration.
	Name string

	// Template is the template content.
	Template string `yaml:"template"`

	// Requires lists watched-resource names this snippet depends on. When an
	// optional watched resource has no served candidate version, every
	// snippet requiring it is stripped from the effective config at load
	// time (surviving snippets must reach stripped resources only through
	// compile-safe seams such as `render "..." default ""`).
	// Each entry must name a key of WatchedResources.
	Requires []string `yaml:"requires,omitempty"`
}

TemplateSnippet is a reusable template fragment.

type TemplatingSettings

type TemplatingSettings struct {
	// Engine specifies which template engine to use for rendering.
	// Valid values: "scriggo" (default and only supported engine)
	Engine string `yaml:"engine" json:"engine"`

	// RenderTimeout is the maximum time allowed for rendering a single template.
	// Uses Go duration format (e.g., "30s", "1m"). Default: "30s"
	RenderTimeout string `yaml:"render_timeout" json:"renderTimeout"`

	// ExtraContext provides custom variables that are passed to all templates.
	//
	// This allows users to add arbitrary data to the template context without
	// modifying controller code. Values can be any valid JSON type (string, number,
	// boolean, object, array).
	//
	// Example in YAML:
	//   extra_context:
	//     debug:
	//       enabled: true
	//     environment: production
	//     custom_value: 42
	//
	// Templates can then reference these variables directly: {{ debug.enabled }}, {{ environment }}, etc.
	ExtraContext map[string]any `yaml:"extra_context" json:"extraContext"`
}

TemplatingSettings configures template rendering behavior and custom variables.

func (*TemplatingSettings) GetRenderTimeout

func (t *TemplatingSettings) GetRenderTimeout() time.Duration

GetRenderTimeout returns the configured render timeout or the default.

type ValidationAssertion

type ValidationAssertion struct {
	// Type is the assertion type: "haproxy_valid", "contains", "not_contains", "match_count", "equals", "jsonpath", "match_order", "deterministic".
	Type string `yaml:"type"`

	// Description explains what this assertion validates.
	Description string `yaml:"description"`

	// Target specifies what to validate: "haproxy.cfg", "map:<name>", "file:<name>", "cert:<name>".
	// Only used for non-haproxy_valid assertions.
	Target string `yaml:"target"`

	// Pattern is the regex pattern to match (for "contains" and "not_contains").
	Pattern string `yaml:"pattern"`

	// Expected is the expected value (for "equals" and "jsonpath").
	Expected string `yaml:"expected"`

	// JSONPath is the JSONPath expression to query (for "jsonpath").
	JSONPath string `yaml:"jsonpath"`

	// Patterns is a list of regex patterns that must appear in the specified order (for "match_order").
	Patterns []string `yaml:"patterns"`
}

ValidationAssertion defines a single validation check.

type ValidationTest

type ValidationTest struct {
	// Description explains what this test validates.
	Description string `yaml:"description"`

	// Fixtures contains mock Kubernetes resources for this test.
	// The map key is the resource type name (e.g., "services", "ingresses").
	// The map value is a list of resources in unstructured format.
	Fixtures map[string][]any `yaml:"fixtures"`

	// HTTPFixtures contains mock HTTP responses for this test.
	// When templates call http.Fetch() for a URL that matches a fixture,
	// the fixture content is returned instead of making an actual HTTP request.
	HTTPFixtures []HTTPResourceFixture `yaml:"httpResources,omitempty"`

	// CurrentConfig contains the raw HAProxy configuration from a previous deployment.
	// This is used for testing slot-aware server assignment during rolling deployments.
	// When provided, templates can access currentConfig to preserve server slot ordering.
	// The content is parsed using the HAProxy config parser before being passed to templates.
	CurrentConfig string `yaml:"currentConfig,omitempty"`

	// ExtraContext provides custom variables that override the global extraContext for this test.
	// This allows testing template behavior with different extraContext values.
	ExtraContext map[string]any `yaml:"extraContext,omitempty" json:"extraContext,omitempty"`

	// MinHAProxyVersion specifies the minimum HAProxy version required to run this test.
	// Format: "major.minor" (e.g., "3.3"). When set, the test is skipped if the local
	// HAProxy version is below this threshold.
	MinHAProxyVersion string `yaml:"minHAProxyVersion,omitempty"`

	// Requires lists watched-resource names this test depends on. When an
	// optional watched resource has no served candidate version, every test
	// requiring it is stripped from the effective config at load time.
	// Each entry must name a key of WatchedResources.
	Requires []string `yaml:"requires,omitempty"`

	// RequiresFields lists schema field paths this test depends on, each in
	// the form "<watchedResourceKey>.<field.path>" (e.g.
	// "httproutes.spec.rules.filters.cors"). When ANY referenced field is
	// absent from the RESOLVED schema generation of its watched resource,
	// the test is stripped from the effective config at load time. This
	// covers clusters that serve the resource at the same version string
	// as newer releases but with an older schema generation lacking the
	// field — resource-level Requires stripping cannot fire there. The
	// first dot-segment of each entry must name a key of WatchedResources.
	RequiresFields []string `yaml:"requiresFields,omitempty"`

	// Assertions contains validation checks to run against the rendered config.
	Assertions []ValidationAssertion `yaml:"assertions"`
}

ValidationTest defines a single validation test with fixtures and assertions.

The test name is provided by the map key in ValidationTests.

type ValidatorConfig

type ValidatorConfig struct {
	// Name is the operator-facing validator identifier.
	Name string `yaml:"name"`

	// SocketPath is the absolute filesystem path to the validator's
	// Unix domain socket inside the controller pod.
	SocketPath string `yaml:"socket_path"`

	// Files is the list of glob patterns matched against rendered
	// file paths to decide which files to send to this validator.
	// Globs follow Go's `path/filepath.Match` rules; absolute paths
	// only.
	Files []string `yaml:"files"`

	// TimeoutMs is the per-call deadline in milliseconds, covering
	// the request-response cycle for one file. Zero falls back to
	// 5000 (5 seconds).
	TimeoutMs int32 `yaml:"timeout_ms,omitempty"`

	// MaxConnections caps the controller-side pool size for this
	// validator's socket. Zero falls back to 4. The pool starts
	// small and grows on contention up to this cap.
	MaxConnections int32 `yaml:"max_connections,omitempty"`
}

ValidatorConfig is the core-config mirror of v1alpha1.ValidatorConfig.

The CRD type carries Kubernetes validation tags; this type carries the raw values after conversion. Consumers (controller startup, the pluggablevalidator package) work against this type so they don't take a dependency on the v1alpha1 API surface.

The wire protocol this struct configures lives in docs/development/validator-protocol.md.

type WatchedResource

type WatchedResource struct {
	// APIVersion is the Kubernetes API version (e.g., "networking.k8s.io/v1").
	// Mutually exclusive with APIVersions; equivalent to a one-element list.
	APIVersion string `yaml:"api_version"`

	// APIVersions is an ordered candidate list of API versions. At iteration
	// start the controller resolves the entry to the FIRST candidate the
	// apiserver serves, and that resolved version is used everywhere the
	// literal APIVersion would be (informer GVR, stores, schema fetch,
	// webhook registration, fixture defaulting). Mutually exclusive with
	// APIVersion.
	//
	// Example: ["gateway.networking.k8s.io/v1", "gateway.networking.k8s.io/v1beta1"]
	APIVersions []string `yaml:"api_versions,omitempty"`

	// Optional marks this resource as non-essential: when NO candidate
	// version is served by the cluster, the watch is dropped and every
	// TemplateSnippet / ValidationTest whose Requires names this resource is
	// stripped from the effective config at load time, instead of failing
	// startup. When false (default), an unserved resource fails the
	// iteration fast with a named error.
	Optional bool `yaml:"optional,omitempty"`

	// Resources is the plural form of the Kubernetes resource type (e.g., "ingresses", "services").
	// This is the name used in RBAC rules and API paths.
	Resources string `yaml:"resources"`

	// EnableValidationWebhook enables admission webhook validation for this resource.
	// Default: false
	EnableValidationWebhook bool `yaml:"enable_validation_webhook"`

	// IndexBy specifies JSONPath expressions for extracting index keys.
	//
	// Resources are indexed by these values for O(1) lookup.
	//
	// Examples:
	//   ["metadata.namespace", "metadata.name"]
	//   ["metadata.labels['kubernetes.io/service-name']"]
	IndexBy []string `yaml:"index_by"`

	// LabelSelector filters resources by labels (server-side filtering).
	//
	// Example:
	//   app: haproxy
	//   component: loadbalancer
	LabelSelector map[string]string `yaml:"label_selector,omitempty"`

	// FieldSelector filters resources using client-side JSONPath evaluation.
	// Unlike Kubernetes' native fieldSelector (which only supports limited fields),
	// this supports any JSONPath expression.
	//
	// Format: "field.path=value"
	// Example: "spec.ingressClassName=haproxy-internal"
	FieldSelector string `yaml:"field_selector,omitempty"`

	// Store specifies the storage backend: "full" (MemoryStore) or "on-demand" (CachedStore).
	// Default: "full"
	//
	// Use "on-demand" for large resources that are accessed infrequently (e.g., Secrets).
	// Use "full" for frequently accessed resources (e.g., Services, EndpointSlices).
	Store string `yaml:"store"`

	// DebounceInterval overrides the default leading-edge refractory window
	// for this resource type's watcher. Format: Go duration string (e.g.,
	// "500ms", "10s"). When empty or invalid, the watcher falls back to
	// pkg/k8s/types.DefaultDebounceInterval (100ms).
	//
	// Lower values make the controller respond faster to bursty changes at
	// the cost of more reconciliations and GC pressure. Most workloads
	// should leave this empty.
	DebounceInterval string `yaml:"debounce_interval,omitempty"`
}

WatchedResource configures watching for a specific Kubernetes resource type.

func (*WatchedResource) CandidateVersions

func (r *WatchedResource) CandidateVersions() []string

CandidateVersions returns the ordered API-version candidate list for this resource: APIVersions when set, otherwise the singular APIVersion as a one-element list. Validation guarantees exactly one of the two fields is populated, so callers can treat the result as the single source of truth.

func (*WatchedResource) GetDebounceInterval

func (r *WatchedResource) GetDebounceInterval() time.Duration

GetDebounceInterval returns the configured debounce interval. The CRD field is a Go duration string. Mapping:

  • empty / unparseable → 0, signalling "no override" — the watcher's WatcherConfig.SetDefaults swaps it for pkg/k8s/types.DefaultDebounceInterval.
  • explicit zero ("0", "0s", "0ms", …) → pkg/k8s/types.DebounceImmediate (-1), signalling "fire on every event without batching." SetDefaults normalises this to 0 before reaching the Debouncer, whose leading-edge code already handles the zero case correctly.
  • any other valid duration → parsed value.

The two distinct return values for "user wrote 0" vs "user wrote nothing" are why this function can't just return the ParseDuration result directly: the WatcherConfig contract treats 0 as "unset, apply default," so a genuine immediate-fire intent has to ride a different value through the pipeline.

Jump to

Keyboard shortcuts

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