validator

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

README

pkg/controller/validator

Configuration validators (scatter-gather participants).

Overview

Four validators run as the responder side of the scatter-gather validation pattern. Each one implements ValidationHandler.Validate, which receives a typed config and returns a verdict and errors. The shared BaseValidator owns the subscription, input checks, and response publishing, including rejection after a panic. The orchestrator that fans the request out and aggregates responses is not in this package — it lives in pkg/controller/configchange.ConfigChangeHandler. Rendered-HAProxy-config validation (syntax + OpenAPI schema + haproxy -c) runs synchronously inside pkg/controller/pipeline.Pipeline via pkg/dataplane.ValidateConfiguration, not through this package.

Validators

  • BasicValidator — Structural validation (required fields, type checks, basic schema sanity).
  • TemplateValidator — Calls helpers.ExtractTemplatesFromConfig to collect every template defined under haproxyConfig, templateSnippets, maps, files, and sslCertificates, then compiles them with templating.NewScriggoWithDeclarations to surface syntax errors before they reach the render pipeline.
  • JSONPathValidator — Evaluates the indexBy JSONPath expressions on every entry under spec.watchedResources against a synthetic resource.
  • ValidationTestsValidator — Runs the config's entire embedded validationTests suite (render + assertions) through pkg/controller/configtest, which drives pkg/controller/testrunner. By far the slowest responder, which is why its run budget scales with suite size and the orchestrator's scatter-gather envelope is derived from the same formula.

Quick Start

import "github.com/haproxy-haptic/HAPTIC/pkg/controller/validator"

basic := validator.NewBasicValidator(bus, logger)
tmpl := validator.NewTemplateValidator(bus, logger, bootstrap)
jp := validator.NewJSONPathValidator(bus, logger)

go basic.Start(ctx)
go tmpl.Start(ctx)
go jp.Start(ctx)

BasicValidator and JSONPathValidator take (eventBus, logger); TemplateValidator and ValidationTestsValidator additionally take a TypeBootstrapper, so they can build the typed watched-resource declarations before compiling. None takes an engine or a validator-name list. Each validator's internal name ("basic", "template", "jsonpath", "validationtests") is what the orchestrator uses in its ExpectedResponders list.

Events

Subscribed
  • ConfigValidationRequest — scatter-gather validation request (handled by all four validators)
Published
  • ConfigValidationResponse — one per validator, per request

A response with Inconclusive set reports that the validator couldn't judge the config (schemas transiently unavailable); it is never Valid.

ConfigValidatedEvent, ConfigInvalidEvent, and ConfigValidationInconclusiveEvent are published by the orchestrator (configchange.ConfigChangeHandler) after collecting all four responses, not by these validators directly.

License

Apache-2.0 — see root LICENSE.

Documentation

Index

Constants

View Source
const (
	// ValidatorNameBasic is the name for the basic structure validator.
	ValidatorNameBasic = "basic"

	// ValidatorNameTemplate is the name for the template syntax validator.
	ValidatorNameTemplate = "template"

	// ValidatorNameJSONPath is the name for the JSONPath expression validator.
	ValidatorNameJSONPath = "jsonpath"

	// ValidatorNameValidationTests is the name for the validator that runs the
	// config's embedded validationTests (the same suite the `controller validate`
	// CLI runs) before a config is accepted, so the daemon never loads a config
	// whose tests fail.
	ValidatorNameValidationTests = "validationtests"
)

Validator names used in the scatter-gather validation pattern.

These constants ensure consistency between: - Validator responder names (in ConfigValidationResponse) - Expected responders list (in ConfigChangeHandler)

Using constants prevents typos and silent failures where the scatter-gather pattern would timeout waiting for a validator that uses a different name.

EventBufferSize is the size of the event subscription buffer. Low-volume component (~1 validation request per reconciliation).

Variables

This section is empty.

Functions

func AllValidatorNames

func AllValidatorNames() []string

AllValidatorNames returns a slice of all validator names. Use this when registering validators in the ConfigChangeHandler.

func RunValidationTestsSync

func RunValidationTestsSync(ctx context.Context, cfg *coreconfig.Config, bootstrap TypeBootstrapper, runTimeout time.Duration, logger *slog.Logger) (configtest.Result, error)

RunValidationTestsSync resolves typed schemas, builds a throwaway engine, and runs the config's embedded validationTests via the shared configtest helper (the same one the admission webhook uses, so the gates can't drift). It is the shared core behind both the live scatter-gather gate (the validator's HandleRequest) and the startup load gate (controller.runIteration) — so a config's tests are bootstrapped, compiled, and executed identically whether they run on a live change or at controller load.

A config with no validationTests is a zero-cost pass. A runTimeout <= 0 uses the default suite budget (validationTestsRunTimeout). Any setup error is returned so the caller can fail validation with a clear reason rather than silently skipping the gate.

func SuiteRunBudget

func SuiteRunBudget(testCount int) time.Duration

SuiteRunBudget returns the live-gate test-execution budget for a suite of the given size: the validationTestsRunTimeout floor PLUS suitePerTestBudget per test. A fixed 25s cap rejected the chart's all-passing 362-test suite on contended CI nodes (issue #77: 362/362 passed in 27.8s, cancelled at 25s, config rejected as "partially-validated"). Scaling with suite size keeps small suites on a tight bound while a legitimately large suite gets time proportional to its work. Exported so configchange can derive its scatter-gather envelope from the SAME formula — the envelope must stay strictly larger than this budget (see SuiteValidationEnvelope).

The floor is ADDED, not max()'d. Taking the larger of the two pinned every suite below 250 tests to exactly 25s, so the floor — chosen as headroom for a *small* suite on a cold node — silently became the whole budget for a suite doing ten times the work. That reintroduced #77's false rejection at the crossover: the chart's ~249-test effective suite (post-`requires` stripping on a cluster without Gateway CRDs) needed 27.3s against a budget that computed to 24.9s and therefore clamped to the 25s floor, and a perfectly good default config was rejected as partially-validated. Adding the floor is also what the doc comment on suitePerTestBudget and the shipped changelog ("25s floor + ~100ms per test") always described.

func SuiteValidationEnvelope

func SuiteValidationEnvelope(testCount int) time.Duration

SuiteValidationEnvelope returns the scatter-gather timeout for validating a config with the given suite size. Every component that grows with the suite (run budget, engine compile) scales, so the envelope is strictly larger than the validator's worst-case wall time for every testCount and the validationtests validator always self-reports (pass, or "did not complete") before the coordinator declares it a missing responder.

func ValidateIncrementalActivationPaths

func ValidateIncrementalActivationPaths(cfg *coreconfig.Config) []string

ValidateIncrementalActivationPaths validates component activation paths.

Types

type BaseValidator

type BaseValidator struct {
	*component.Base
	// contains filtered or unexported fields
}

BaseValidator owns request decoding and response publishing for a ValidationHandler. A panic produces a rejection so the scatter-gather coordinator can finish.

func NewBaseValidator

func NewBaseValidator(
	eventBus *busevents.EventBus,
	logger *slog.Logger,
	name string,
	handler ValidationHandler,
) *BaseValidator

NewBaseValidator subscribes the handler to config validation requests.

func (*BaseValidator) HandleEvent

func (v *BaseValidator) HandleEvent(event busevents.Event)

HandleEvent implements component.EventHandler.

func (*BaseValidator) HandlePanic

func (v *BaseValidator) HandlePanic(recovered any, event busevents.Event)

HandlePanic rejects the request so scatter-gather does not wait for a lost response.

func (*BaseValidator) HandleRequest

func (v *BaseValidator) HandleRequest(req *events.ConfigValidationRequest)

HandleRequest owns config decoding and the single response for each validation.

type BasicValidator

type BasicValidator struct {
	*BaseValidator
}

BasicValidator validates basic structural configuration requirements.

This component subscribes to ConfigValidationRequest events and validates basic structural requirements such as: - Required fields are present - Field types and values are correct - Port numbers are in valid ranges - Non-empty slices where required

This validator uses the existing config.ValidateStructure() function and does NOT validate template syntax or JSONPath expressions (handled by specialized validators).

This component is part of the scatter-gather validation pattern and publishes ConfigValidationResponse events with validation results.

func NewBasicValidator

func NewBasicValidator(eventBus *busevents.EventBus, logger *slog.Logger) *BasicValidator

NewBasicValidator creates a new basic validator component.

func (*BasicValidator) Validate

func (v *BasicValidator) Validate(_ context.Context, cfg *coreconfig.Config, version string) Verdict

Validate implements ValidationHandler.

type JSONPathValidator

type JSONPathValidator struct {
	*BaseValidator
}

JSONPathValidator validates JSONPath expressions in configuration.

This component subscribes to ConfigValidationRequest events and validates all JSONPath expressions in the configuration using the k8s indexer package.

Validated fields: - WatchedResourcesIgnoreFields (all expressions) - WatchedResources[*].IndexBy (all expressions) - WatchedResources[*].FieldSelector (field.path=value format)

This component is part of the scatter-gather validation pattern and publishes ConfigValidationResponse events with validation results.

func NewJSONPathValidator

func NewJSONPathValidator(eventBus *busevents.EventBus, logger *slog.Logger) *JSONPathValidator

NewJSONPathValidator creates a new JSONPath validator component.

func (*JSONPathValidator) Validate

func (v *JSONPathValidator) Validate(_ context.Context, cfg *coreconfig.Config, version string) Verdict

Validate implements ValidationHandler.

type SchemaAcquisitionError

type SchemaAcquisitionError struct {
	// Subject names what could not be validated without the schemas.
	Subject string
	Err     error
}

SchemaAcquisitionError reports that watched-resource schemas could not be acquired, so typed template access cannot be checked.

func (*SchemaAcquisitionError) Error

func (e *SchemaAcquisitionError) Error() string

func (*SchemaAcquisitionError) Transient

func (e *SchemaAcquisitionError) Transient() bool

Transient reports whether the failure says nothing about the config: the apiserver was slow, overloaded, or unreachable.

func (*SchemaAcquisitionError) Unwrap

func (e *SchemaAcquisitionError) Unwrap() error

type SyncFailures

type SyncFailures map[string][]string

SyncFailures contains the complete config-validator verdict by validator.

func ValidateConfigSync

func ValidateConfigSync(
	ctx context.Context,
	cfg *coreconfig.Config,
	bootstrap TypeBootstrapper,
	runTimeout time.Duration,
	logger *slog.Logger,
) SyncFailures

ValidateConfigSync applies the same four validators as the live scatter-gather before an iteration adopts a re-resolved config.

func (SyncFailures) Error

func (f SyncFailures) Error() string

Error returns a deterministic summary suitable for the startup error.

func (SyncFailures) Flat

func (f SyncFailures) Flat() []string

Flat returns the deterministic messages written to config status.

type TemplateValidator

type TemplateValidator struct {
	*BaseValidator
	// contains filtered or unexported fields
}

TemplateValidator validates template syntax in configuration.

This component subscribes to ConfigValidationRequest events and validates all templates together as a complete set. It uses helpers.ExtractTemplatesFromConfig to ensure validation matches production behavior exactly (DRY principle).

Templates are validated together, not in isolation, so snippets that reference each other via render, import, or inherit_context work correctly.

This component is part of the scatter-gather validation pattern and publishes ConfigValidationResponse events with validation results.

func NewTemplateValidator

func NewTemplateValidator(eventBus *busevents.EventBus, logger *slog.Logger, bootstrap TypeBootstrapper) *TemplateValidator

NewTemplateValidator creates a new template validator component.

Parameters:

  • eventBus: the EventBus to subscribe to and publish on
  • logger: structured logger for diagnostics
  • bootstrap: resolver for typed reflect.Types per watched resource. MUST be non-nil — without real types the engine compile would degrade to envelope-only declarations and false-positively reject any chart that uses typed Spec / Status access. Tests pass a stub returning an in-memory Result; production passes a closure around controller.runTypeBootstrap.

func (*TemplateValidator) Validate

func (v *TemplateValidator) Validate(ctx context.Context, cfg *coreconfig.Config, version string) Verdict

Validate implements ValidationHandler.

type TypeBootstrapper

type TypeBootstrapper func(ctx context.Context, cfg *coreconfig.Config) (*typebootstrap.Result, error)

TypeBootstrapper runs the schema-acquisition pipeline for a candidate config and returns the resolved typed reflect.Types for each watched resource. Used by TemplateValidator so its engine compile sees the same typed globals the Stage-5 production engine will see — without this, a chart that uses typed Spec/Status access (e.g. `gw.Spec.Listeners`) would be false-positively rejected at Stage 1 against an envelope-only declaration set.

Production wiring binds this to controller.runTypeBootstrap with the iteration's K8s client captured in the closure; tests pass a stub that returns a Result built from in-memory schemas.

type ValidationHandler

type ValidationHandler interface {
	Validate(ctx context.Context, cfg *coreconfig.Config, version string) Verdict
}

ValidationHandler validates a typed config without owning event responses.

type ValidationTestsValidator

type ValidationTestsValidator struct {
	*BaseValidator
	// contains filtered or unexported fields
}

ValidationTestsValidator runs the config's embedded validationTests — the exact suite the `controller validate` CLI runs — as a scatter-gather validator. It guards live config changes: when a CRD update's tests fail the aggregation publishes ConfigInvalidEvent and the last-good config keeps serving.

The load path invokes the complete synchronous validator contract, which shares RunValidationTestsSync with this adapter.

It builds a throwaway engine from the candidate config (using the same live-schema TypeBootstrapper the TemplateValidator uses, so typed access in tests compiles identically to production) plus a temporary HAProxy validation tree for `haproxy_valid` assertions, runs the suite, and reports the outcome. This runs only on config load/change — never on the resource reconciliation hot path.

func NewValidationTestsValidator

func NewValidationTestsValidator(eventBus *busevents.EventBus, logger *slog.Logger, bootstrap TypeBootstrapper) *ValidationTestsValidator

NewValidationTestsValidator creates the validationTests validator.

bootstrap MUST be non-nil — without real typed reflect.Types the engine compile (and therefore any test exercising typed Spec/Status access) would false-positively fail. Production passes a closure around controller.runTypeBootstrap; tests pass a stub returning an in-memory Result.

func (*ValidationTestsValidator) Validate

func (v *ValidationTestsValidator) Validate(ctx context.Context, cfg *coreconfig.Config, version string) Verdict

Validate implements ValidationHandler.

type Verdict

type Verdict struct {
	Valid  bool
	Errors []string
	// Inconclusive marks Errors as reasons the handler could not judge the
	// config, not defects in it.
	Inconclusive bool
}

Verdict is a ValidationHandler's judgement of one config.

Jump to

Keyboard shortcuts

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