renderer

package
v0.1.0-alpha.9 Latest Latest
Warning

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

Go to latest
Published: Dec 30, 2025 License: Apache-2.0 Imports: 19 Imported by: 0

README

pkg/controller/renderer

Renderer component - template rendering for HAProxy configuration.

Overview

Event-driven component that renders HAProxy configuration from templates using current resource state.

Quick Start

renderer := renderer.NewRendererComponent(bus, engine, logger)
go renderer.Start(ctx)

Events

  • Subscribes: ReconciliationTriggeredEvent
  • Publishes: TemplateRenderedEvent, TemplateRenderFailedEvent

Template Context

The renderer builds a context with all watched Kubernetes resources:

{
  "resources": {
    "ingresses": StoreWrapper,    // Provides List() and Get()
    "services": StoreWrapper,
    "endpoints": StoreWrapper,
    // ... other watched resources
  }
}
StoreWrapper Performance

StoreWrapper unwraps unstructured.Unstructured objects to plain maps for template access:

  • List(): Lazy-cached - unwraps all resources on first call, caches for subsequent calls within the same reconciliation
  • Get(keys...): On-demand - unwraps matched resources each call (typically small result sets)

This ensures templates pay the unwrapping cost only once per reconciliation, regardless of how many times List() is called.

License

See main repository for license information.

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

View Source
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

func (c *Component) HealthCheck() error

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

func (c *Component) Name() string

Name returns the unique identifier for this component. Implements the lifecycle.Component interface.

func (*Component) SetHTTPStoreComponent

func (c *Component) SetHTTPStoreComponent(httpStoreComponent *httpstore.Component)

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

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

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.

Jump to

Keyboard shortcuts

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