renderer

package
v0.7.0 Latest Latest
Warning

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

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

README

pkg/controller/renderer

Pure render service that turns the controller's templates + Kubernetes state into a complete HAProxy configuration plus auxiliary files. Wraps pkg/templating.Engine with the controller-side concerns (path resolution, template context building, status-patch collection).

Overview

RenderService (service.go) is synchronous. The reconciliation coordinator, follower warmer, and admission/proposal pipeline call it directly; only the leader deploys the resulting plan. The coordinator publishes TemplateRenderedEvent.

The renderer is a library, not an event-driven component. See docs/adr/0001-renderer-is-synchronous-not-event-adapter.md for the rationale.

Quick Start (RenderService)

import (
    "github.com/haproxy-haptic/HAPTIC/pkg/controller/rendercontext"
    "github.com/haproxy-haptic/HAPTIC/pkg/controller/renderer"
)

svc := renderer.NewRenderService(&renderer.RenderServiceConfig{
    Engine:             templateEngine,        // pre-compiled templating.Engine
    Config:             cfg,                   // built from the HAProxyTemplateConfig CRD
    Logger:             logger,
    Capabilities:       capabilities,          // from local HAProxy probe
    HAProxyPodStore:    haproxyPodStore,       // optional; needed for {{ controller.haproxy_pods }}
    HTTPStoreComponent: httpStoreComponent,    // optional; needed for {{ http.Fetch(...) }}
})

result, err := svc.Render(ctx, storeProvider, rendercontext.RenderModeReconcile) // *RenderResult, error

RenderResult binds the configuration, deployment plan, auxiliary files, status patches, Events, and owned resources in immutable snapshots. Production callers use CycleSnapshot and the output-specific snapshots. The mutable compatibility fields (AuxiliaryFiles, StatusPatches, Events, and RenderedResources) stay nil; use the Materialize* methods when a detached copy is required.

PlanID identifies the desired plan. DurationMs, AuxFileCount, and optional IncludeStats report render cost.

Path resolution uses relative paths derived from cfg.Dataplane.{MapsDir,SSLCertsDir,GeneralStorageDir}. The rendered config relies on HAProxy's default-path origin <baseDir> directive, so the same render output works in:

  • Local validation, where the validation service swaps baseDir for a temp directory.
  • Production deployment, where baseDir is the agent's configuration directory.

Template Context

buildRenderingContext prepares the resource stores, acknowledged configuration, and HTTP input view, then delegates to rendercontext.NewBuilder. Watched resources expose List, Fetch, and GetSingle; schema-backed resources support typed field access. Store reads and typed projections are cached for reuse.

A Kubernetes read failure, typed-resource conversion failure, or ambiguous .GetSingle() result aborts the render. Only a stale on-demand reference whose live API object is already gone is treated as ordinary absence.

See Also

License

Apache-2.0 — see root LICENSE.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RegisterK8sResourceDocs

func RegisterK8sResourceDocs(
	templateName, rendered string,
	collector *templating.RenderedResourceCollector,
	createOnlyFields []string,
) error

RegisterK8sResourceDocs parses rendered YAML (one or more documents separated by `---`), validates each, and adds it to the collector.

Types

type CapabilitiesFanout

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

CapabilitiesFanout keeps every RenderService that feeds a gate on one capability set. Admission and production must render the same config for the same objects: a webhook still branching on the controller image's own HAProxy admits a config the leader then renders differently, and nothing checked that one. Services register at different points in startup, so one that registers later is seeded with what the fleet last reported.

func NewCapabilitiesFanout

func NewCapabilitiesFanout(seed dataplane.Capabilities) *CapabilitiesFanout

NewCapabilitiesFanout starts from the controller image's own HAProxy, which is what every render uses until the fleet has reported.

func (*CapabilitiesFanout) Add

func (f *CapabilitiesFanout) Add(service *RenderService)

Add registers a service and hands it what the fleet last reported.

func (*CapabilitiesFanout) Capabilities

func (f *CapabilitiesFanout) Capabilities() dataplane.Capabilities

Capabilities is what the fleet last reported.

func (*CapabilitiesFanout) SetCapabilities

func (f *CapabilitiesFanout) SetCapabilities(capabilities dataplane.Capabilities)

SetCapabilities re-sources every registered service from the fleet's lowest reported HAProxy version.

type ColdIncrementalRender

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

ColdIncrementalRender runs components for offline tools without retaining a cache.

func NewColdIncrementalRender

func NewColdIncrementalRender(
	ctx context.Context,
	cfg *ColdIncrementalRenderConfig,
) (*ColdIncrementalRender, error)

NewColdIncrementalRender prepares one offline render over an existing static context.

func (*ColdIncrementalRender) Context

Context attaches the cold transaction to a render context.

func (*ColdIncrementalRender) ValidateIncrementalCalls

func (r *ColdIncrementalRender) ValidateIncrementalCalls() error

ValidateIncrementalCalls verifies complete canonical group placement.

type ColdIncrementalRenderAnalysis

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

ColdIncrementalRenderAnalysis is the part of a cold render that depends on the config and engine alone, shared by every cold render of the pair.

func AnalyzeColdIncrementalRenders

func AnalyzeColdIncrementalRenders(cfg *config.Config, engine templating.Engine) *ColdIncrementalRenderAnalysis

AnalyzeColdIncrementalRenders prepares what cold renders of cfg and engine share. The result is read-only and safe for concurrent renders.

type ColdIncrementalRenderConfig

type ColdIncrementalRenderConfig struct {
	Config             *config.Config
	Engine             templating.Engine
	StoreProvider      stores.StoreProvider
	Mode               rendercontext.RenderMode
	TemplateContext    map[string]any
	ResourceErrors     *rendercontext.ResourceErrorCollector
	Logger             *slog.Logger
	TypedResourceTypes map[string]reflect.Type
	// Analysis, when set, must come from AnalyzeColdIncrementalRenders over
	// this Config and Engine; nil analyzes them for this render alone.
	Analysis *ColdIncrementalRenderAnalysis
}

ColdIncrementalRenderConfig supplies one non-caching render transaction.

type IncrementalCacheBuildIdentity

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

IncrementalCacheBuildIdentity authenticates one cold-cache build and its output reservation.

func (IncrementalCacheBuildIdentity) Generation

func (i IncrementalCacheBuildIdentity) Generation() (uint64, error)

Generation returns the authenticated output generation.

func (IncrementalCacheBuildIdentity) Same

Same reports whether both handles authenticate the same exact build.

func (IncrementalCacheBuildIdentity) ValidateAuthentication

func (i IncrementalCacheBuildIdentity) ValidateAuthentication() error

ValidateAuthentication verifies that the identity belongs to its exact renderer state.

type IncrementalCacheBuildObserver

type IncrementalCacheBuildObserver interface {
	IncrementalCacheBuildStarted(context.Context, IncrementalCacheBuildIdentity)
	IncrementalCacheBuildCompleted(IncrementalCacheBuildIdentity, error)
}

IncrementalCacheBuildObserver observes optional cold-cache construction outside render latency.

type RenderInputTransaction

type RenderInputTransaction interface {
	// HasCandidates reports whether this render is the first to accept some
	// external content, which is what the commit gate keys on.
	HasCandidates() bool
	Commit(context.Context) error
	Abort()
}

RenderInputTransaction finalizes external inputs used by one render.

type RenderResult

type RenderResult struct {
	// HTTPObservations records fresh accepted reads independently of transaction wrappers.
	HTTPObservations []purehttpstore.ObservationToken

	// CycleSnapshot binds the output and every effect from this render.
	CycleSnapshot *rendercycle.Snapshot

	// OutputSnapshot binds the exact config, plan, and auxiliary artifacts.
	OutputSnapshot *renderoutput.Snapshot

	// HAProxyConfig is the rendered HAProxy configuration.
	HAProxyConfig string

	// AuxiliaryFiles contains all rendered auxiliary files (maps, certs, general).
	// Production renders leave it nil and use AuxiliaryFileSnapshot.
	AuxiliaryFiles *dataplane.AuxiliaryFiles

	// AuxiliaryFileSnapshot is the authenticated immutable production representation.
	AuxiliaryFileSnapshot *renderartifact.Snapshot

	// ContentChecksum covers HAProxyConfig and every auxiliary-file identifier and byte.
	ContentChecksum string

	// Plan is the structure the templates declared about this render: the
	// ordered sections of the config, the backend records behind them, the map
	// entries and the file set.
	Plan *renderplan.Plan

	// PlanID identifies the plan — the digest downstream consumers compare on.
	PlanID string

	// StatusPatches is the detached compatibility representation.
	// Production renders leave it nil and use StatusPatchSnapshot.
	StatusPatches []templating.StatusPatch

	// StatusPatchSnapshot carries the same patches without materializing payload maps.
	StatusPatchSnapshot *templating.StatusPatchSnapshot

	// Events contains Kubernetes Events templates asked to emit via recordEvent()
	// (e.g. a RouteConflict Warning on an Ingress whose route lost to an older
	// one). Resource-agnostic — each carries its own apiVersion/kind/namespace/name.
	// Production renders leave it nil and use EventSnapshot.
	Events []templating.RenderedEvent

	// EventSnapshot is the authenticated immutable production representation.
	EventSnapshot *templating.RenderedEventSnapshot

	// RenderedResources contains full Kubernetes resources the templates declared
	// the controller should own and reconcile (e.g. an auxiliary Service or other
	// object a template emits alongside the HAProxy config). The applier compares
	// each against the last-applied checksum and skips unchanged entries to avoid
	// hammering the API server.
	// Production renders leave it nil and use RenderedResourceSnapshot.
	RenderedResources []templating.RenderedResource

	// RenderedResourceSnapshot is the authenticated immutable production representation.
	RenderedResourceSnapshot *templating.RenderedResourceSnapshot

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

	// CacheState is "warm" when this render had a graph to build on, "cold" when
	// it re-evaluated everything, and "replay" when it reused the previous output.
	CacheState string

	// CacheBuildMs is what the most recent completed cache build cost, or 0 while
	// none has completed.
	CacheBuildMs int64

	// RootsReused counts the auxiliary roots that handed back their previous
	// output instead of executing, because they observed nothing new.
	RootsReused int

	// IncludeStats holds per-snippet render counts/timing for the main template.
	// Populated only when the engine was built with profiling enabled (nil in
	// production); consumed by the browser playground's render-trace view.
	IncludeStats []templating.IncludeStats

	// AuxFileCount is the total number of auxiliary files.
	AuxFileCount int

	// InputTransaction owns render-local inputs until the full pipeline decides their fate.
	InputTransaction RenderInputTransaction
	// contains filtered or unexported fields
}

RenderResult contains the output of a render operation.

func (*RenderResult) MaterializeAuxiliaryFiles

func (r *RenderResult) MaterializeAuxiliaryFiles() (*dataplane.AuxiliaryFiles, error)

MaterializeAuxiliaryFiles returns a caller-isolated compatibility view.

func (*RenderResult) MaterializeEvents

func (r *RenderResult) MaterializeEvents() ([]templating.RenderedEvent, error)

MaterializeEvents returns a caller-isolated compatibility view.

func (*RenderResult) MaterializePlan

func (r *RenderResult) MaterializePlan() (*renderplan.Plan, error)

MaterializePlan returns a caller-owned plan from the authenticated output root.

func (*RenderResult) MaterializeRenderedResources

func (r *RenderResult) MaterializeRenderedResources() ([]templating.RenderedResource, error)

MaterializeRenderedResources returns a caller-isolated compatibility view.

func (*RenderResult) MaterializeStatusPatches

func (r *RenderResult) MaterializeStatusPatches() ([]templating.StatusPatch, error)

MaterializeStatusPatches returns a caller-isolated compatibility view.

type RenderService

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

RenderService is a pure service that transforms stores into HAProxy configuration.

This service uses absolute paths from the config's Dataplane settings to ensure rendered configs reference files at the correct locations where the agent stores auxiliary files.

Resources in stores are already converted (floats to ints) at storage time, so the service simply passes through store data without additional processing.

func NewRenderService

func NewRenderService(cfg *RenderServiceConfig) *RenderService

NewRenderService creates a new RenderService.

The service uses relative paths derived from the config's Dataplane settings. The directory names are extracted using path.Base() to get just the final directory component (e.g., /etc/haproxy/maps → maps).

These relative paths are resolved by HAProxy using the `default-path origin <baseDir>` directive in the global section, which makes HAProxy resolve paths from the specified base directory regardless of where the config file is located. This works for:

  • Local validation: ValidationService replaces baseDir with temp directory
  • Deployment: baseDir points to where the agent writes files (e.g., /etc/haproxy)

func (*RenderService) ClearVMPool

func (s *RenderService) ClearVMPool()

ClearVMPool releases pooled template engine VMs. Call after rendering completes to reduce memory from parallel rendering spikes.

func (*RenderService) FirstGraphPublished

func (s *RenderService) FirstGraphPublished() bool

FirstGraphPublished reports whether this service has published a render graph, after which no render pays the full cold cost again. Lock-free for probes. A service that doesn't run cold renders to completion reports true.

func (*RenderService) HTTPStoreComponent

func (s *RenderService) HTTPStoreComponent() *httpstore.Component

HTTPStoreComponent returns the HTTP store this service renders against, or nil.

func (*RenderService) IncrementalGraphWarm

func (s *RenderService) IncrementalGraphWarm() bool

IncrementalGraphWarm reports whether the next reconcile render builds on a committed graph: one has been published and no cache build is pending. A service without incremental snippets has nothing to warm and reports true.

func (*RenderService) Render

Render transforms the stores into HAProxy configuration.

Parameters:

  • ctx: Context for cancellation
  • provider: StoreProvider for accessing resource stores

Returns:

  • RenderResult containing the rendered configuration and auxiliary files
  • Error if rendering fails

func (*RenderService) RenderSourceMaps

func (s *RenderService) RenderSourceMaps(ctx context.Context, provider stores.StoreProvider) (map[string]TemplateSourceMap, error)

RenderSourceMaps renders the main config and each map/file template a second time with source-map collection, over the same context Render builds, and returns name→source map. It is a playground-only pass (provenance); it returns nil if the engine doesn't support source maps. Names are the template registry keys: names.MainTemplateName for the config, and the map/file key otherwise.

func (*RenderService) RetireIncrementalCache

func (s *RenderService) RetireIncrementalCache() error

RetireIncrementalCache releases persistent inputs owned by this service.

func (*RenderService) SetAckedPlan

func (s *RenderService) SetAckedPlan(plan *renderplan.Plan)

SetAckedPlan records the plan the fleet confirmed it is running. Pods that disagree resolve to the newest ACK, which is what this call carries.

func (*RenderService) SetCapabilities

func (s *RenderService) SetCapabilities(capabilities dataplane.Capabilities)

SetCapabilities re-sources what templates read as `capabilities` from the fleet's lowest reported HAProxy version. The controller image's own binary seeds the value so the first render is not degraded; discovery replaces it once the pods have reported.

type RenderServiceConfig

type RenderServiceConfig struct {
	// Engine is the template engine to use for rendering.
	Engine templating.Engine

	// Config is the controller configuration.
	Config *config.Config

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

	// Capabilities defines HAProxy version capabilities.
	Capabilities dataplane.Capabilities

	// HAProxyPodStore is the store containing HAProxy pods (optional).
	HAProxyPodStore stores.Store

	// HTTPStoreComponent is the HTTP store for dynamic content (optional).
	HTTPStoreComponent *httpstore.Component

	// IncrementalCacheBuildObserver receives optional cold-cache lifecycle notifications.
	IncrementalCacheBuildObserver IncrementalCacheBuildObserver

	// AdmissionSlotWaitObserver, if set, receives how long each admission
	// render waited for its slot, whether or not it got one.
	AdmissionSlotWaitObserver func(time.Duration)

	// CurrentAuxFilesProvider returns the default auxiliary baseline. The
	// Coordinator overrides it with a leader-term snapshot for reconciliation.
	CurrentAuxFilesProvider func() map[string]string

	// ColdRendersRunToCompletion lets a reconcile render that starts without
	// a committed graph run past the render timeout until its context ends,
	// one at a time. Set it only when every reconcile caller's context ends on
	// shutdown or leadership loss; a caller without one, such as the
	// playground, relies on the timeout to stop a template that never returns.
	ColdRendersRunToCompletion bool

	// TypedResourceTypes carries the generated Go types produced
	// by pkg/controller/typebootstrap at iteration start. The
	// renderer emits one *[]*<generated-struct> top-level context
	// entry per type at render time, matching the typed-global
	// declarations the engine constructor received.
	//
	// Optional. nil or empty means typed-resource access isn't
	// available and templates fall back to the untyped
	// resources["<name>"] path.
	TypedResourceTypes map[string]reflect.Type
}

RenderServiceConfig contains configuration for creating a RenderService.

type TemplateSourceMap

type TemplateSourceMap struct {
	Raw   string
	Spans []templating.SourceSpan
}

TemplateSourceMap is the raw (pre-post-processing) render of one template plus its output-to-source spans. Length fields of Spans sum to len(Raw).

Source Files

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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