proposalvalidator

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

README

pkg/controller/proposalvalidator

Speculative render+validate of a hypothetical configuration change without deploying it. Composes a BaseStoreProvider (the live watched-resource stores) with caller-supplied overlays, drives pkg/controller/pipeline.Pipeline against the merged view, and reports the outcome.

Overview

Two production paths need this:

Caller Mode What's overlaid
pkg/controller/dryrunvalidator (admission webhook) Sync — direct ValidateSync call One *stores.StoreOverlay per affected resource type, built from the admission verb (CREATE / UPDATE / DELETE)
pkg/controller/httpstore (background HTTP content refresh) Async — ProposalValidationRequestedEvent An HTTPContentOverlay containing newly fetched pending content; no K8s overlays

Both flows go through the same Pipeline.Execute, so anything that passes here will also pass leader-side reconciliation. Reconciliation itself does not use this component — the leader-only Coordinator calls Pipeline.Execute directly without going through proposalvalidator.

Quick Start

import (
    "gitlab.com/haproxy-haptic/haptic/pkg/controller/proposalvalidator"
    "gitlab.com/haproxy-haptic/haptic/pkg/controller/pipeline"
)

pl := pipeline.New(/* renderer + validator services + paths */)

// Sync mode (no event subscription; caller invokes ValidateSync directly).
sync := proposalvalidator.New(&proposalvalidator.ComponentConfig{
    Pipeline:          pl,
    BaseStoreProvider: storeProvider,
    Logger:            logger,
    SyncOnly:          true,
})

overlays := map[string]*stores.StoreOverlay{
    "ingresses": stores.NewStoreOverlayForCreate(newIngressObj),
}
pipelineResult, result := sync.ValidateSync(ctx, overlays) // (*pipeline.PipelineResult, *validation.ValidationResult)

// Async mode (subscribes to ProposalValidationRequestedEvent during construction).
async := proposalvalidator.New(&proposalvalidator.ComponentConfig{
    EventBus:          eventBus,
    Pipeline:          pl,
    BaseStoreProvider: storeProvider,
    Logger:            logger,
})
go async.Start(ctx)

ValidateSync returns (*pipeline.PipelineResult, *validation.ValidationResult). The PipelineResult carries the rendered HAProxy config and auxiliary files (populated only on success); the *validation.ValidationResult carries the validation outcome:

type ValidationResult struct {
    Valid        bool
    Error        error
    Phase        string                   // "setup" / "render" / "syntax" / "schema" / "semantic" — empty when Valid; "setup" is emitted by this component when the overlay set itself is invalid (before the pipeline runs)
    DurationMs   int64
    ParsedConfig *parser.StructuredConfig // pre-parsed; downstream sync can skip the parse
}

The async path publishes ProposalValidationCompletedEvent (or its failure variant) keyed by the request ID so multiple in-flight proposals don't get correlated incorrectly.

How It Works

  1. The caller hands over overlays map[string]*stores.StoreOverlay (one per resource type they want to perturb), and optionally an HTTP overlay for pending HTTP content.
  2. The component wraps BaseStoreProvider in an OverlayStoreProvider so the pipeline sees the merged view: live store + overlay on top.
  3. Pipeline.Execute(ctx, mergedProvider) runs the full render + three-phase validation against that view.
  4. Failures come back as *PipelineError (with Phase + Cause); the simplification helpers in pkg/dataplane (SimplifyRenderingError / SimplifyValidationError) turn the underlying library error into something a webhook user can act on.

Because the merged view exists only for the duration of the call, a successful proposal validation never mutates live store state.

See Also

License

Apache-2.0 — see root LICENSE.

Documentation

Overview

Package proposalvalidator provides validation of hypothetical configuration changes.

Index

Constants

View Source
const (
	// ComponentName is the unique identifier for this component.
	ComponentName = "proposalvalidator"

	// EventBufferSize is the buffer size for event channel.
	EventBufferSize = busevents.StandardSubscriberBuffer
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Component

type Component struct {
	// Base is the embedded event-loop scaffold for async mode. It is nil in
	// sync-only mode (SyncOnly=true), where no subscription occurs and
	// Start() must not be called.
	*component.Base
	// contains filtered or unexported fields
}

Component validates hypothetical configuration changes without deploying.

It supports two modes:

  1. Async (event-driven): Subscribes to ProposalValidationRequestedEvent and publishes ProposalValidationCompletedEvent.
  2. Sync (direct call): ValidateSync() for synchronous callers like webhooks.

The component uses RenderValidatePipeline with CompositeStoreProvider to render and validate configurations with proposed changes overlaid on actual stores.

func New

func New(cfg *ComponentConfig) *Component

New creates a new ProposalValidator component.

For async mode (SyncOnly=false): The component subscribes to events during construction (before EventBus.Start()) to ensure proper startup synchronization. Call Start() to begin processing events.

For sync-only mode (SyncOnly=true): No event subscription occurs. Only ValidateSync() can be used. Do not call Start().

func (*Component) HandleEvent

func (c *Component) HandleEvent(event busevents.Event)

HandleEvent implements component.EventHandler: it processes incoming events.

func (*Component) Name

func (c *Component) Name() string

Name returns the unique identifier for this component. Implements the lifecycle.Component interface. Kept as an override (rather than relying on the promoted Base.Name) because sync-only components have no Base.

func (*Component) Start

func (c *Component) Start(ctx context.Context) error

Start runs the embedded component.Base event loop until the context is cancelled. It listens for ProposalValidationRequestedEvent and processes validation requests. Must not be called in sync-only mode (Base is nil).

func (*Component) ValidateSync

ValidateSync performs synchronous validation of proposed changes.

This is the preferred method for webhook admission where the caller needs an immediate response. Unlike event-driven validation, this blocks until validation completes.

On a proposed-config failure (render or validate phase), this method also runs a baseline check — the same render+validate pipeline against the live stores *without* the overlay. If the baseline already fails, the new resource isn't the cause of the failure and admission is allowed (with a warning log). This prevents a real production reliability issue: a single broken existing resource (e.g., an Ingress referencing a Secret the user deleted) would otherwise block admission of every unrelated resource until the broken one is fixed. The baseline check reuses the validation cache, so in steady state (baseline healthy) the extra cost only kicks in on failure.

Parameters:

  • ctx: Context for cancellation
  • overlays: Map of store name to proposed changes

Returns:

  • PipelineResult with the rendered HAProxy config + auxiliary files, populated only when ValidationResult.Valid is true. Callers wiring pluggable validators after the standard render+validate phases need these to feed Manager.ValidateAll. Nil on any failure.
  • ValidationResult with valid/invalid status and error details.

type ComponentConfig

type ComponentConfig struct {
	// EventBus is the event bus for async validation requests.
	// Required for async mode, optional for sync-only mode.
	EventBus *busevents.EventBus

	// Pipeline is the render-validate pipeline.
	Pipeline *pipeline.Pipeline

	// BaseStoreProvider is the provider for actual (non-overlaid) stores.
	BaseStoreProvider stores.StoreProvider

	// Logger is the structured logger for logging.
	Logger *slog.Logger

	// SyncOnly, when true, creates a validator that only supports ValidateSync().
	// It does not subscribe to EventBus events and Start() should not be called.
	// Use this mode when only synchronous validation is needed (e.g., webhook).
	SyncOnly bool
}

ComponentConfig contains configuration for creating a ProposalValidator.

Jump to

Keyboard shortcuts

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