rendercontext

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

Documentation

Overview

Package rendercontext provides a centralized builder for template rendering contexts.

This package consolidates the previously duplicated context creation logic from renderer, testrunner, benchmark, and dryrunvalidator into a single, reusable builder.

Usage:

builder := rendercontext.NewBuilder(
    cfg,
    pathResolver,
    logger,
    rendercontext.WithStores(stores),
    rendercontext.WithCapabilities(capabilities),
)
ctx, fileRegistry := builder.Build()

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func MergeAuxiliaryFiles

func MergeAuxiliaryFiles(static, dynamic *dataplane.AuxiliaryFiles) *dataplane.AuxiliaryFiles

MergeAuxiliaryFiles merges two AuxiliaryFiles structures, with dynamic files appended to static files.

This is used to combine:

  • Pre-declared templates (maps, files, certs from config)
  • Dynamically registered files (via FileRegistry during rendering).

func MergeExtraContextInto

func MergeExtraContextInto(renderCtx map[string]interface{}, cfg *config.Config)

MergeExtraContextInto merges the extraContext variables from the config into the provided template context.

This allows templates to access custom variables directly (e.g., {{ debug.enabled }}) instead of wrapping them in a "config" object.

The extraContext key is always populated (with an empty map if nil) to prevent nil pointer dereferences in templates that use extraContext | dig("key") | fallback(default).

func PathResolverFromValidationPaths

func PathResolverFromValidationPaths(vp *dataplane.ValidationPaths) *templating.PathResolver

PathResolverFromValidationPaths creates a PathResolver from ValidationPaths.

This is a convenience function for creating PathResolvers in contexts where ValidationPaths is available (testrunner, dryrunvalidator, benchmark).

func SeparateHAProxyPodStore

func SeparateHAProxyPodStore(stores map[string]types.Store) (resourceStores map[string]types.Store, haproxyPodStore types.Store)

SeparateHAProxyPodStore separates the haproxy-pods store from resource stores.

This is needed because haproxy-pods goes in the controller namespace (controller.haproxy_pods), not in the resources namespace. This function extracts haproxy-pods and returns both the filtered resource stores and the haproxy pod store separately.

Returns:

  • resourceStores: All stores except haproxy-pods
  • haproxyPodStore: The haproxy-pods store, or nil if not present

func SortSnippetNames

func SortSnippetNames(snippets map[string]config.TemplateSnippet) []string

SortSnippetNames sorts template snippet names alphabetically. Returns a slice of snippet names in sorted order.

Note: Snippet ordering is now controlled by encoding priority in the snippet name (e.g., "features-050-ssl" for priority 50). This is required because render_glob sorts templates alphabetically.

Types

type Builder

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

Builder constructs template rendering contexts with consistent structure. Use NewBuilder() to create a builder and functional options to configure it.

func NewBuilder

func NewBuilder(cfg *config.Config, pathResolver *templating.PathResolver, logger *slog.Logger, opts ...Option) *Builder

NewBuilder creates a new context builder with required dependencies.

Parameters:

  • cfg: Controller configuration (required)
  • pathResolver: Path resolver for file paths (required)
  • logger: Structured logger (required)
  • opts: Optional configuration via functional options

func (*Builder) Build

func (b *Builder) Build() (map[string]interface{}, *FileRegistry)

Build creates the template rendering context and file registry.

The context structure is:

{
  "resources": map of StoreWrappers,
  "controller": {"haproxy_pods": StoreWrapper},
  "templateSnippets": []string,
  "fileRegistry": FileRegistry,
  "pathResolver": PathResolver,
  "dataplane": Config.Dataplane,
  "capabilities": map[string]bool (if set),
  "shared": map[string]interface{},
  "runtimeEnvironment": RuntimeEnvironment,
  "http": HTTPFetcher (if set),
  "extraContext": map from config,
}

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 Option

type Option func(*Builder)

Option configures a Builder.

func WithCapabilities

func WithCapabilities(caps *dataplane.Capabilities) Option

WithCapabilities sets the HAProxy capabilities for conditional template generation. If nil, no capabilities map is added to the context.

func WithHAProxyPodStore

func WithHAProxyPodStore(store types.Store) Option

WithHAProxyPodStore sets the HAProxy pod store for controller.haproxy_pods. This enables templates to access HAProxy pod count for calculations.

func WithHTTPFetcher

func WithHTTPFetcher(fetcher templating.HTTPFetcher) Option

WithHTTPFetcher sets the HTTP fetcher for http.Fetch() calls in templates. Pass nil to disable HTTP fetching capability.

func WithStores

func WithStores(stores map[string]types.Store) Option

WithStores sets the resource stores for the template context. Each store is wrapped in a StoreWrapper to provide template-friendly methods.

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)

Lifecycle: StoreWrapper is designed for single-reconciliation use. A new wrapper should be created for each reconciliation cycle via Builder.Build(). The caching behavior assumes the underlying Store contents don't change during the wrapper's lifetime. Do NOT reuse StoreWrapper instances across reconciliations, as the cached List() results may become stale.

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