Documentation
¶
Overview ¶
Package renderer implements the Renderer component that renders HAProxy configuration and auxiliary files from templates.
The Renderer is a Stage 5 component that subscribes to reconciliation trigger events, builds rendering context from resource stores, and publishes rendered output events for the next phase (validation/deployment).
Index ¶
Constants ¶
const ( // ComponentName is the unique identifier for this component. ComponentName = "renderer" // EventBufferSize is the size of the event subscription buffer. EventBufferSize = 50 )
Variables ¶
This section is empty.
Functions ¶
func MergeAuxiliaryFiles ¶
func MergeAuxiliaryFiles(static, dynamic *dataplane.AuxiliaryFiles) *dataplane.AuxiliaryFiles
mergeAuxiliaryFiles merges static (pre-declared) and dynamic (registered during rendering) auxiliary files.
The function combines both sets of files into a single AuxiliaryFiles structure. Both static and dynamic files are included in the merged result.
Parameters:
- static: Pre-declared auxiliary files from config (templates in config.Maps, config.Files, config.SSLCertificates)
- dynamic: Dynamically registered files from FileRegistry during template rendering
Returns:
- Merged AuxiliaryFiles containing all files from both sources
MergeAuxiliaryFiles merges static (pre-declared) and dynamic (FileRegistry-registered) auxiliary files. Exported for use by test runner.
Types ¶
type Component ¶
type Component struct {
// contains filtered or unexported fields
}
Component implements the renderer component.
It subscribes to ReconciliationTriggeredEvent and BecameLeaderEvent, renders all templates using the template engine and resource stores, and publishes the results via TemplateRenderedEvent or TemplateRenderFailedEvent.
The component renders configurations twice per reconciliation: 1. Production version with absolute paths for HAProxy pods (/etc/haproxy/*) 2. Validation version with temp directory paths for controller validation
The component caches the last rendered output to support state replay during leadership transitions (when new leader-only components start subscribing).
CRT-list Fallback: The component determines CRT-list storage capability from the local HAProxy version (passed at construction time). When CRT-list storage is not supported (HAProxy < 3.2), CRT-list file paths are resolved to the general files directory instead of the SSL directory, ensuring the generated configuration matches where files are actually stored.
func New ¶
func New( eventBus *busevents.EventBus, cfg *config.Config, stores map[string]types.Store, haproxyPodStore types.Store, capabilities dataplane.Capabilities, logger *slog.Logger, ) (*Component, error)
New creates a new Renderer component.
The component pre-compiles all templates during initialization for optimal runtime performance.
Parameters:
- eventBus: The EventBus for subscribing to events and publishing results
- config: Controller configuration containing templates
- stores: Map of resource type names to their stores (e.g., "ingresses" -> Store)
- haproxyPodStore: Store containing HAProxy controller pods for pod-maxconn calculations
- capabilities: HAProxy capabilities determined from local version
- logger: Structured logger for component logging
Returns:
- A new Component instance ready to be started
- Error if template compilation fails
func (*Component) HealthCheck ¶
HealthCheck implements the lifecycle.HealthChecker interface. Returns an error if the component appears to be stalled (processing for > timeout). Returns nil when idle (not processing) - idle is always healthy for event-driven components.
func (*Component) Name ¶
Name returns the unique identifier for this component. Implements the lifecycle.Component interface.
func (*Component) SetHTTPStoreComponent ¶
SetHTTPStoreComponent sets the HTTP store component for dynamic HTTP resource fetching. This must be called before Start() to enable http.Fetch() in templates.
func (*Component) Start ¶
Start begins the renderer's event loop.
This method blocks until the context is cancelled or an error occurs. The component is already subscribed to the EventBus (subscription happens in New()), so this method only processes events:
- ReconciliationTriggeredEvent: Starts template rendering
- BecameLeaderEvent: Replays last rendered state for new leader-only components
The component runs until the context is cancelled, at which point it performs cleanup and returns.
Parameters:
- ctx: Context for cancellation and lifecycle management
Returns:
- nil when context is cancelled (graceful shutdown)
- Error only in exceptional circumstances
type FileRegistry ¶
type FileRegistry struct {
// contains filtered or unexported fields
}
FileRegistry allows templates to dynamically register auxiliary files (certs, maps, general files) during rendering. This is used for cases where file content comes from dynamic sources (e.g., certificates from secrets) rather than pre-declared templates.
Usage in templates:
{% set ca_content = secret.data["ca.crt"] | b64decode %}
{% set ca_path = file_registry.Register("cert", "my-backend-ca.pem", ca_content) %}
server backend:443 ssl ca-file {{ ca_path }} verify required
The Registry method is called on the FileRegistry object in the template rendering context, not as a standalone filter.
func NewFileRegistry ¶
func NewFileRegistry(pathResolver *templating.PathResolver) *FileRegistry
NewFileRegistry creates a new FileRegistry with the given path resolver. The path resolver is used to compute full paths for registered files, ensuring they match the paths used by pathResolver.GetPath() method.
func (*FileRegistry) GetFiles ¶
func (r *FileRegistry) GetFiles() *dataplane.AuxiliaryFiles
GetFiles converts all registered files to dataplane AuxiliaryFiles structure. This is called by the renderer after template rendering completes to merge dynamic files with pre-declared auxiliary files.
func (*FileRegistry) Register ¶
func (r *FileRegistry) Register(args ...interface{}) (string, error)
Register registers a new auxiliary file to be created and returns its predicted path. This method is called from templates as file_registry.Register(type, filename, content).
Parameters:
- fileType: "cert", "map", "file", or "crt-list"
- filename: Base filename (e.g., "ca.pem", "domains.map", "certificate-list.txt")
- content: File content as a string
Returns:
- Predicted absolute path where the file will be located
- Error if validation fails or content conflict detected
Conflict Detection:
- If the same filename is registered multiple times with different content, returns error
- If the same filename is registered with identical content, no error (idempotent)
type StoreWrapper ¶
type StoreWrapper struct {
Store types.Store
ResourceType string
Logger *slog.Logger
// Lazy cache for List() results
CachedList []interface{}
ListCached bool
}
StoreWrapper wraps a types.Store to provide template-friendly methods that don't return errors (errors are logged instead).
This allows templates to call methods like List() and Get() directly without having to handle Go's multi-return values.
The wrapper implements lazy-cached unwrapping:
- List() results are unwrapped once on first call and cached for the reconciliation
- Get() results are unwrapped on-demand (typically small result sets)
func (*StoreWrapper) Fetch ¶
func (w *StoreWrapper) Fetch(keys ...interface{}) []interface{}
Fetch performs O(1) indexed lookup using the provided keys.
This method enables efficient lookups in templates and supports non-unique index keys by returning all resources matching the provided keys:
{% for endpoint_slice in resources.endpoints.Fetch(service_name) %}
{{ endpoint_slice.metadata.name }}
{% endfor %}
The keys must match the index configuration for the resource type. For example, if EndpointSlices are indexed by service name:
index_by: ["metadata.labels['kubernetes.io/service-name']"]
Then you can look them up with:
resources.endpoints.Fetch("my-service")
This will return ALL EndpointSlices for that service (typically multiple).
Accepts interface{} arguments for template compatibility.
If an error occurs, it's logged and an empty slice is returned.
func (*StoreWrapper) GetSingle ¶
func (w *StoreWrapper) GetSingle(keys ...interface{}) interface{}
GetSingle performs O(1) indexed lookup and expects exactly one matching resource.
This method is useful when you know the index keys uniquely identify a resource:
{% set ingress = resources.ingresses.GetSingle("default", "my-ingress") %}
{% if ingress %}
{{ ingress.metadata.name }}
{% endif %}
{# Cross-namespace reference #}
{% set ref = "namespace/name".split("/") %}
{% set secret = resources.secrets.GetSingle(ref[0], ref[1]) %}
Accepts interface{} arguments for template compatibility.
Returns:
- nil if no resources match (this is NOT an error - allows templates to check existence)
- The single matching resource if exactly one matches
- nil + logs error if multiple resources match (ambiguous lookup)
If an error occurs during the store operation, it's logged and nil is returned.
func (*StoreWrapper) List ¶
func (w *StoreWrapper) List() []interface{}
List returns all resources in the store.
This method is intended for template iteration:
{% for ingress in resources.ingresses.List() %}
{{ ingress.metadata.name }}
{% endfor %}
Resources are unwrapped from unstructured.Unstructured to maps on first call and cached for subsequent calls within the same reconciliation cycle.
If an error occurs, it's logged and an empty slice is returned.