validator

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: 16 Imported by: 0

README

pkg/controller/validator

Configuration validators (scatter-gather participants).

Overview

Three validators run as the responder side of the scatter-gather validation pattern. Each one wraps a shared BaseValidator, subscribes to ConfigValidationRequest on the EventBus, and responds with a ConfigValidationResponse flagged valid or invalid. 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.

Quick Start

import "gitlab.com/haproxy-haptic/haptic/pkg/controller/validator"

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

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

The validators take only (eventBus, logger) — no engine, no validator-name list. Their internal name ("basic", "template", "jsonpath") is what the orchestrator uses in its ExpectedResponders list.

Events

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

ConfigValidatedEvent and ConfigInvalidEvent are published by the orchestrator (configchange.ConfigChangeHandler) after collecting all three 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.

Types

type BaseValidator

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

BaseValidator wraps component.Base with validator-specific dispatch: it forwards only ConfigValidationRequest events to the handler, and on panic it publishes a failure ConfigValidationResponse so the scatter-gather coordinator does not time out.

func NewBaseValidator

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

NewBaseValidator creates a new base validator with the given configuration.

Parameters:

  • eventBus: The EventBus to subscribe to and publish on
  • logger: Structured logger for diagnostics
  • name: Validator name (for error messages and responses)
  • handler: ValidationHandler implementation for validator-specific logic

Returns:

  • *BaseValidator ready to start

func (*BaseValidator) HandleEvent

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

HandleEvent implements component.EventHandler. We subscribed with a type filter so only ConfigValidationRequest events arrive, but the type assertion keeps things defensive in case the filter is widened later.

func (*BaseValidator) HandlePanic

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

HandlePanic implements component.PanicHandler. Publishing a failure response on panic keeps the scatter-gather coordinator from waiting on a validator that has unwound. The outer recover in component.Base is still responsible for keeping the event loop alive.

type BasicValidator

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

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.

Parameters:

  • eventBus: The EventBus to subscribe to and publish on
  • logger: Structured logger for diagnostics

Returns:

  • *BasicValidator ready to start

func (*BasicValidator) HandleRequest

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

HandleRequest processes a ConfigValidationRequest by validating basic structure. This implements the ValidationHandler interface.

type JSONPathValidator

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

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.

Parameters:

  • eventBus: The EventBus to subscribe to and publish on
  • logger: Structured logger for diagnostics

Returns:

  • *JSONPathValidator ready to start

func (*JSONPathValidator) HandleRequest

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

HandleRequest processes a ConfigValidationRequest by validating all JSONPath expressions. This implements the ValidationHandler interface.

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) HandleRequest

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

HandleRequest processes a ConfigValidationRequest by validating all templates. This implements the ValidationHandler interface.

Templates are validated together as a complete set, matching production behavior. This ensures snippets that reference each other via render/import work correctly.

Schema acquisition for the request's watched resources happens synchronously here so the engine compile sees the same typed globals the Stage-5 production engine will see. A failure to resolve every declared resource's schema fails validation — template authors using typed access need the guarantee that every declared watched resource has its real schema (RBAC, CRD installation, apiserver health are all surfaced via this gate).

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 {
	// HandleRequest processes a ConfigValidationRequest and publishes a response.
	// The implementation should validate the config and publish a ConfigValidationResponse
	// event to the bus.
	HandleRequest(req *events.ConfigValidationRequest)
}

ValidationHandler defines the interface for validator-specific validation logic.

Each validator (basic, template, jsonpath) implements this interface to provide their specific validation logic while reusing the common event loop infrastructure.

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 is guarded separately by the same suite: controller.runIteration calls RunValidationTestsSync on the initial config and crash-loops the pod if it fails (see that function and the startup gate in iteration.go). Both gates share RunValidationTestsSync so a config's tests behave identically whether they run on a live change or at load — the controller never serves a config whose own tests fail.

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) HandleRequest

HandleRequest runs the embedded validationTests for the candidate config and publishes a ConfigValidationResponse. A config with no validationTests is a no-op pass (the gate adds zero cost when the chart ships no tests).

func (*ValidationTestsValidator) Start

Start captures the lifecycle context (so an in-flight bootstrap/run is cancelled on shutdown) and then runs the embedded validator event loop.

Jump to

Keyboard shortcuts

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