Documentation
¶
Overview ¶
Package dataplane provides a simple, high-level API for synchronizing HAProxy configurations via the Dataplane API.
The library handles all complexity internally:
- Fetches current configuration from the Dataplane API
- Parses both current and desired configurations
- Computes a fine-grained ConfigDiff to classify changes as runtime-eligible server-field updates (weight, address, port, maintenance, agent checks) vs. structural changes
- Applies the desired configuration by pushing it in full in a single request (no per-operation transactions): a skip-reload raw push carrying an X-Runtime-Actions header when every change is runtime-eligible, otherwise a force-reload raw push
- Retries transient connection failures (the master socket is briefly down while HAProxy re-execs on reload)
- Returns detailed results including applied changes and reload information
Basic Usage (Recommended) ¶
For production use, create a Client to reuse connections across multiple operations:
endpoint := &dataplane.Endpoint{
URL: "http://haproxy:5555/v3",
Username: "admin",
Password: "secret",
}
// Create client once, reuse for multiple operations
client, err := dataplane.NewClient(context.Background(), endpoint)
if err != nil {
slog.Error("Failed to create client", "error", err)
os.Exit(1)
}
defer client.Close()
desiredConfig := `
global
daemon
defaults
mode http
timeout client 30s
timeout server 30s
timeout connect 5s
backend web
balance roundrobin
server srv1 192.168.1.10:80 check
`
result, err := client.Sync(ctx, desiredConfig, nil, nil)
if err != nil {
slog.Error("Sync failed", "error", err)
os.Exit(1)
}
fmt.Printf("Applied %d operations\n", len(result.AppliedOperations))
if result.ReloadTriggered {
fmt.Printf("HAProxy reloaded (ID: %s)\n", result.ReloadID)
}
Simple One-Off Operations ¶
For quick scripts, use the convenience functions (creates client internally):
result, err := dataplane.Sync(ctx, endpoint, desiredConfig, nil, nil)
Custom Options ¶
Configure sync behavior with options:
client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
return err
}
defer client.Close()
opts := &dataplane.SyncOptions{
Timeout: 3 * time.Minute, // Overall timeout
VerifyReload: true, // Poll reload status after sync
ReloadVerificationTimeout: 10 * time.Second,
}
result, err := client.Sync(ctx, desiredConfig, nil, opts)
Error Handling ¶
The library provides detailed, actionable error messages:
client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
return err
}
defer client.Close()
result, err := client.Sync(ctx, desiredConfig, nil, nil)
if err != nil {
if syncErr, ok := errors.AsType[*dataplane.SyncError](err); ok {
fmt.Printf("Stage: %s\n", syncErr.Stage)
fmt.Printf("Error: %s\n", syncErr.Message)
for _, hint := range syncErr.Hints {
fmt.Printf(" Hint: %s\n", hint)
}
}
}
Index ¶
- Variables
- func ComputeContentChecksum(haproxyConfig string, auxFiles *AuxiliaryFiles) string
- func SetHAProxyExecutor(e HAProxyExecutor) (restore func())
- func SimplifyRenderingError(err error) string
- func SimplifyValidationError(err error) string
- func ValidateConfiguration(mainConfig string, auxFiles *AuxiliaryFiles, paths *ValidationPaths, ...) (*parser.StructuredConfig, error)
- func ValidateSemantics(mainConfig string, auxFiles *AuxiliaryFiles, paths *ValidationPaths, ...) error
- func ValidateSyntaxAndSchema(config string, version *Version) (*parser.StructuredConfig, error)
- type AppliedOperation
- type AuxiliaryFiles
- type Capabilities
- type Client
- type ConfigParser
- type ConnectionError
- type DiffDetails
- type Endpoint
- type HAProxyExecutor
- type ParseError
- type PathConfig
- type ResolvedPaths
- type RuntimeServerUpdates
- type SyncError
- type SyncMode
- type SyncOptions
- type SyncPhase
- type SyncResult
- type ValidationError
- type ValidationPaths
- type Version
Constants ¶
This section is empty.
Variables ¶
var ErrValidationCacheHit = errors.New("validation cache hit")
ErrValidationCacheHit is returned when validation is skipped because the same configuration was already validated successfully. Callers should use the parser cache to obtain the parsed configuration if needed.
Functions ¶
func ComputeContentChecksum ¶
func ComputeContentChecksum(haproxyConfig string, auxFiles *AuxiliaryFiles) string
ComputeContentChecksum generates a SHA256 checksum covering the main HAProxy config and all auxiliary files (general files, map files, SSL certificates, CRT-list files).
The checksum is used for content deduplication to skip redundant processing when config content hasn't changed. Auxiliary file slices must be pre-sorted (by AuxiliaryFiles.Sort()) to ensure deterministic results regardless of insertion order.
Returns a hex-encoded 8-byte (16 character) checksum for brevity.
func SetHAProxyExecutor ¶
func SetHAProxyExecutor(e HAProxyExecutor) (restore func())
SetHAProxyExecutor replaces the executor used by DetectLocalVersion and semantic validation, returning a function that restores the previous one. It exists so unit tests can substitute a fake instead of shelling out; production code must not call it. Prefer installing the fake once per test package (TestMain) — per-test installs interleave badly with t.Parallel.
func SimplifyRenderingError ¶
SimplifyRenderingError extracts meaningful error messages from template rendering failures.
Handles template-level validation errors from the fail() function which are buried in the template engine's execution stack trace.
Input format:
"failed to render haproxy.cfg: failed to render template 'haproxy.cfg': unable to execute template: ... invalid call to function 'fail': <message>"
Output: "<message>" (the user-provided error message from fail() call)
If the error doesn't match this pattern (e.g., syntax errors, missing variables), returns the original error string.
func SimplifyValidationError ¶
SimplifyValidationError parses HAProxy validation errors and extracts the key information for user-friendly error messages.
Handles two types of validation errors:
Schema validation errors - OpenAPI spec violations: Input: "schema validation failed: configuration violates API schema constraints: ... Error at "/field": constraint" Output: "field constraint (got value)"
Semantic validation errors - HAProxy binary validation failures: Input: "semantic validation failed: configuration has semantic errors: haproxy validation failed: <context>" Output: "<context>" (preserves parseHAProxyError output with context lines)
Returns original error string if parsing fails.
func ValidateConfiguration ¶
func ValidateConfiguration(mainConfig string, auxFiles *AuxiliaryFiles, paths *ValidationPaths, version *Version, skipDNSValidation bool) (*parser.StructuredConfig, error)
ValidateConfiguration performs three-phase HAProxy configuration validation.
Phase 1: Syntax validation using client-native parser Phase 1.5: API schema validation using OpenAPI spec (patterns, formats, required fields) Phase 2: Semantic validation using haproxy binary (-c flag)
The validation writes files to the directories specified in paths. Callers must ensure that paths are isolated (e.g., per-worker temp directories) to allow parallel execution.
Validation result caching: If the same config (main + aux files + version) has been successfully validated before, the cached result is returned immediately. This is safe because HAProxy config validation is deterministic - the same inputs always produce the same result.
Parameters:
- mainConfig: The rendered HAProxy configuration (haproxy.cfg content)
- auxFiles: All auxiliary files (maps, certificates, general files)
- paths: Filesystem paths for validation (must be isolated for parallel execution)
- version: HAProxy/DataPlane API version for schema selection (nil uses default v3.0)
- skipDNSValidation: If true, adds -dr flag to skip DNS resolution failures. Use true for runtime validation (permissive, prevents blocking when DNS fails) and false for webhook validation (strict, catches DNS issues before resource admission).
Returns:
- *parser.StructuredConfig: The pre-parsed configuration from syntax validation (nil on cache hit or error)
- error: nil if validation succeeds, ValidationError with phase information if validation fails
func ValidateSemantics ¶
func ValidateSemantics(mainConfig string, auxFiles *AuxiliaryFiles, paths *ValidationPaths, skipDNSValidation bool) error
ValidateSemantics performs semantic validation using the haproxy binary (-c flag).
This function runs only Phase 2 (semantic validation) and assumes syntax/schema validation has already been done. Use this after ValidateSyntaxAndSchema() when you need to validate a modified config (e.g., with temp paths) separately from parsing.
Parameters:
- mainConfig: The HAProxy configuration content (may have modified paths for temp directory)
- auxFiles: All auxiliary files (maps, certificates, general files)
- paths: Filesystem paths for validation (must be isolated for parallel execution)
- skipDNSValidation: If true, adds -dr flag to skip DNS resolution failures
Returns:
- error: ValidationError with phase "semantic" if validation fails
func ValidateSyntaxAndSchema ¶
func ValidateSyntaxAndSchema(config string, version *Version) (*parser.StructuredConfig, error)
ValidateSyntaxAndSchema performs syntax and schema validation on HAProxy configuration.
This function runs Phase 1 (syntax validation) and Phase 1.5 (API schema validation) but NOT Phase 2 (semantic validation with haproxy binary).
Use this when you need to parse the config without needing file I/O or the haproxy binary. The primary use case is when you need to parse the original config (before path modifications) for downstream reuse, while semantic validation is done separately with a modified config.
Parameters:
- config: The HAProxy configuration content to validate
- version: HAProxy/DataPlane API version for schema selection (nil uses default v3.0)
Returns:
- *parser.StructuredConfig: The parsed configuration
- error: ValidationError with phase information if validation fails
Types ¶
type AppliedOperation ¶
type AppliedOperation struct {
// Type is the operation type: "create", "update", or "delete"
Type string
// Section is the configuration section: "backend", "server", "frontend", "acl", "http-rule", etc.
Section string
// Resource is the resource name or identifier (e.g., backend name, server name)
Resource string
// Description is a human-readable description of what was changed
Description string
}
AppliedOperation represents a single applied configuration change.
type AuxiliaryFiles ¶
type AuxiliaryFiles struct {
// GeneralFiles contains general-purpose files (error pages, custom response files, etc.)
GeneralFiles []auxiliaryfiles.GeneralFile
// SSLCertificates contains SSL certificates to sync to HAProxy SSL storage
SSLCertificates []auxiliaryfiles.SSLCertificate
// SSLCaFiles contains SSL CA certificate files for client/backend certificate verification
SSLCaFiles []auxiliaryfiles.SSLCaFile
// MapFiles contains map files for backend routing and other map-based features
MapFiles []auxiliaryfiles.MapFile
// CRTListFiles contains crt-list files for SSL certificate lists with per-certificate options
CRTListFiles []auxiliaryfiles.CRTListFile
}
AuxiliaryFiles contains files to synchronize alongside configuration changes. Diffs are applied in two of the three sync phases (see SyncPhase in phases.go):
- PhasePreConfig: creates and updates (files must exist before config references them)
- PhasePostConfig: deletes (safe to remove only after config stops referencing them)
func DefaultAuxiliaryFiles ¶
func DefaultAuxiliaryFiles() *AuxiliaryFiles
DefaultAuxiliaryFiles returns an empty auxiliary files struct.
func (*AuxiliaryFiles) Sort ¶
func (a *AuxiliaryFiles) Sort()
Sort sorts all auxiliary file slices in-place by their path/filename. This establishes a deterministic order so that downstream consumers (checksum, diff, etc.) can iterate directly without cloning or sorting.
type Capabilities ¶
type Capabilities = client.Capabilities
Capabilities defines which features are available for a given HAProxy/DataPlane API version. This type is re-exported from pkg/dataplane/client for convenience.
func CapabilitiesFromVersion ¶
func CapabilitiesFromVersion(v *Version) Capabilities
CapabilitiesFromVersion computes capabilities based on a HAProxy version. This is used for local HAProxy binary detection (haproxy -v).
Capability thresholds (verified against OpenAPI specs):
- SupportsCrtList: v3.2+ (CRT-list storage endpoint)
- SupportsMapStorage: v3.0+ (Map file storage endpoint)
- SupportsGeneralStorage: v3.0+ (General file storage)
- SupportsSslCaFiles: v3.2+ (SSL CA file runtime endpoint)
- SupportsSslCrlFiles: v3.2+ (SSL CRL file runtime endpoint)
- SupportsLogProfiles: v3.1+ (Log profiles configuration endpoint)
- SupportsTraces: v3.1+ (Traces configuration endpoint)
- SupportsAcmeProviders: v3.2+ (ACME provider configuration endpoint)
- SupportsQUIC: v3.0+ (QUIC/HTTP3 configuration options)
- SupportsQUICInitialRules: v3.1+ (QUIC initial rules endpoints)
- SupportsHTTP2: v3.0+ (HTTP/2 configuration)
- SupportsRuntimeMaps: v3.0+ (Runtime map operations)
- SupportsRuntimeServers: v3.0+ (Runtime server operations)
type Client ¶
type Client struct {
// Endpoint contains connection information
Endpoint Endpoint
// contains filtered or unexported fields
}
Client manages a persistent connection to the HAProxy Dataplane API. It reuses connections for multiple operations, making it efficient for repeated sync operations.
For production use with multiple operations, create a Client explicitly:
client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
return err
}
defer client.Close()
// Reuse client for multiple operations
result1, err := client.Sync(ctx, config1, auxFiles1, opts)
result2, err := client.Sync(ctx, config2, auxFiles2, opts)
func NewClient ¶
NewClient creates a new Client for the given endpoint. The client reuses connections for multiple operations.
Example:
// NewClient takes endpoint by pointer (so the controller can mutate
// the cached version fields on the same struct).
endpoint := &dataplane.Endpoint{
URL: "http://haproxy:5555/v3",
Username: "admin",
Password: "secret",
}
client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
return fmt.Errorf("creating client: %w", err)
}
defer client.Close()
result, err := client.Sync(ctx, desiredConfig, nil, nil)
func (*Client) Close ¶
Close releases client resources. The current implementation has no background work to clean up, but the method is part of the documented API so existing `defer client.Close()` call sites stay valid as the client gains owned resources.
func (*Client) Sync ¶
func (c *Client) Sync(ctx context.Context, desiredConfig string, auxFiles *AuxiliaryFiles, opts *SyncOptions) (*SyncResult, error)
Sync synchronizes the desired HAProxy configuration using this client.
This method:
- Fetches the current configuration from the Dataplane API
- Parses both current and desired configurations
- Compares them to compute a fine-grained ConfigDiff, classifying changes as runtime-eligible server-field updates vs. structural changes
- Applies the desired configuration with a single full-config push (no per-operation transactions): a skip-reload raw push carrying X-Runtime-Actions when every change is runtime-eligible, otherwise a force-reload raw push
- Retries transient connection failures across HAProxy's reload re-exec
- Returns detailed results including applied changes and reload information
Parameters:
- ctx: Context for cancellation and timeout
- desiredConfig: The desired HAProxy configuration as a string
- auxFiles: Auxiliary files to sync (use nil for defaults)
- opts: Sync options (use nil for defaults)
Returns:
- *SyncResult: Detailed information about the sync operation
- error: Detailed error with actionable hints if the sync fails
Example:
client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
return err
}
defer client.Close()
result, err := client.Sync(ctx, desiredConfig, nil, nil)
if err != nil {
return fmt.Errorf("sync failed: %w", err)
}
fmt.Printf("Applied %d operations in %v\n", len(result.AppliedOperations), result.Duration)
func (*Client) SyncRuntimeFast ¶
func (c *Client) SyncRuntimeFast(ctx context.Context, updates *RuntimeServerUpdates, desiredConfig string, opts *SyncOptions) (*SyncResult, error)
SyncRuntimeFast is the runtime-raw apply: a single raw config push of the DESIRED config body with skip_reload+skip_version, carrying the precomputed render diff's runtime `set server` actions (X-Runtime-Actions). No per-pod fetch and no reload — cost is O(config size) for the push but independent of the number of pods (the diff is computed once and shared across pods).
updates is the precomputed runtime-eligible render diff (ComputeRuntimeServerUpdates); desiredConfig is the render whose server addresses/states the actions move the live worker to. Callers: the deployer's runtime-raw lane (a purely runtime-eligible render — this is the only apply), and the scheduler's pre-interval apply (the runtime subset of a STRUCTURAL render, applied off the interval-gated path before the gated reload). When the body carries structural changes they land on disk un-activated until that gated reload — never hidden from a reload indefinitely. Reloads are config-driven (no server-state-file — ADR-0011), so the change persists across any later structural reload because that deploy re-renders the current endpoints.
type ConfigParser ¶
type ConfigParser interface {
ParseFromString(config string) (*parserconfig.StructuredConfig, error)
}
ConfigParser defines the interface for HAProxy configuration parsing. Both CE (parser.Parser) and EE (enterprise.Parser) parsers implement this interface.
type ConnectionError ¶
type ConnectionError struct {
// Endpoint is the URL that failed to connect
Endpoint string
// Cause is the underlying connection error
Cause error
}
ConnectionError represents a failure to connect to the Dataplane API.
func (*ConnectionError) Error ¶
func (e *ConnectionError) Error() string
Error implements the error interface.
func (*ConnectionError) Unwrap ¶
func (e *ConnectionError) Unwrap() error
Unwrap returns the underlying cause for error unwrapping.
type DiffDetails ¶
type DiffDetails struct {
// Total operation counts
TotalOperations int
Creates int
Updates int
Deletes int
// Global and defaults changes
GlobalChanged bool
DefaultsChanged bool
// Frontend changes
FrontendsAdded []string
FrontendsModified []string
FrontendsDeleted []string
// Backend changes
BackendsAdded []string
BackendsModified []string
BackendsDeleted []string
// Backend diff fields: for each modified backend, the BackendBase fields that differ.
// Only populated when backend attribute changes (not nested collections) cause the update.
BackendDiffFields map[string][]string
// Server changes (map of backend -> server names)
ServersAdded map[string][]string
ServersModified map[string][]string
ServersDeleted map[string][]string
// Auxiliary file changes
MapsAdded int
MapsModified int
MapsDeleted int
SSLCertsAdded int
SSLCertsModified int
SSLCertsDeleted int
SSLCaFilesAdded int
SSLCaFilesModified int
SSLCaFilesDeleted int
GeneralFilesAdded int
GeneralFilesModified int
GeneralFilesDeleted int
}
DiffDetails contains detailed diff information about configuration changes.
func NewDiffDetails ¶
func NewDiffDetails() DiffDetails
NewDiffDetails creates an empty DiffDetails with initialized maps.
func (*DiffDetails) String ¶
func (d *DiffDetails) String() string
String returns a human-readable summary of the diff details.
type Endpoint ¶
type Endpoint struct {
// URL is the Dataplane API endpoint (e.g., "http://haproxy:5555/v3").
// A trailing "/v2" or "/v3" is stripped before the v3 version-detection probe;
// only v3.x operations are supported (no v2-only endpoints).
URL string
// Username for basic authentication
Username string
// Password for basic authentication
Password string
// PodName is the Kubernetes pod name (for observability)
PodName string
// PodNamespace is the Kubernetes pod namespace (for observability)
PodNamespace string
// Version info (cached after discovery admission, avoids redundant /v3/info calls)
// Zero values indicate version not yet detected.
DetectedMajorVersion int // Major version (e.g., 3)
DetectedMinorVersion int // Minor version (e.g., 2)
DetectedFullVersion string // Full version string (e.g., "v3.2.6 87ad0bcf")
}
Endpoint represents HAProxy Dataplane API connection information.
func (*Endpoint) HasCachedVersion ¶
HasCachedVersion returns true if version info has been cached on this endpoint.
type HAProxyExecutor ¶
type HAProxyExecutor interface {
// Version returns the combined output of `haproxy -v`.
Version() (string, error)
// Check runs `haproxy <args...>` with the working directory set to
// workDir and returns the combined output. The working directory
// matters: semantic validation invokes haproxy with a relative config
// path so that relative file references inside the config resolve.
Check(workDir string, args ...string) ([]byte, error)
}
HAProxyExecutor abstracts execution of the local HAProxy binary so that unit tests never shell out to an external process. Production code uses the default binaryHAProxyExecutor; tests install a fake via SetHAProxyExecutor (most conveniently through pkg/dataplane/dataplanetest.InstallFakeHAProxy in a TestMain).
type ParseError ¶
type ParseError struct {
// ConfigType indicates which config failed: "current" or "desired"
ConfigType string
// ConfigSnippet contains the first 200 characters of the problematic config
ConfigSnippet string
// Cause is the underlying parsing error
Cause error
}
ParseError represents a configuration parsing failure.
func (*ParseError) Error ¶
func (e *ParseError) Error() string
Error implements the error interface.
func (*ParseError) Unwrap ¶
func (e *ParseError) Unwrap() error
Unwrap returns the underlying cause for error unwrapping.
type PathConfig ¶
type PathConfig struct {
// MapsDir is the base path for HAProxy map files (e.g., /etc/haproxy/maps).
MapsDir string
// SSLDir is the base path for HAProxy SSL certificates (e.g., /etc/haproxy/ssl).
SSLDir string
// GeneralDir is the base path for HAProxy general files (e.g., /etc/haproxy/general).
GeneralDir string
// ConfigFile is the path to the HAProxy configuration file (e.g., /tmp/haproxy.cfg).
// Only used in validation contexts; can be empty for production paths.
ConfigFile string
}
PathConfig contains the base directory configuration for HAProxy auxiliary files. These are the raw filesystem paths before capability-based resolution.
type ResolvedPaths ¶
type ResolvedPaths struct {
// MapsDir is the resolved path for HAProxy map files.
MapsDir string
// SSLDir is the resolved path for HAProxy SSL certificates.
SSLDir string
// CRTListDir is the resolved path for CRT-list files.
// Always equals GeneralDir because CRT-list files are stored as general files
// to avoid reload on create (native CRT-list API doesn't support skip_reload).
CRTListDir string
// GeneralDir is the resolved path for HAProxy general files.
GeneralDir string
// ConfigFile is the path to the HAProxy configuration file.
ConfigFile string
}
ResolvedPaths contains capability-aware resolved paths for HAProxy auxiliary files. This is the result of applying capability-based resolution to a PathConfig.
The key difference from PathConfig is that CRTListDir is always set to GeneralDir because CRT-list files are stored as general files to avoid reload on create.
func ResolvePaths ¶
func ResolvePaths(base PathConfig) *ResolvedPaths
ResolvePaths applies capability-based path resolution to base paths. This is the SINGLE SOURCE OF TRUTH for all capability-dependent path logic.
Currently handles:
- CRT-list storage: Always uses general directory because the native CRT-list API triggers reload on create without supporting skip_reload parameter. General file storage returns 201 without triggering reload.
func (*ResolvedPaths) ToValidationPaths ¶
func (r *ResolvedPaths) ToValidationPaths() *ValidationPaths
ToValidationPaths converts ResolvedPaths to ValidationPaths. Use this when you need ValidationPaths for HAProxy configuration validation. TempDir is derived from ConfigFile's parent directory (e.g., /tmp/validate-xxx/haproxy.cfg -> /tmp/validate-xxx).
type RuntimeServerUpdates ¶
type RuntimeServerUpdates struct {
// contains filtered or unexported fields
}
RuntimeServerUpdates is the precomputed result of diffing the previous render against the current one for runtime-eligible server changes. The render diff (comparator.Compare, O(config size)) is render-vs-render and so identical for every pod; callers compute it ONCE per fire via ComputeRuntimeServerUpdates and apply it to every pod, instead of re-diffing per pod.
func ComputeRuntimeServerUpdates ¶
func ComputeRuntimeServerUpdates(prev, current *parser.StructuredConfig) (*RuntimeServerUpdates, error)
ComputeRuntimeServerUpdates diffs prev (last-dispatched render) against current (this render) for runtime-eligible server changes. It is a pure, pod- independent computation (no client, no fetch), so the deployer's fast path runs it ONCE per render and applies the result to every pod. Returns a non-nil result even when there are no changes.
func (*RuntimeServerUpdates) IsRuntimeEligible ¶
func (u *RuntimeServerUpdates) IsRuntimeEligible() bool
IsRuntimeEligible reports whether this diff can be applied entirely through the runtime fast path: it carries at least one runtime-eligible server change and NO structural (reload-inducing) operation. A diff with structural ops, or with nothing at all to apply, is not runtime-eligible (the deployer routes it to the rate-limited structural lane).
func (*RuntimeServerUpdates) ServerOpCount ¶
func (u *RuntimeServerUpdates) ServerOpCount() int
ServerOpCount returns the number of runtime-eligible server changes in the set (0 = nothing to apply). Safe on a nil receiver.
func (*RuntimeServerUpdates) StructuralOpCount ¶
func (u *RuntimeServerUpdates) StructuralOpCount() int
StructuralOpCount returns the number of non-runtime-eligible (reload-inducing) operations in the render diff. Safe on a nil receiver (returns 0). The deployer's lane classifier uses this to decide runtime-raw vs structural: a diff with zero structural ops can apply purely via the runtime fast path (no reload), bypassing the deployment interval.
type SyncError ¶
type SyncError struct {
// Stage indicates where the failure occurred. Common values:
// "connect", "parse-current", "parse-desired",
// "compare", "compare_files", "compare_ssl", "compare_ssl_ca", "compare_maps", "compare_crtlists",
// "sync_ssl_pre", "sync_ssl_ca_pre", "sync_files_pre", "sync_maps_pre",
// "apply", "commit", "reload_verification", "auxiliary_reload_verification",
// "fallback"
Stage string
// Message provides a detailed error description
Message string
// Cause is the underlying error that caused the failure
Cause error
// Hints provides actionable suggestions for fixing the problem
Hints []string
}
SyncError represents a synchronization failure with actionable context. It provides detailed information about what stage failed and suggestions for how to fix the problem.
func NewConnectionError ¶
NewConnectionError creates a ConnectionError.
func NewParseError ¶
NewParseError creates a ParseError.
type SyncMode ¶
type SyncMode string
SyncMode indicates which sync strategy was used.
const ( // SyncModeNoChanges indicates the diff was empty; no API calls were made. SyncModeNoChanges SyncMode = "no_changes" // SyncModeRuntime indicates the runtime path: one PushRawConfigurationSkipReload // with X-Runtime-Actions, no HAProxy reload. SyncModeRuntime SyncMode = "runtime" // SyncModeReload indicates the reload path: optional skip_reload+actions push // to seed the live worker, then force_reload. SyncModeReload SyncMode = "reload" )
type SyncOptions ¶
type SyncOptions struct {
// Timeout for the entire sync operation (default: 2 minutes)
Timeout time.Duration
// VerifyReload enables async reload verification after sync (default: true)
// When true, polls the reload status endpoint until succeeded/failed/timeout.
// Disable for dry-run or when reload verification is not needed.
VerifyReload bool
// ReloadVerificationTimeout is the maximum time to wait for reload verification (default: 10s)
// This should be set higher than the DataPlane API's reload-delay setting.
// Only used when VerifyReload is true.
ReloadVerificationTimeout time.Duration
// PreParsedConfig is an optional pre-parsed desired configuration. When
// non-nil, sync skips parsing the desiredConfig string.
PreParsedConfig *parserconfig.StructuredConfig
// CachedCurrentConfig is an optional cached parsed current configuration
// from a previous sync. When set with CachedConfigVersion, sync calls
// GetVersion() first and reuses the cached config if the version matches.
CachedCurrentConfig *parserconfig.StructuredConfig
// CachedConfigVersion is the expected config version on the pod. Only
// used when CachedCurrentConfig is also set.
CachedConfigVersion int64
// ContentChecksum is the pre-computed checksum of the desired config +
// aux files. When it matches LastDeployedChecksum, the expensive aux
// file comparison is skipped because the desired state is identical
// to what was last deployed.
ContentChecksum string
// LastDeployedChecksum is the content checksum from the last successful
// sync to this endpoint. Drift prevention syncs leave this empty.
LastDeployedChecksum string
// RestampVersionHeader (SyncRuntimeFast only) re-writes the pushed body
// WITH a `# _version=N` header after a successful pure-runtime apply, via
// one versioned skip_reload push. A skip_version push leaves the on-disk
// config headerless, and sync() refuses to trust an empty diff against a
// headerless config (it forces a reload to activate potentially parked
// content). Re-stamping proves "disk == running state" so the next
// structural sync stays reload-free.
//
// Callers must set this ONLY when no structural deploy can be in flight
// on the pod (the deployer's authoritative runtime-raw lane dispatch).
// A fast-track partial apply racing an in-flight structural reload must
// leave the config headerless: its `set server` actions can land on the
// worker the reload replaces, and a re-stamped header would let the next
// sync trust an empty diff over that lost update.
RestampVersionHeader bool
}
SyncOptions configures synchronization behavior.
func DefaultSyncOptions ¶
func DefaultSyncOptions() *SyncOptions
DefaultSyncOptions returns sensible default sync options.
type SyncPhase ¶
type SyncPhase int
SyncPhase identifies one of the three phases of a configuration sync.
HAProxy configs can reference auxiliary files (maps, SSL certs, error pages, etc.). A single sync therefore runs in three phases, sequenced so that referenced files exist before the config mentions them and orphaned files are only cleaned up after the config that stopped referencing them has been applied:
- PhasePreConfig — create or update auxiliary files. Includes verifying any reloads those operations trigger, so the next phase's config operations don't race against pending reloads.
- PhaseConfig — apply HAProxy configuration changes by pushing the full rendered config (raw push), reloading only when structural changes are present.
- PhasePostConfig — delete auxiliary files that the new config no longer references. Must run after PhaseConfig succeeds.
The enum is advisory — used in log fields and error context — rather than a control-flow driver; the orchestrator still calls the phase-specific helpers directly.
type SyncResult ¶
type SyncResult struct {
// Success indicates whether the sync completed successfully
Success bool
// AppliedOperations contains structured information about operations that were applied
AppliedOperations []AppliedOperation
// ReloadTriggered indicates whether a HAProxy reload was triggered
// true when commit status is 202, false when 200
ReloadTriggered bool
// ReloadID is the reload identifier from the Reload-ID response header
// Only set when ReloadTriggered is true
ReloadID string
// ReloadVerified indicates whether the reload was verified as successful.
// Only set when VerifyReload option is enabled and ReloadTriggered is true.
ReloadVerified bool
// ReloadVerificationError contains an error message if reload verification failed.
// This includes timeout errors or explicit reload failures from HAProxy.
ReloadVerificationError string
// SyncMode indicates which sync strategy was used.
// See SyncMode* constants for possible values.
SyncMode SyncMode
// Duration of the sync operation
Duration time.Duration
// Details contains detailed diff information
// This field is always populated regardless of SyncMode
Details DiffDetails
// Message provides additional context about the result
Message string
// PostSyncVersion is the config version on the pod after a successful sync.
// Callers can cache this alongside the desired parsed config to skip
// redundant GetRawConfiguration() + parse on subsequent syncs when the
// pod's version hasn't changed. Zero means version was not captured.
//
// Never carries 1: version 1 is the headerless sentinel — a config
// written by a skip_version push (the runtime bypass) has no
// `# _version=N` header and GetVersion reads it as 1 regardless of
// body, so 1 cannot discriminate states and must never be cached
// (see fetchCurrentConfig). The runtime fast path (SyncRuntimeFast)
// deliberately leaves PostSyncVersion zero and PostSyncParsedConfig
// nil: it is fetch-free by design, and its skip_version push strips
// the version header, forcing the next versioned sync to take a
// cache miss and re-fetch the pod's actual state.
PostSyncVersion int64
// PostSyncParsedConfig is the pod's actual configuration AFTER the sync
// completed, fetched and parsed from the dataplane API. Set only when
// AppliedOperations is non-empty AND the post-sync fetch+parse
// succeeded; nil otherwise (no-changes paths, or fetch/parse failure —
// callers fall back to the desired config in those cases).
//
// Callers should cache this in preference to their input desired config:
// incremental dataplane patches don't guarantee byte-identity with the
// caller's intent (different starting baselines across pods produce
// logically-equivalent-but-byte-different end states). Caching the
// actual post-sync state lets subsequent drift checks compare apples to
// apples — the comparator sees pod-actual vs desired, not
// desired vs desired.
PostSyncParsedConfig *parserconfig.StructuredConfig
}
SyncResult contains detailed information about a sync operation.
func (*SyncResult) String ¶
func (r *SyncResult) String() string
String returns a human-readable summary of the sync result.
type ValidationError ¶
type ValidationError struct {
// Phase indicates which validation phase failed: "syntax" or "semantic"
Phase string
// Message is the validation error message
Message string
// Cause is the underlying error
Cause error
}
ValidationError represents semantic validation failure from HAProxy.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
Error implements the error interface.
func (*ValidationError) Unwrap ¶
func (e *ValidationError) Unwrap() error
Unwrap returns the underlying error for error unwrapping.
type ValidationPaths ¶
type ValidationPaths struct {
// TempDir is the root temp directory for validation files.
// The validator is responsible for cleaning this up after validation completes.
// This prevents race conditions where the renderer's cleanup runs before
// the async validator can use the validation files.
TempDir string
MapsDir string
SSLCertsDir string
CRTListDir string // Directory for CRT-list files (may differ from SSLCertsDir on HAProxy < 3.2)
GeneralStorageDir string
ConfigFile string
}
ValidationPaths holds the filesystem paths for HAProxy validation. These paths must match the HAProxy Dataplane API server's resource configuration.
type Version ¶
Version is the project-wide HAProxy / DataPlane API version type. It is an alias of client.Version (Major.Minor.Patch with Compare), kept under the dataplane.Version name so existing callers (discovery, validation, testrunner, …) compile unchanged.
func DetectLocalVersion ¶
DetectLocalVersion runs "haproxy -v" (via the installed HAProxyExecutor) and returns the local HAProxy version. Returns an error if haproxy is not found or version cannot be parsed.
func ParseHAProxyVersionOutput ¶
ParseHAProxyVersionOutput parses the output of "haproxy -v" command. Expected format: "HAProxy version 3.2.9 2025/11/21 - https://haproxy.org/\n..." Returns extracted major.minor version.
func ParseVersionString ¶
ParseVersionString parses a version string like "3.3" or "3.3.0" into a Version. This is useful for parsing user-provided version constraints.
Unlike client.ParseVersion it is intentionally strict about the "v" prefix: a leading "v" is rejected rather than stripped, so an operator typo such as "v3.3.beta" surfaces as a parse error (callers then run the test anyway rather than silently skipping it). The original input is preserved verbatim in Version.Full so callers can echo it back unchanged.
func VersionFromAPIInfo ¶
func VersionFromAPIInfo(info *client.VersionInfo) (*Version, error)
VersionFromAPIInfo converts client.VersionInfo (from /v3/info) to Version. The API version string format is "vX.Y.Z commit" (e.g., "v3.2.6 87ad0bcf").
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package auxiliaryfiles provides functionality for synchronizing auxiliary files (general files, SSL certificates, map files, crt-lists) with the HAProxy Dataplane API.
|
Package auxiliaryfiles provides functionality for synchronizing auxiliary files (general files, SSL certificates, map files, crt-lists) with the HAProxy Dataplane API. |
|
Package client provides a high-level wrapper around the generated HAProxy Dataplane API client.
|
Package client provides a high-level wrapper around the generated HAProxy Dataplane API client. |
|
Package comparator provides comparison functions for HAProxy Enterprise Edition sections.
|
Package comparator provides comparison functions for HAProxy Enterprise Edition sections. |
|
sections
Package sections provides factory functions for creating HAProxy configuration operations.
|
Package sections provides factory functions for creating HAProxy configuration operations. |
|
Package dataplanetest provides test doubles for pkg/dataplane.
|
Package dataplanetest provides test doubles for pkg/dataplane. |
|
Package parser provides HAProxy configuration parsing using client-native library.
|
Package parser provides HAProxy configuration parsing using client-native library. |
|
enterprise
Package enterprise provides HAProxy Enterprise Edition configuration parsing.
|
Package enterprise provides HAProxy Enterprise Edition configuration parsing. |
|
enterprise/parsers
Package parsers provides wrapper parsers for HAProxy Enterprise Edition directives.
|
Package parsers provides wrapper parsers for HAProxy Enterprise Edition directives. |
|
parserconfig
Package parserconfig provides canonical configuration types for HAProxy parsing.
|
Package parserconfig provides canonical configuration types for HAProxy parsing. |
|
sectionextract
Package sectionextract extracts standard (Community Edition) HAProxy sections from a client-native config-parser into a parserconfig.StructuredConfig.
|
Package sectionextract extracts standard (Community Edition) HAProxy sections from a client-native config-parser into a parserconfig.StructuredConfig. |
|
Package validators provides zero-allocation OpenAPI validation for HAProxy models.
|
Package validators provides zero-allocation OpenAPI validation for HAProxy models. |