validation

package
v0.2.0-alpha.1 Latest Latest
Warning

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

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

README

pkg/controller/validation

Three-phase HAProxy configuration validator with per-instance result caching.

Overview

ValidationService runs the rendered HAProxy config through:

  1. Syntaxclient-native parser via pkg/dataplane/parser.
  2. OpenAPI schema — version-specific Dataplane API schema check via pkg/generated.
  3. Semantic — actual haproxy -c invocation. Each call writes auxiliary files into its own per-call os.MkdirTemp and rewrites the rendered config's default-path origin to point at it, so callers do not contend on shared paths. The serialisation that does exist sits one layer down in pkg/dataplane.haproxyCheckMutex — it serialises the haproxy -c binary invocation itself because concurrent runs interfere with each other even with isolated temp directories.

The result of a successful validation is cached per-instance keyed by a content checksum of the config + auxiliary files. Identical content (the common case during drift-prevention cycles) skips all three phases and returns the cached *parser.StructuredConfig immediately. Failures are never cached — a failed validation always retries on the next call.

The service is consumed by pkg/controller/pipeline.Pipeline.Execute, which is in turn driven by both the leader-side reconciler (pkg/controller/reconciler.Coordinator) and the webhook-side proposal validator (pkg/controller/proposalvalidator). Per-instance caching keeps the webhook from evicting the main pipeline's cache.

Quick Start

import (
    "gitlab.com/haproxy-haptic/haptic/pkg/controller/validation"
    "gitlab.com/haproxy-haptic/haptic/pkg/dataplane"
)

svc := validation.NewValidationService(&validation.ValidationServiceConfig{
    Logger:            logger,
    Version:           &dataplane.Version{Major: 3, Minor: 2}, // schema selector; nil = v3.0
    SkipDNSValidation: false,                                  // true for runtime, false for webhook
    BaseDir:           "/etc/haproxy",                         // production default-path origin
    MapsDir:           "maps",                                 // relative names match RenderService
    SSLCertsDir:       "ssl",
    GeneralDir:        "general",
})

// One-shot: hashes content internally and returns *ValidationResult
result := svc.Validate(ctx, haproxyConfig, auxFiles)

// Pipeline-friendly: pass the checksum the renderer already computed,
// so we don't hash the same config twice per reconciliation.
result = svc.ValidateWithChecksum(ctx, haproxyConfig, auxFiles, checksum)
// result.Valid, result.Phase, result.Error, result.ParsedConfig (for downstream Sync)

SkipDNSValidation is the only field whose right value depends on the caller: the leader pipeline runs in permissive mode (true) so a temporarily-unresolvable backend hostname doesn't cause cascading reconciliation failures; the webhook runs in strict mode (false) so admission catches typos in service names before they reach production.

Caching Semantics

Call Behaviour
First call, or content checksum changes Run all three phases; cache ParsedConfig if successful
Repeat call with the same checksum Return cached ParsedConfig immediately, skip all phases
Failure (any phase) Return error, leave cache untouched (next call retries)

The cache lives on the *ValidationService instance. Constructing a new service (e.g. between iterations) clears it implicitly.

See Also

License

Apache-2.0 — see root LICENSE.

Documentation

Overview

Package validation provides pure validation services for HAProxy configuration.

Index

Constants

View Source
const (
	// DefaultBaseDir is the default base directory for HAProxy configuration.
	DefaultBaseDir = "/etc/haproxy"

	// DefaultMapsDir is the default relative directory name for map files.
	DefaultMapsDir = "maps"

	// DefaultSSLCertsDir is the default relative directory name for SSL certificates.
	DefaultSSLCertsDir = "ssl"

	// DefaultGeneralDir is the default relative directory name for general files.
	DefaultGeneralDir = "general"
)

Default directory paths for HAProxy validation.

View Source
const (
	// DefaultValidationTimeout is the default timeout for validation operations.
	// This is used by event handlers and webhook validators to prevent indefinite hangs.
	// 30 seconds allows sufficient time for render + validate while preventing stuck requests.
	DefaultValidationTimeout = 30 * time.Second
)

Timeout constants for validation operations.

Variables

This section is empty.

Functions

This section is empty.

Types

type ValidationResult

type ValidationResult struct {
	// Valid is true if the configuration passed all validation phases.
	Valid bool

	// Error contains the validation error if Valid is false.
	Error error

	// Phase indicates which validation phase failed (syntax, schema, semantic, render, setup).
	// Empty if validation succeeded.
	Phase string

	// DurationMs is the total validation duration in milliseconds.
	DurationMs int64

	// ParsedConfig is the pre-parsed configuration from syntax validation.
	// May be nil if validation failed or validation cache was used.
	// When non-nil, can be passed to downstream sync operations to avoid re-parsing.
	ParsedConfig *parser.StructuredConfig
}

ValidationResult contains the output of a validation operation.

func (*ValidationResult) ErrorMessage

func (r *ValidationResult) ErrorMessage() string

ErrorMessage returns a user-friendly error message. Returns empty string if validation succeeded.

type ValidationService

type ValidationService struct {
	// contains filtered or unexported fields
}

ValidationService is a pure service that validates HAProxy configuration.

This service encapsulates temp directory lifecycle internally: - Creates an isolated temp directory for each validation - Writes config and auxiliary files - Runs haproxy -c for semantic validation - Cleans up temp directory after validation

The service caches the last successful validation result keyed by a content checksum of the config and auxiliary files. When called with unchanged content, it returns the cached ParsedConfig immediately, skipping all validation phases. Only successful validations are cached; failures always trigger a full retry.

The service can be called concurrently from multiple goroutines.

func NewValidationService

func NewValidationService(cfg *ValidationServiceConfig) *ValidationService

NewValidationService creates a new ValidationService.

func (*ValidationService) ValidateWithChecksum

func (s *ValidationService) ValidateWithChecksum(ctx context.Context, config string, auxFiles *dataplane.AuxiliaryFiles, checksum string) *ValidationResult

ValidateWithChecksum validates HAProxy configuration using a pre-computed content checksum. This avoids redundant hashing when the caller (e.g., Pipeline) has already computed the checksum.

This method: 1. Parses and validates the ORIGINAL config (syntax + schema) - this produces the ParsedConfig 2. Creates an isolated temp directory 3. Replaces production baseDir with temp directory in config (for default-path origin) 4. Writes the config and auxiliary files 5. Runs semantic validation with haproxy -c using the MODIFIED config 6. Cleans up the temp directory 7. Returns the original ParsedConfig (with production paths)

The key insight is that syntax/schema validation doesn't need actual files or temp paths - it just parses the config string. Only semantic validation (haproxy -c) needs temp paths for file I/O. By parsing the original config first, we ensure the returned ParsedConfig contains production paths that downstream components can use.

Parameters:

  • ctx: Context for cancellation
  • config: The rendered HAProxy configuration content
  • auxFiles: Auxiliary files (maps, certificates, general files)
  • checksum: Pre-computed content checksum (see dataplane.ComputeContentChecksum)

Returns:

  • ValidationResult with success/failure status, timing, and ParsedConfig with production paths

type ValidationServiceConfig

type ValidationServiceConfig struct {
	// Logger is the structured logger for logging.
	Logger *slog.Logger

	// Version is the HAProxy version for schema selection (nil uses default v3.0).
	Version *dataplane.Version

	// SkipDNSValidation controls whether to skip DNS resolution failures during validation.
	// When true, servers with unresolvable hostnames start in DOWN state instead of failing.
	// When false (strict mode), DNS resolution failures cause validation to fail.
	SkipDNSValidation bool

	// SkipSemanticValidation controls whether to skip the `haproxy -c`
	// semantic-validation step. Set true for the deployment pipeline
	// (~60 ms per render saved, syntax + schema still runs), false for
	// the webhook (admission requests must still be strictly validated).
	SkipSemanticValidation bool

	// BaseDir is the production base directory used in "default-path origin" directive.
	// During local validation, this is replaced with the temp directory path so that
	// HAProxy resolves relative paths from the temp directory instead of production paths.
	// Example: "/etc/haproxy"
	BaseDir string

	// MapsDir is the relative directory name for map files (e.g., "maps").
	// Should match the basename of the dataplane MapsDir config.
	MapsDir string

	// SSLCertsDir is the relative directory name for SSL certificates (e.g., "ssl").
	// Should match the basename of the dataplane SSLCertsDir config.
	SSLCertsDir string

	// GeneralDir is the relative directory name for general files (e.g., "general").
	// Should match the basename of the dataplane GeneralStorageDir config.
	GeneralDir string
}

ValidationServiceConfig contains configuration for creating a ValidationService.

Jump to

Keyboard shortcuts

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