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
- func ParseSecretData(dataRaw map[string]any) (map[string][]byte, error)
- func ResolveEffective(cfg *Config, served ServedVersionChecker, fields SchemaFieldChecker) (*Config, *Resolution, error)
- func SetDefaults(cfg *Config)
- func ValidateCredentials(creds *Credentials) error
- func ValidateStructure(cfg *Config) error
- type CRTListFile
- type Config
- type ConfigPublishingConfig
- type ControllerConfig
- type Credentials
- type DataplaneConfig
- func (d *DataplaneConfig) GetConfigPublishInterval() time.Duration
- func (d *DataplaneConfig) GetDeploymentTimeout() time.Duration
- func (d *DataplaneConfig) GetDriftPreventionInterval() time.Duration
- func (d *DataplaneConfig) GetMinDeploymentInterval() time.Duration
- func (d *DataplaneConfig) GetReloadVerificationTimeout() time.Duration
- func (d *DataplaneConfig) GetSyncTimeout() time.Duration
- type GeneralFile
- type HAProxyConfig
- type HTTPResourceFixture
- type K8sResource
- type LeaderElectionConfig
- type LoggingConfig
- type MapFile
- type PodSelector
- type PostProcessorConfig
- type Resolution
- type SSLCertificate
- type SchemaFieldChecker
- type ServedVersionChecker
- type TemplateSnippet
- type TemplatingSettings
- type ValidationAssertion
- type ValidationTest
- type ValidatorConfig
- type WatchedResource
Constants ¶
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.
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.
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.
const DefaultRenderTimeout = 30 * time.Second
DefaultRenderTimeout is the default maximum duration for rendering a single template.
Variables ¶
This section is empty.
Functions ¶
func ParseSecretData ¶
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 ¶
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
// 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.