dataplane

package
v0.1.0-alpha.12 Latest Latest
Warning

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

Go to latest
Published: Feb 27, 2026 License: Apache-2.0 Imports: 27 Imported by: 0

README

HAProxy Dataplane Sync Library

A Go library for synchronizing HAProxy configurations via the Dataplane API. Provide an endpoint and a desired configuration, and it ensures HAProxy matches that configuration.

Features

  • Minimal API: Provide an endpoint and desired config string
  • Connection Reuse: Client-based API for connection management
  • Two-Phase Validation: Syntax validation (client-native parser) + semantic validation (haproxy binary)
  • Fine-grained Sync: Uses granular operations (create/update/delete servers, backends, ACLs, etc.)
  • Automatic Fallback: Falls back to raw config push if fine-grained sync fails
  • Conflict Resolution: Retries on version conflicts (409 errors)
  • Structured Results: Returns information about applied changes
  • Reload Optimization: Uses runtime API when possible to avoid HAProxy reloads
  • Detailed Errors: Error messages with hints for troubleshooting

Installation

go get haptic/pkg/dataplane

Quick Start

Simple (One-Off Operations)

For quick scripts or one-off operations, use the convenience functions:

package main

import (
    "context"
    "log"

    "haptic/pkg/dataplane"
)

func main() {
    endpoint := dataplane.Endpoint{
        URL:      "http://haproxy:5555/v2",
        Username: "admin",
        Password: "secret",
    }

    desiredConfig := `
global
    daemon
    maxconn 4096

defaults
    mode http
    timeout client 30s
    timeout server 30s
    timeout connect 5s

backend web
    balance roundrobin
    server web1 192.168.1.10:80 check
    server web2 192.168.1.11:80 check
`

    // Convenience function - creates client internally
    result, err := dataplane.Sync(context.Background(), endpoint, desiredConfig, nil, nil)
    if err != nil {
        log.Fatalf("sync failed: %v", err)
    }

    log.Printf("Applied %d operations in %v\n", len(result.AppliedOperations), result.Duration)
}
Production (Reusable Client)

For production use with multiple operations, create a client explicitly:

func main() {
    endpoint := dataplane.Endpoint{
        URL:      "http://haproxy:5555/v2",
        Username: "admin",
        Password: "secret",
    }

    // Create client once, reuse for multiple operations
    client, err := dataplane.NewClient(context.Background(), endpoint)
    if err != nil {
        log.Fatalf("failed to create client: %v", err)
    }
    defer client.Close()

    // Reuse client for multiple sync operations (efficient!)
    result1, err := client.Sync(ctx, config1, nil, nil)
    result2, err := client.Sync(ctx, config2, nil, nil)
    diff, err := client.DryRun(ctx, config3)
}

Usage Examples

Client Management

Production Pattern (Recommended):

// Create client once
client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
    return err
}
defer client.Close()

// Reuse for multiple operations
result, err := client.Sync(ctx, desiredConfig, nil, nil)

Simple Pattern (Quick Scripts):

// For one-off operations - 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{
    MaxRetries:       5,                 // Retry 409 conflicts up to 5 times
    Timeout:          3 * time.Minute,   // Overall timeout
    ContinueOnError:  false,             // Stop on first error
    FallbackToRaw:    true,              // Fall back to raw push on errors
    RawPushThreshold: 100,               // Use raw push when changes exceed this count
}

result, err := client.Sync(ctx, desiredConfig, nil, opts)

Options explained:

  • MaxRetries: How many times to retry on 409 version conflicts (default: 3)
  • Timeout: Overall timeout for the sync operation (default: 2 minutes)
  • ContinueOnError: Continue applying operations even if some fail (default: false)
  • FallbackToRaw: Automatically fall back to raw config push on non-recoverable errors (default: true)
  • RawPushThreshold: Trigger raw config push when change count exceeds this value (0 = disabled)
Dry Run (Preview Changes)

Preview what changes would be applied without actually applying them:

client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
    return err
}
defer client.Close()

diff, err := client.DryRun(ctx, desiredConfig)
if err != nil {
    log.Fatal(err)
}

if !diff.HasChanges {
    fmt.Println("No changes needed")
    return
}

fmt.Printf("Would apply %d operations:\n", len(diff.PlannedOperations))
for _, op := range diff.PlannedOperations {
    fmt.Printf("  - %s %s '%s'\n", op.Type, op.Section, op.Resource)
}
Detailed Diff

Get detailed information about configuration differences:

client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
    return err
}
defer client.Close()

diff, err := client.Diff(ctx, desiredConfig)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Backends added: %v\n", diff.Details.BackendsAdded)
fmt.Printf("Backends modified: %v\n", diff.Details.BackendsModified)
fmt.Printf("Servers deleted: %v\n", diff.Details.ServersDeleted)
Inspecting Results

The sync result contains detailed information:

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 {
    log.Fatal(err)
}

// Check applied operations
for _, op := range result.AppliedOperations {
    fmt.Printf("%s %s '%s': %s\n", op.Type, op.Section, op.Resource, op.Description)
}

// Check reload status
if result.ReloadTriggered {
    fmt.Printf("HAProxy reloaded with ID: %s\n", result.ReloadID)
}

// Check sync mode (fine-grained vs raw push)
if result.UsedRawPush() {
    fmt.Printf("Warning: Used raw config push (mode: %s)\n", result.SyncMode)
}
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 {
    // Check for specific error types
    var syncErr *dataplane.SyncError
    if errors.As(err, &syncErr) {
        fmt.Printf("Failed at stage: %s\n", syncErr.Stage)
        fmt.Printf("Error: %s\n", syncErr.Message)
        fmt.Println("\nTroubleshooting hints:")
        for _, hint := range syncErr.Hints {
            fmt.Printf("  • %s\n", hint)
        }
    }

    return
}
Context and Timeout

Use context for cancellation and timeouts:

// Timeout via context
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
    return err
}
defer client.Close()

result, err := client.Sync(ctx, desiredConfig, nil, nil)

// Or timeout via options (overrides context timeout)
opts := &dataplane.SyncOptions{
    Timeout: 1 * time.Minute,
}
result, err := client.Sync(ctx, desiredConfig, nil, opts)
Configuration Validation

Validate HAProxy configurations before deployment with two-phase validation:

import (
    "haptic/pkg/dataplane"
)

func main() {
    // Main HAProxy configuration
    mainConfig := `
global
    daemon

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

frontend http-in
    bind :80
    http-request set-header X-Backend %[base,map(maps/hosts.map,default)]
    default_backend servers

backend servers
    server s1 127.0.0.1:8080
`

    // Auxiliary files (maps, certificates, error pages)
    auxFiles := &dataplane.AuxiliaryFiles{
        MapFiles: []dataplane.MapFile{
            {
                Path:    "maps/hosts.map",
                Content: "example.com backend1\ntest.com backend2\n",
            },
        },
    }

    // Validate configuration
    err := dataplane.ValidateConfiguration(mainConfig, auxFiles)
    if err != nil {
        var valErr *dataplane.ValidationError
        if errors.As(err, &valErr) {
            fmt.Printf("Validation failed in %s phase: %s\n", valErr.Phase, valErr.Message)
        }
        return
    }

    fmt.Println("Configuration is valid!")
}

Two-Phase Validation:

  1. Phase 1 - Syntax Validation: Uses client-native parser to validate configuration structure and syntax
  2. Phase 2 - Semantic Validation: Runs haproxy -c -f config to perform full semantic validation

The validator writes auxiliary files to the actual HAProxy directories on disk (with mutex locking to prevent concurrent writes) to validate file references (maps, certificates, error pages) exactly as the Dataplane API does.

ValidationError Fields:

  • Phase: Either "syntax" or "semantic" indicating which phase failed
  • Message: Human-readable error description
  • Err: Wrapped underlying error for detailed inspection
Path Requirements for Auxiliary Files

All auxiliary file references in HAProxy configuration must use absolute paths matching the configured validation paths.

Required Configuration:

Validation paths must match the HAProxy Dataplane API server's resource configuration. These are configured via the validation section in the controller ConfigMap:

validation:
  maps_dir: /etc/haproxy/maps
  ssl_certs_dir: /etc/haproxy/certs
  general_storage_dir: /etc/haproxy/general
  config_file: /etc/haproxy/haproxy.cfg

Supported Paths:

/etc/haproxy/maps/host.map - absolute path to map file ✅ /etc/haproxy/general/503.http - absolute path to general file ✅ /etc/haproxy/certs/server.pem - absolute path to SSL certificate

Example:

config := `
frontend http-in
    bind :80
    http-request set-header X-Backend %[base,map(/etc/haproxy/maps/host.map,default)]
    errorfile 503 /etc/haproxy/general/503.http
`

auxFiles := &AuxiliaryFiles{
    MapFiles: []auxiliaryfiles.MapFile{
        {Path: "/etc/haproxy/maps/host.map", Content: "example.com backend1\n"},
    },
    GeneralFiles: []auxiliaryfiles.GeneralFile{
        {Filename: "503.http", Content: "HTTP/1.0 503 Service Unavailable\n"},
    },
}

paths := ValidationPaths{
    MapsDir:           "/etc/haproxy/maps",
    SSLCertsDir:       "/etc/haproxy/certs",
    GeneralStorageDir: "/etc/haproxy/general",
    ConfigFile:        "/etc/haproxy/haproxy.cfg",
}

err := ValidateConfiguration(config, auxFiles, paths)

Validation Behavior:

  • Validation writes files directly to the configured paths on disk
  • A mutex ensures only one validation runs at a time to prevent concurrent writes
  • Validation directories are cleared before each validation to ensure clean state
  • This approach matches exactly how the HAProxy Dataplane API validates configurations
Feature Detection with Capabilities

The library provides capability detection for HAProxy version-specific features:

import "haptic/pkg/dataplane"

// When using DataPlane API client
client, err := dataplane.NewClient(ctx, endpoint)
if client.Clientset().Capabilities().SupportsCrtList {
    // Use CRT-list storage (v3.2+ only)
}

// When using local HAProxy binary (e.g., CLI validation)
localVersion, err := dataplane.GetLocalVersion(ctx)
if err == nil {
    caps := dataplane.CapabilitiesFromVersion(localVersion)
    if caps.SupportsCrtList {
        // Configure CRT-list based paths
    }
}

Available Capabilities:

Capability Description HAProxy Version
SupportsCrtList CRT-list file storage v3.2+
SupportsMapStorage Map file storage v3.1+
SupportsGeneralStorage General file storage v3.0+
SupportsHTTP2 HTTP/2 protocol v3.0+
SupportsQUIC QUIC/HTTP3 protocol v3.2+
SupportsAdvancedACLs Advanced ACL features v3.1+
SupportsRuntimeMaps Runtime map updates v3.0+
SupportsRuntimeServers Runtime server updates v3.0+

How It Works

The library performs the following steps:

  1. Fetch Current Config: Retrieves the current HAProxy configuration from the Dataplane API
  2. Parse Configurations: Parses both current and desired configs into structured objects
  3. Compare: Generates fine-grained operations (create server, delete ACL, update backend, etc.)
  4. Execute: Applies operations with automatic retry on version conflicts (409 errors)
  5. Fallback: If fine-grained sync fails, automatically falls back to raw config push
  6. Results: Returns detailed information about what was changed
Fine-Grained vs Raw Sync

Fine-Grained Sync (default):

  • Individual operations for each change
  • Minimal HAProxy reloads
  • Uses runtime API when possible (server weight/status changes)
  • Detailed operation tracking

Raw Config Push (fallback):

  • Pushes complete configuration
  • Always triggers reload
  • Used when fine-grained sync fails
  • Simple but less efficient

API Reference

Main Functions
Sync(ctx, endpoint, desiredConfig, opts) (*SyncResult, error)

Synchronizes the desired configuration to HAProxy.

Parameters:

  • ctx: Context for cancellation and timeout
  • endpoint: Dataplane API connection info
  • desiredConfig: Desired HAProxy configuration as string
  • opts: Sync options (use nil for defaults)

Returns:

  • *SyncResult: Detailed sync results
  • error: Error with actionable hints if sync fails
DryRun(ctx, endpoint, desiredConfig) (*DiffResult, error)

Previews changes without applying them.

Parameters:

  • ctx: Context for cancellation and timeout
  • endpoint: Dataplane API connection info
  • desiredConfig: Desired HAProxy configuration as string

Returns:

  • *DiffResult: Planned operations and diff details
  • error: Error if comparison fails
Diff(ctx, endpoint, desiredConfig) (*DiffResult, error)

Alias for DryRun() - compares configurations and returns differences.

Types
Endpoint
type Endpoint struct {
    URL      string  // Dataplane API URL (e.g., "http://haproxy:5555/v2")
    Username string  // Basic auth username
    Password string  // Basic auth password
}
SyncOptions
type SyncOptions struct {
    MaxRetries       int           // Retry limit for 409 conflicts (default: 3)
    Timeout          time.Duration // Overall timeout (default: 2 minutes)
    ContinueOnError  bool          // Continue on operation failure (default: false)
    FallbackToRaw    bool          // Auto-fallback to raw push (default: true)
    RawPushThreshold int           // Raw push when changes exceed threshold (0 = disabled)
}
SyncResult
type SyncResult struct {
    Success           bool              // Whether sync succeeded
    AppliedOperations []AppliedOperation // Structured operations applied
    ReloadTriggered   bool              // Whether reload was triggered
    ReloadID          string            // Reload ID (if triggered)
    SyncMode          SyncMode          // Sync strategy used (fine_grained, raw_initial, raw_threshold, raw_fallback)
    Duration          time.Duration     // Operation duration
    Retries           int               // Number of retries
    Details           DiffDetails       // Detailed diff information
    Message           string            // Summary message
}

// SyncMode indicates which sync strategy was used
const (
    SyncModeFineGrained  SyncMode = "fine_grained"  // Fine-grained API operations
    SyncModeRawInitial   SyncMode = "raw_initial"   // Raw push for initial config (version=1)
    SyncModeRawThreshold SyncMode = "raw_threshold" // Raw push because changes exceeded threshold
    SyncModeRawFallback  SyncMode = "raw_fallback"  // Raw push as fallback after fine-grained failure
)

// UsedRawPush() returns true if any form of raw config push was used
func (r *SyncResult) UsedRawPush() bool
DiffResult
type DiffResult struct {
    HasChanges        bool                // Whether any differences exist
    PlannedOperations []PlannedOperation  // Operations that would be executed
    Details           DiffDetails         // Detailed diff information
}

Best Practices

1. Use Client for Multiple Operations

Production code should reuse clients:

// Good - create once, reuse
client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
    return err
}
defer client.Close()

// Efficient - reuses connection
diff, err := client.DryRun(ctx, newConfig)
if diff.HasChanges {
    result, err := client.Sync(ctx, newConfig, nil, nil)
}

Avoid recreating clients:

// Bad - creates new connection each time
for _, config := range configs {
    result, err := dataplane.Sync(ctx, endpoint, config, nil, nil)  // inefficient!
}

// Good - reuses connection
client, err := dataplane.NewClient(ctx, endpoint)
defer client.Close()

for _, config := range configs {
    result, err := client.Sync(ctx, config, nil, nil)  // efficient!
}
2. Use Dry Run Before Applying

Always preview changes in production:

client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
    return err
}
defer client.Close()

// Preview
diff, err := client.DryRun(ctx, newConfig)
if err != nil {
    return err
}

if diff.HasChanges {
    fmt.Printf("About to apply %d changes\n", len(diff.PlannedOperations))
    // Show to human operator for confirmation

    // Apply
    result, err := client.Sync(ctx, newConfig, nil, nil)
}
3. Handle Errors Properly

Check for specific error types and provide context:

client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
    return err
}
defer client.Close()

result, err := client.Sync(ctx, config, nil, nil)
if err != nil {
    var syncErr *dataplane.SyncError
    if errors.As(err, &syncErr) {
        log.Printf("Sync failed at %s stage: %s", syncErr.Stage, syncErr.Message)
        // Log hints for debugging
        for _, hint := range syncErr.Hints {
            log.Printf("Hint: %s", hint)
        }
    }
    return fmt.Errorf("failed to sync HAProxy: %w", err)
}
4. Configure Appropriate Timeouts

Set timeouts based on your environment:

opts := &dataplane.SyncOptions{
    Timeout:    5 * time.Minute,  // Longer for large configs
    MaxRetries: 5,                // More retries in busy environments
}

client, err := dataplane.NewClient(ctx, endpoint)
defer client.Close()

result, err := client.Sync(ctx, config, nil, opts)
5. Monitor Raw Push Usage

Alert on unexpected raw config push:

client, err := dataplane.NewClient(ctx, endpoint)
defer client.Close()

result, err := client.Sync(ctx, config, nil, nil)
if err == nil && result.UsedRawPush() {
    log.Warn("Used raw config push",
        "mode", result.SyncMode,
        "reason", getRawPushReason(result.SyncMode))
    // Send alert for fallback mode (indicates fine-grained sync failure)
    if result.SyncMode == dataplane.SyncModeRawFallback {
        sendAlert("Fine-grained sync failed, used fallback")
    }
}

Troubleshooting

Connection Errors

Problem: Can't connect to Dataplane API

Solutions:

  • Verify endpoint URL is correct
  • Check HAProxy is running and accessible
  • Verify credentials
  • Check network connectivity
  • Ensure Dataplane API is enabled in HAProxy config
Parse Errors

Problem: Configuration parsing fails

Solutions:

  • Validate config syntax: haproxy -c -f config.cfg
  • Check for syntax errors in desired config
  • Verify config is compatible with HAProxy version
Version Conflicts

Problem: Getting 409 errors even with retries

Solutions:

  • Increase MaxRetries in options
  • Coordinate config updates to avoid concurrent modifications
  • Check for other automation tools modifying HAProxy
Validation Errors

Problem: HAProxy rejects the configuration

Solutions:

  • Check for references to non-existent backends/servers
  • Verify all directives are compatible with HAProxy version
  • Ensure resource dependencies are satisfied
  • Review validation error messages from HAProxy

License

This library is part of the HAPTIC project.

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
  • Generates fine-grained operations to transform current → desired
  • Executes operations with automatic retry on version conflicts (409 errors)
  • Falls back to raw config push on non-recoverable errors
  • Returns detailed results including applied changes and reload information

For production use, create a Client to reuse connections across multiple operations:

endpoint := dataplane.Endpoint{
    URL:      "http://haproxy:5555/v2",
    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{
    MaxRetries:      5,                 // Retry 409 conflicts up to 5 times
    Timeout:         3 * time.Minute,   // Overall timeout
    ContinueOnError: false,             // Stop on first error
    FallbackToRaw:   true,              // Fall back to raw push on errors
}

result, err := client.Sync(ctx, desiredConfig, nil, opts)

Dry Run

Preview changes without applying them:

client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
    return err
}
defer client.Close()

diff, err := client.DryRun(ctx, desiredConfig)
if err != nil {
    slog.Error("dry run failed", "error", err)
    os.Exit(1)
}

fmt.Printf("Would apply %d operations:\n", len(diff.PlannedOperations))
for _, op := range diff.PlannedOperations {
    fmt.Printf("  - %s\n", op.Description)
}

Diff Only

Get detailed diff information:

client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
    return err
}
defer client.Close()

diff, err := client.Diff(ctx, desiredConfig)
if err != nil {
    slog.Error("diff failed", "error", err)
    os.Exit(1)
}

fmt.Printf("Backends added: %v\n", diff.Details.BackendsAdded)
fmt.Printf("Servers modified: %d\n", len(diff.Details.ServersModified))

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 {
    var syncErr *dataplane.SyncError
    if errors.As(err, &syncErr) {
        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

Constants

This section is empty.

Variables

View Source
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 SimplifyRenderingError

func SimplifyRenderingError(err error) string

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

func SimplifyValidationError(err error) string

SimplifyValidationError parses HAProxy validation errors and extracts the key information for user-friendly error messages.

Handles two types of validation errors:

  1. Schema validation errors - OpenAPI spec violations: Input: "schema validation failed: configuration violates API schema constraints: ... Error at "/field": constraint" Output: "field constraint (got value)"

  2. 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 before configuration changes. These files are synced in two phases:

  • Phase 1 (pre-config): Creates and updates are applied before config sync
  • Phase 2 (post-config): Deletes are applied after successful config sync

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 simple one-off operations, use the package-level convenience functions (Sync, DryRun, Diff) which create a client internally.

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

func NewClient(ctx context.Context, endpoint *Endpoint) (*Client, error)

NewClient creates a new Client for the given endpoint. The client reuses connections for multiple operations.

Example:

endpoint := dataplane.Endpoint{
    URL:      "http://haproxy:5555/v2",
    Username: "admin",
    Password: "secret",
}

client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
    return fmt.Errorf("failed to create client: %w", err)
}
defer client.Close()

result, err := client.Sync(ctx, desiredConfig, nil, nil)

func (*Client) Close

func (c *Client) Close() error

Close cleans up client resources. Currently a no-op, but provided for future resource cleanup needs.

func (*Client) Diff

func (c *Client) Diff(ctx context.Context, desiredConfig string) (*DiffResult, error)

Diff compares the current and desired configurations and returns detailed differences.

This is an alias for DryRun - both methods perform the same operation. Use whichever name makes more sense in your context.

Parameters:

  • ctx: Context for cancellation and timeout
  • desiredConfig: The desired HAProxy configuration as a string

Returns:

  • *DiffResult: Detailed information about differences
  • error: Error if comparison fails

Example:

client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
    return err
}
defer client.Close()

diff, err := client.Diff(ctx, desiredConfig)
if err != nil {
    return fmt.Errorf("diff failed: %w", err)
}

fmt.Printf("Backends added: %v\n", diff.Details.BackendsAdded)
fmt.Printf("Backends modified: %v\n", diff.Details.BackendsModified)
fmt.Printf("Servers deleted: %d total\n", len(diff.Details.ServersDeleted))

func (*Client) DryRun

func (c *Client) DryRun(ctx context.Context, desiredConfig string) (*DiffResult, error)

DryRun previews what changes would be applied without actually applying them.

This method performs all the same steps as Sync except for the actual application:

  1. Fetches the current configuration from the Dataplane API
  2. Parses both current and desired configurations
  3. Compares them to generate a list of planned operations
  4. Returns the diff without executing any operations

This is useful for:

  • Previewing changes before applying them
  • Validating configurations
  • Understanding what would change

Parameters:

  • ctx: Context for cancellation and timeout
  • desiredConfig: The desired HAProxy configuration as a string

Returns:

  • *DiffResult: Detailed information about planned changes
  • error: Error if comparison fails

Example:

client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
    return err
}
defer client.Close()

diff, err := client.DryRun(ctx, desiredConfig)
if err != nil {
    return fmt.Errorf("dry run failed: %w", err)
}

if diff.HasChanges {
    fmt.Printf("Would apply %d operations:\n", len(diff.PlannedOperations))
    for _, op := range diff.PlannedOperations {
        fmt.Printf("  - %s %s %s\n", op.Type, op.Section, op.Resource)
    }
}

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:

  1. Fetches the current configuration from the Dataplane API
  2. Parses both current and desired configurations
  3. Compares them to generate fine-grained operations
  4. Executes operations with automatic retry on 409 version conflicts
  5. Falls back to raw config push on non-recoverable errors (if enabled)
  6. 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)

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 ConflictError

type ConflictError struct {
	// Retries is the number of retry attempts made
	Retries int

	// ExpectedVersion is the version we tried to use
	ExpectedVersion int64

	// ActualVersion is the version that exists on the server
	ActualVersion string
}

ConflictError represents unresolved version conflicts after exhausting retries.

func (*ConflictError) Error

func (e *ConflictError) Error() string

Error implements the error 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

	// Server changes (map of backend -> server names)
	ServersAdded    map[string][]string
	ServersModified map[string][]string
	ServersDeleted  map[string][]string

	// ACL changes (map of parent resource -> ACL names)
	ACLsAdded    map[string][]string
	ACLsModified map[string][]string
	ACLsDeleted  map[string][]string

	// HTTP rule changes (map of parent resource -> count)
	HTTPRulesAdded    map[string]int
	HTTPRulesModified map[string]int
	HTTPRulesDeleted  map[string]int

	// 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 DiffResult

type DiffResult struct {
	// HasChanges indicates whether any differences were detected
	HasChanges bool

	// PlannedOperations contains structured information about operations that would be executed
	PlannedOperations []PlannedOperation

	// Details contains detailed diff information
	Details DiffDetails
}

DiffResult contains comparison results without applying changes.

func Diff

func Diff(ctx context.Context, endpoint *Endpoint, desiredConfig string) (*DiffResult, error)

Diff compares the current and desired configurations and returns detailed differences.

This is a convenience function that creates a client internally for one-off operations. This is an alias for DryRun. For production use with multiple operations, create a Client explicitly.

Parameters:

  • ctx: Context for cancellation and timeout
  • endpoint: Dataplane API connection information
  • desiredConfig: The desired HAProxy configuration as a string

Returns:

  • *DiffResult: Detailed information about differences
  • error: Error if comparison fails

func DryRun

func DryRun(ctx context.Context, endpoint *Endpoint, desiredConfig string) (*DiffResult, error)

DryRun previews what changes would be applied without actually applying them.

This is a convenience function that creates a client internally for one-off operations. For production use with multiple operations, create a Client explicitly.

Parameters:

  • ctx: Context for cancellation and timeout
  • endpoint: Dataplane API connection information
  • desiredConfig: The desired HAProxy configuration as a string

Returns:

  • *DiffResult: Detailed information about planned changes
  • error: Error if comparison fails

func (*DiffResult) String

func (r *DiffResult) String() string

String returns a human-readable summary of the diff result.

type Endpoint

type Endpoint struct {
	// URL is the Dataplane API endpoint (e.g., "http://haproxy:5555/v2")
	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

func (e *Endpoint) HasCachedVersion() bool

HasCachedVersion returns true if version info has been cached on this endpoint.

func (*Endpoint) Redacted

func (e *Endpoint) Redacted() map[string]string

Redacted returns a redacted version of the endpoint for safe logging. Credentials are masked to prevent exposure in logs.

type FallbackError

type FallbackError struct {
	// OriginalError is the error that triggered the fallback
	OriginalError error

	// FallbackCause is the error that occurred during fallback
	FallbackCause error
}

FallbackError represents a failure during raw config fallback.

func (*FallbackError) Error

func (e *FallbackError) Error() string

Error implements the error interface.

func (*FallbackError) Unwrap

func (e *FallbackError) Unwrap() error

Unwrap returns the fallback cause for error unwrapping.

type OperationError

type OperationError struct {
	// OperationType is "create", "update", or "delete"
	OperationType string

	// Section is the configuration section (e.g., "backend", "server")
	Section string

	// Resource is the resource identifier (e.g., backend name, server name)
	Resource string

	// Cause is the underlying error
	Cause error
}

OperationError represents a failure of a specific configuration operation.

func (*OperationError) Error

func (e *OperationError) Error() string

Error implements the error interface.

func (*OperationError) Unwrap

func (e *OperationError) Unwrap() error

Unwrap returns the underlying cause for error unwrapping.

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

	// Line indicates the approximate line number where parsing failed (if available)
	Line int

	// 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 PlannedOperation

type PlannedOperation 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
	Resource string

	// Description is a human-readable description of what would be changed
	Description string

	// Priority indicates execution order (lower = earlier for creates, higher = earlier for deletes)
	Priority int
}

PlannedOperation represents an operation that would be executed.

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, _ Capabilities) *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.

Future capability-dependent paths should be added here to ensure consistent resolution across all components.

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 SyncError

type SyncError struct {
	// Stage indicates where the failure occurred:
	// "connect", "fetch", "parse-current", "parse-desired", "compare", "apply", "commit", "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 NewConflictError

func NewConflictError(retries int, expectedVersion int64, actualVersion string) *SyncError

NewConflictError creates a ConflictError.

func NewConnectionError

func NewConnectionError(endpoint string, cause error) *SyncError

NewConnectionError creates a ConnectionError.

func NewFallbackError

func NewFallbackError(originalErr, fallbackCause error) *SyncError

NewFallbackError creates a FallbackError.

func NewOperationError

func NewOperationError(opType, section, resource string, cause error) *SyncError

NewOperationError creates an OperationError.

func NewParseError

func NewParseError(configType, configSnippet string, cause error) *SyncError

NewParseError creates a ParseError.

func NewValidationError

func NewValidationError(message string, cause error) *SyncError

NewValidationError creates a ValidationError.

func (*SyncError) Error

func (e *SyncError) Error() string

Error implements the error interface.

func (*SyncError) Unwrap

func (e *SyncError) Unwrap() error

Unwrap returns the underlying cause for error unwrapping.

type SyncMode

type SyncMode string

SyncMode indicates which sync strategy was used.

const (
	// SyncModeFineGrained indicates fine-grained API operations were used.
	SyncModeFineGrained SyncMode = "fine_grained"
	// SyncModeRawInitial indicates raw push was used for initial configuration (version=1).
	SyncModeRawInitial SyncMode = "raw_initial"
	// SyncModeRawThreshold indicates raw push was used because changes exceeded threshold.
	SyncModeRawThreshold SyncMode = "raw_threshold"
	// SyncModeRawFallback indicates raw push was used as fallback after fine-grained failure.
	SyncModeRawFallback SyncMode = "raw_fallback"
)

type SyncOptions

type SyncOptions struct {
	// MaxRetries for 409 version conflict errors (default: 3)
	// These are always retried as they're recoverable errors.
	MaxRetries int

	// Timeout for the entire sync operation (default: 2 minutes)
	Timeout time.Duration

	// ContinueOnError continues applying operations even if some fail (default: false)
	// When false, the first error stops execution.
	ContinueOnError bool

	// FallbackToRaw enables automatic fallback to raw config push on non-409 errors (default: true)
	// When enabled, if fine-grained sync fails with non-recoverable errors,
	// the library automatically falls back to pushing the complete raw configuration.
	FallbackToRaw bool

	// 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

	// MaxParallel limits concurrent Dataplane API operations during sync.
	// This prevents overwhelming the API when syncing large configurations.
	// 0 means unlimited (not recommended for large configs).
	MaxParallel int

	// RawPushThreshold triggers raw config push when change count exceeds this value.
	// When set to 0, this threshold check is disabled (fine-grained sync always used).
	// Note: Version 1 (initial state) always triggers raw push regardless of this setting.
	RawPushThreshold int

	// PreParsedConfig is an optional pre-parsed desired configuration.
	// If provided, sync skips parsing the desiredConfig string, saving CPU and allocations.
	// If nil, the config will be parsed normally (backward compatible).
	// This is typically set by the validation pipeline which already parsed the config.
	PreParsedConfig *parserconfig.StructuredConfig

	// CachedCurrentConfig is an optional cached parsed current configuration from a previous sync.
	// When set together with CachedConfigVersion, the orchestrator calls GetVersion() first
	// and skips the expensive GetRawConfiguration() + parse if the version matches.
	// On mismatch or error, falls through to the full fetch+parse path.
	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 set together with LastDeployedChecksum, the orchestrator skips the
	// expensive auxiliary file comparison (which downloads content from HAProxy via
	// Dataplane API) if both checksums match — the desired state hasn't changed
	// since the last successful sync.
	ContentChecksum string

	// LastDeployedChecksum is the content checksum from the last successful sync
	// to this endpoint. When it matches ContentChecksum, auxiliary file comparison
	// is skipped because the desired state is identical to what was last deployed.
	// Drift prevention syncs should leave this empty to force comparison.
	LastDeployedChecksum string
}

SyncOptions configures synchronization behavior.

func DefaultSyncOptions

func DefaultSyncOptions() *SyncOptions

DefaultSyncOptions returns sensible default sync options.

func DryRunOptions

func DryRunOptions() *SyncOptions

DryRunOptions returns options configured for dry-run mode.

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

	// Retries indicates how many times operations were retried (for 409 conflicts)
	Retries int

	// 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.
	PostSyncVersion int64
}

SyncResult contains detailed information about a sync operation.

func Sync

func Sync(ctx context.Context, endpoint *Endpoint, desiredConfig string, auxFiles *AuxiliaryFiles, opts *SyncOptions) (*SyncResult, error)

Sync synchronizes the desired HAProxy configuration to the dataplane endpoint.

This is a convenience function that creates a client internally for one-off operations. For production use with multiple operations, create a Client explicitly to reuse connections:

client, err := dataplane.NewClient(ctx, endpoint)
if err != nil {
    return err
}
defer client.Close()
result, err := client.Sync(ctx, desiredConfig, auxFiles, opts)

Parameters:

  • ctx: Context for cancellation and timeout
  • endpoint: Dataplane API connection information
  • 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

func (*SyncResult) String

func (r *SyncResult) String() string

String returns a human-readable summary of the sync result.

func (*SyncResult) UsedRawPush

func (r *SyncResult) UsedRawPush() bool

UsedRawPush returns true if raw configuration push was used instead of fine-grained sync. This is a convenience helper for code that needs to know whether any form of raw push was used.

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

type Version struct {
	Major int
	Minor int
	Full  string // Original version string for logging
}

Version represents HAProxy or DataPlane API version with major.minor components. Only major and minor are used for compatibility comparison.

func DetectLocalVersion

func DetectLocalVersion() (*Version, error)

DetectLocalVersion runs "haproxy -v" and returns the local HAProxy version. Returns an error if haproxy is not found or version cannot be parsed.

func ParseHAProxyVersionOutput

func ParseHAProxyVersionOutput(output string) (*Version, error)

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 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").

func (*Version) Compare

func (v *Version) Compare(other *Version) int

Compare compares two versions using major.minor semantics. Returns:

  • -1 if v < other
  • 0 if v == other
  • 1 if v > other

Only Major and Minor are compared; patch versions are ignored.

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.
enterprise
Package enterprise provides client operations for HAProxy Enterprise-only DataPlane API endpoints.
Package enterprise provides client operations for HAProxy Enterprise-only DataPlane API endpoints.
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.
sections/executors
Package executors provides pre-built executor functions for HAProxy configuration operations.
Package executors provides pre-built executor functions for HAProxy configuration operations.
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/directives
Package types provides parser implementations for HAProxy Enterprise Edition section-specific directives.
Package types provides parser implementations for HAProxy Enterprise Edition section-specific directives.
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.
Package synchronizer provides configuration synchronization between desired state and HAProxy via the Dataplane API.
Package synchronizer provides configuration synchronization between desired state and HAProxy via the Dataplane API.
Package validators provides zero-allocation OpenAPI validation for HAProxy models.
Package validators provides zero-allocation OpenAPI validation for HAProxy models.

Jump to

Keyboard shortcuts

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