templating

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

README

Template Engine Library

Overview

This package provides template rendering using Scriggo, a Go-native template engine.

Key features:

  • Pre-compilation at startup (fail-fast, microsecond rendering)
  • Go template syntax
  • Thread-safe concurrent rendering
  • Custom filters for HAProxy use cases
  • Dynamic include support

Quick Start

package main

import (
    "log"
    "haptic/pkg/templating"
)

func main() {
    templates := map[string]string{
        "greeting": "Hello {{ name }}!",
        "config":   "server {{ host }}:{{ port }}",
    }

    // EngineTypeScriggo is the default and recommended engine
    engine, err := templating.New(templating.EngineTypeScriggo, templates, nil, nil, nil)
    if err != nil {
        log.Fatalf("failed to create engine: %v", err)
    }

    output, err := engine.Render("greeting", map[string]interface{}{
        "name": "World",
    })
    if err != nil {
        log.Fatal(err)
    }

    log.Println(output) // Output: Hello World!
}

API Reference

Constructor
func New(engineType EngineType, templates map[string]string) (*TemplateEngine, error)
func NewWithFilters(engineType EngineType, templates map[string]string, filters map[string]FilterFunc) (*TemplateEngine, error)

Creates a new engine and compiles all templates. Returns CompilationError if any template has syntax errors.

Rendering
func (e *TemplateEngine) Render(templateName string, context map[string]interface{}) (string, error)

Executes a template with the provided context.

Helper Methods
func (e *TemplateEngine) HasTemplate(name string) bool      // Check existence
func (e *TemplateEngine) TemplateNames() []string           // List templates
func (e *TemplateEngine) TemplateCount() int                // Count templates
func (e *TemplateEngine) GetRawTemplate(name string) (string, error)  // Get source
Error Types
Type When Returned Key Fields
CompilationError Template syntax error TemplateName, TemplateSnippet, Cause
RenderError Runtime rendering failure TemplateName, Cause
TemplateNotFoundError Template doesn't exist TemplateName, AvailableTemplates
UnsupportedEngineError Invalid engine type EngineType
output, err := engine.Render("mytemplate", context)
if err != nil {
    var renderErr *templating.RenderError
    if errors.As(err, &renderErr) {
        log.Printf("Render failed for '%s': %v", renderErr.TemplateName, renderErr.Cause)
    }
}

Custom Filters

Creating Custom Filters
filters := map[string]templating.FilterFunc{
    "to_upper": func(in interface{}, args ...interface{}) (interface{}, error) {
        str, ok := in.(string)
        if !ok {
            return nil, fmt.Errorf("to_upper requires string input")
        }
        return strings.ToUpper(str), nil
    },
}

engine, err := templating.NewWithFilters(templating.EngineTypeScriggo, templates, filters)
Built-in Custom Filters
Filter Description Example
b64decode Decode base64 {{ secret.data.password | b64decode }}
glob_match Filter by glob pattern {{ snippets | glob_match("backend-*") }}
sort_by Sort by JSONPath {{ routes | sort_by(["$.priority:desc"]) }}
extract Extract via JSONPath {{ routes | extract("$.rules[*].host") }}
group_by Group by JSONPath {{ items | group_by("$.namespace") }}
transform Regex substitution {{ paths | transform("^/api", "") }}
debug Dump as JSON comment {{ routes | debug("routes") }}
eval Show JSONPath evaluation {{ route | eval("$.priority") }}

sort_by modifiers:

  • :desc - Descending order
  • :exists - Sort by field presence
  • | length - Sort by collection/string length
Built-in Functions
Function Description Example
fail(msg) Stop rendering with error {% fail("Missing required field") %}
merge(dict, updates) Merge two maps {% config = merge(config, updates) %}
keys(dict) Get sorted map keys {% for _, k := range keys(config) %}
has_cached(key) Check if value is cached {% if !has_cached("analysis") %}
get_cached(key) Retrieve cached value {% var data = get_cached("analysis") %}
set_cached(key, val) Store value in cache {% set_cached("analysis", result) %}

Cache Functions:

The cache functions enable expensive computations to run only once per render:

{% if !has_cached("gateway_analysis") %}
    {% var result = analyzeRoutes(resources) %}
    {% set_cached("gateway_analysis", result) %}
{% end %}
{% var analysis = get_cached("gateway_analysis") %}

Cache is per-render (isolated between Render() calls) and supports any value type.

pathResolver.GetPath() - Context method for file path resolution:

{{ pathResolver.GetPath("host.map", "map") }}     {# /etc/haproxy/maps/host.map #}
{{ pathResolver.GetPath("cert.pem", "cert") }}    {# /etc/haproxy/ssl/cert.pem #}
{{ pathResolver.GetPath("error.http", "file") }}  {# /etc/haproxy/general/error.http #}

Template Syntax (Scriggo)

Scriggo uses Go template syntax. See Scriggo Documentation for complete reference.

Variables:

{{ name }}
{{ user.name }}
{{ items[0] }}

Functions (Scriggo uses function calls, not pipe syntax):

{{ strip(value) }}
{{ coalesce(value, "default") }}
{{ join(items, ", ") }}

Control Structures:

{% if condition %}...{% end %}
{% for _, item := range items %}...{% end %}

Variable Declaration:

{% var count = 0 %}
{% count = count + 1 %}

Whitespace Control: Use {%- and -%} to strip whitespace.

Caching Expensive Computations

Use has_cached, get_cached, set_cached for compute-once patterns:

{% if !has_cached("analysis") %}
    {% var result = analyzeRoutes(resources) %}
    {% set_cached("analysis", result) %}
{% end %}
{% var analysis = get_cached("analysis") %}

Performance: Reduces redundant computations by 75%+ in multi-include scenarios.

Template Tracing

engine.EnableTracing()
output, _ := engine.Render("template", context)
trace := engine.GetTraceOutput()
// Rendering: haproxy.cfg
// Completed: haproxy.cfg (0.007ms)
engine.DisableTracing()

Filter Debug Logging

engine.EnableFilterDebug()
// Logs sort_by comparisons with values and types
output, _ := engine.Render("template", context)
engine.DisableFilterDebug()

Best Practices

1. Pre-compile at startup:

// Good - compile once, reuse
engine, err := templating.New(templating.EngineTypeScriggo, templates, nil, nil, nil)
for _, ctx := range contexts {
    output, _ := engine.Render("template", ctx)
}

// Bad - recompiles every time
for _, ctx := range contexts {
    engine, _ := templating.New(templating.EngineTypeScriggo, templates, nil, nil, nil)
    output, _ := engine.Render("template", ctx)
}

2. Check compilation errors early:

engine, err := templating.New(templating.EngineTypeScriggo, templates, nil, nil, nil)
if err != nil {
    var compErr *templating.CompilationError
    if errors.As(err, &compErr) {
        log.Fatal("compilation failed", "template", compErr.TemplateName)
    }
}

3. Use coalesce for optional values (Scriggo):

timeout connect {{ coalesce(timeout_connect, "5s") }}

4. Break large templates into pieces:

templates := map[string]string{
    "haproxy.cfg": `{% render "global" %}{% render "backends" %}`,
    "global":      "global\n    daemon",
    "backends":    "...",
}

Troubleshooting

Problem Solution
Compilation error Check template syntax, verify filter names
Template not found Verify name spelling, use HasTemplate()
Empty output Check context data, verify conditionals
Slow rendering Reuse engine instance, simplify loops

Performance

  • Compilation: 1-10ms per template
  • Rendering: 10-100µs per typical template
  • Thread-safe: Safe for concurrent use from multiple goroutines

Documentation

Overview

Package templating provides template rendering capabilities using the Scriggo template engine.

This package offers a unified interface for compiling and rendering templates using Go template syntax via the Scriggo engine.

Templates are pre-compiled at initialization for optimal runtime performance and early detection of syntax errors.

Index

Constants

View Source
const (
	// FilterSortBy sorts items by JSONPath criteria.
	FilterSortBy = "sort_by"

	// FilterGlobMatch filters items by glob pattern.
	FilterGlobMatch = "glob_match"

	// FilterStrip removes leading/trailing whitespace.
	FilterStrip = "strip"

	// FilterTrim removes leading/trailing whitespace (alias for strip).
	FilterTrim = "trim"

	// FilterB64Decode decodes base64-encoded strings.
	FilterB64Decode = "b64decode"

	// FilterDebug outputs debug information for items.
	FilterDebug = "debug"

	// FilterIndent indents each line of a string by a specified amount.
	FilterIndent = "indent"
)

Filter name constants for template engines.

View Source
const (
	// FuncFail stops template execution with an error message.
	FuncFail = "fail"

	// FuncMerge merges two maps, returning a new map.
	// Values from the second map override values from the first.
	FuncMerge = "merge"

	// FuncKeys returns sorted keys from a map.
	FuncKeys = "keys"

	// FuncSortStrings sorts a []any slice as strings.
	// Useful when append() returns []any but you need sorted strings.
	FuncSortStrings = "sort_strings"

	// FuncStringsContains checks if a string contains a substring.
	FuncStringsContains = "strings_contains"

	// FuncStringsSplit splits a string by a separator.
	FuncStringsSplit = "strings_split"

	// FuncStringsTrim trims whitespace from a string.
	FuncStringsTrim = "strings_trim"

	// FuncStringsLower converts a string to lowercase.
	FuncStringsLower = "strings_lower"

	// FuncStringsReplace replaces all occurrences of old with new.
	FuncStringsReplace = "strings_replace"

	// FuncStringsSplitN splits a string by a separator with a maximum number of parts.
	FuncStringsSplitN = "strings_splitn"

	// FuncToString converts a value to string.
	FuncToString = "tostring"

	// FuncToInt converts a value to int.
	FuncToInt = "toint"

	// FuncToFloat converts a value to float64.
	FuncToFloat = "tofloat"

	// FuncCeil returns the ceiling of a float.
	FuncCeil = "ceil"

	// FuncSeq generates a sequence of integers from 0 to n-1.
	// Syntax: seq(n) returns []int{0, 1, 2, ..., n-1}.
	FuncSeq = "seq"

	// FuncRegexSearch checks if a string matches a regex pattern.
	FuncRegexSearch = "regex_search"

	// FuncIsDigit checks if a string contains only digits.
	FuncIsDigit = "isdigit"

	// FuncSanitizeRegex escapes regex special characters.
	FuncSanitizeRegex = "sanitize_regex"

	// FuncTitle converts a string to title case.
	FuncTitle = "title"

	// FuncDig navigates nested maps/structures using a sequence of keys.
	// Returns nil if any key along the path is missing.
	// Ruby-style dig: obj | dig("metadata", "namespace") | fallback("")
	// Available in: Scriggo only.
	FuncDig = "dig"

	// FuncToStringSlice converts []any to []string.
	// Available in: Scriggo only.
	FuncToStringSlice = "toStringSlice"

	// FuncJoin joins a string slice with a separator.
	// Available in: Scriggo only.
	FuncJoin = "join"

	// FuncReplace replaces strings (alias for strings_replace).
	FuncReplace = "replace"

	// FuncCoalesce returns the first non-nil value, or the default if nil.
	// Workaround for Scriggo's default operator limitation with field access.
	FuncCoalesce = "coalesce"

	// FuncFallback returns the first non-nil value, or the fallback if nil.
	// Jinja2-style alias for coalesce, works well with pipe syntax (e.g., {{ value | fallback("default") }}).
	FuncFallback = "fallback"

	// FuncNamespace creates a mutable map for storing state in loops.
	FuncNamespace = "namespace"

	// FuncToSlice converts any value to []any for safe ranging.
	// Returns empty slice if input is nil, otherwise converts to []any.
	// Syntax: toSlice(value) - returns []any.
	FuncToSlice = "toSlice"

	// FuncAppendAny appends an item to a slice, handling interface{} types.
	// Syntax: append(slice, item) - returns []any
	// If slice is nil, creates a new slice with the item.
	FuncAppendAny = "append"

	// FuncFirstSeen checks if a composite key is being seen for the first time.
	// Returns true on first occurrence, false on subsequent calls with same key.
	// Thread-safe for parallel template rendering.
	// Syntax: first_seen("prefix", key1, key2, ...) returns bool.
	FuncFirstSeen = "first_seen"

	// FuncSelectAttr filters items by attribute existence or value.
	// Jinja2-compatible filter for selecting items from a sequence.
	// Syntax:
	//   selectattr(items, "attr")           - items where attr is defined/truthy
	//   selectattr(items, "attr", "eq", v)  - items where attr equals v
	//   selectattr(items, "attr", "ne", v)  - items where attr not equals v
	//   selectattr(items, "attr", "in", list) - items where attr is in list
	FuncSelectAttr = "selectattr"

	// FuncJoinKey joins multiple values into a composite key string with a separator.
	// Automatically converts all values to strings.
	// Syntax: join_key("_", val1, val2, ...) returns string.
	FuncJoinKey = "join_key"

	// FuncShardSlice divides a slice into N shards and returns the portion for a given shard index.
	// Used for parallel template rendering where work is split across goroutines.
	// Syntax: shard_slice(items, shardIndex, totalShards) returns []any.
	FuncShardSlice = "shard_slice"
)

Function name constants for template engines.

View Source
const (
	// EngineNameScriggo is the string identifier for the Scriggo engine.
	EngineNameScriggo = "scriggo"

	// EngineNameUnknown is used when an engine type is not recognized.
	EngineNameUnknown = "unknown"
)

Engine name constants used for parsing and display.

Variables

View Source
var RenderContextContextKey = renderContextKey{}

RenderContextContextKey is exported for use in engine_scriggo.go.

Functions

func B64Decode

func B64Decode(in interface{}, args ...interface{}) (interface{}, error)

B64Decode decodes a base64-encoded value. The input is converted to string using lenient type conversion.

Usage in templates:

{{ secret.data.username | b64decode }}
{{ secret.data.password | b64decode }}

Parameters:

  • in: Base64-encoded value to decode (converted to string)

Returns:

  • Decoded string
  • Error if decoding fails

Note: Kubernetes secrets automatically base64-encode all data values, so this filter is needed to access the plain-text content.

func Debug

func Debug(value interface{}, label string) string

Debug formats a value as JSON-formatted HAProxy comments.

This is the shared core implementation used by the Scriggo engine. Useful for debugging template data during development.

Usage in templates:

{{ routes | debug }}           → "# DEBUG:\n# [...]"
{{ routes | debug("label") }}  → "# DEBUG label:\n# [...]"

Parameters:

  • value: Any value to debug (will be JSON serialized)
  • label: Optional label to identify the debug output

Returns:

  • Formatted string with JSON data as HAProxy comments

func FailFunction

func FailFunction(args ...interface{}) (interface{}, error)

FailFunction is the standard "fail" template function that causes template rendering to abort with an error message. This function should be registered as a global function when creating template engines.

Usage in templates:

{{ fail("Service not found") }}
{% if not service %}{{ fail("Service is required") }}{% endif %}

The error message from fail() is extracted by dataplane.SimplifyRenderingError() to provide user-friendly feedback in admission webhooks and validation tests.

func FormatCompilationError

func FormatCompilationError(err error, templateName, templateContent string) string

FormatCompilationError formats a template compilation error into a human-readable multi-line string.

This function is similar to FormatRenderError but optimized for compilation/syntax errors. It parses template error messages to extract:

  • Line and column numbers
  • The actual problem (syntax error, unexpected token, etc.)
  • Contextual information from the template source

And returns a nicely formatted multi-line error message with:

  • Clear section headers
  • Template snippet showing the error location with surrounding lines
  • A caret (^) pointing to the exact column
  • Actionable hints for fixing the error

Parameters:

  • err: The error from template compilation (typically a *CompilationError)
  • templateName: Name of the template that failed
  • templateContent: Full template content for extracting context

Returns:

  • Formatted multi-line error string

func FormatRenderError

func FormatRenderError(err error, templateName, templateContent string) string

FormatRenderError formats a template rendering error into a human-readable multi-line string.

This function parses template error messages to extract:

  • Line and column numbers
  • The actual problem (e.g., "unknown method", "undefined variable")
  • Contextual information

And returns a nicely formatted multi-line error message with:

  • Clear section headers
  • Template snippet showing the error location
  • Actionable hints for fixing the error

Parameters:

  • err: The error from template rendering (typically a *RenderError)
  • templateName: Name of the template that failed
  • templateContent: Full template content for extracting context

Returns:

  • Formatted multi-line error string

func FormatRenderErrorShort

func FormatRenderErrorShort(err error, templateName string) string

FormatRenderErrorShort returns a shortened single-line version of the error. Useful for logging contexts where multi-line output isn't appropriate.

func GlobMatch

func GlobMatch(in interface{}, args ...interface{}) (interface{}, error)

GlobMatch filters a list of strings by glob pattern.

Usage in templates:

{%- set matching = template_snippets | glob_match("backend-annotation-*") %}
{%- for snippet_name in matching %}
  {% include snippet_name %}
{%- endfor %}

Parameters:

  • in: List of strings to filter ([]interface{} or []string)
  • args: Single argument specifying glob pattern (supports * and ? wildcards)

Returns:

  • Filtered list containing only matching strings
  • Error if input is not a list, pattern is missing, or pattern is invalid

func Strip

func Strip(s string) string

Strip removes leading and trailing whitespace from a string.

This is the shared core implementation used by the Scriggo engine.

Usage in templates:

{{ "  hello world  " | strip }}  → "hello world"

Parameters:

  • s: String to strip whitespace from

Returns:

  • String with leading and trailing whitespace removed

Types

type CompilationError

type CompilationError struct {
	// TemplateName is the name of the template that failed to compile
	TemplateName string

	// TemplateSnippet contains the first 200 characters of the template
	TemplateSnippet string

	// Cause is the underlying compilation error from the template engine
	Cause error
}

CompilationError represents a template compilation failure. This error occurs during template initialization when the template syntax is invalid or contains unsupported constructs.

func NewCompilationError

func NewCompilationError(templateName, templateContent string, cause error) *CompilationError

NewCompilationError creates a CompilationError for a template compilation failure.

func (*CompilationError) Error

func (e *CompilationError) Error() string

Error implements the error interface.

func (*CompilationError) Unwrap

func (e *CompilationError) Unwrap() error

Unwrap returns the underlying cause for error unwrapping.

type Engine

type Engine interface {
	// Render executes a template with the given context and returns the output.
	// Returns RenderError if template execution fails, or TemplateNotFoundError
	// if the template doesn't exist.
	Render(templateName string, context map[string]interface{}) (string, error)

	// RenderWithProfiling renders a template and returns profiling statistics
	// for included templates. Useful for performance debugging.
	//
	// Use NewScriggoWithProfiling to enable profiling. Returns nil
	// stats when profiling is disabled (NewScriggo).
	//
	// Stats are aggregated by template name - multiple renders of the same
	// template are combined into a single IncludeStats entry with count > 1.
	RenderWithProfiling(templateName string, context map[string]interface{}) (string, []IncludeStats, error)

	// EngineType returns the type of this engine.
	EngineType() EngineType

	// TemplateNames returns the names of all available templates, sorted alphabetically.
	TemplateNames() []string

	// HasTemplate checks if a template with the given name exists.
	HasTemplate(templateName string) bool

	// GetRawTemplate returns the original template string for the given name.
	// Returns TemplateNotFoundError if the template doesn't exist.
	GetRawTemplate(templateName string) (string, error)

	// TemplateCount returns the number of templates in the engine.
	TemplateCount() int

	// EnableTracing enables template execution tracing for debugging.
	EnableTracing()

	// DisableTracing disables template execution tracing.
	DisableTracing()

	// IsTracingEnabled returns whether tracing is currently enabled.
	IsTracingEnabled() bool

	// GetTraceOutput returns accumulated trace output and clears the buffer.
	GetTraceOutput() string

	// EnableFilterDebug enables detailed filter operation logging.
	EnableFilterDebug()

	// DisableFilterDebug disables detailed filter operation logging.
	DisableFilterDebug()

	// IsFilterDebugEnabled returns whether filter debug logging is enabled.
	IsFilterDebugEnabled() bool

	// AppendTraces appends traces from another engine to this engine's trace buffer.
	// This is useful for aggregating traces from multiple worker engines.
	AppendTraces(other Engine)
}

Engine defines the interface that all template engines must implement.

func New

func New(engineType EngineType, templates map[string]string, customFilters map[string]FilterFunc, customFunctions map[string]GlobalFunc, postProcessorConfigs map[string][]PostProcessorConfig) (Engine, error)

New creates a new template engine of the specified type. Currently only EngineTypeScriggo is supported.

All provided templates are compiled as entry points. This is the simplest API for creating a template engine. For more control over which templates are compiled vs discovered on-demand, use NewScriggo directly.

Parameters:

  • engineType: The type of engine to create (must be EngineTypeScriggo)
  • templates: All template content (all are compiled as entry points)
  • customFilters: Additional filters beyond built-in ones (can be nil)
  • customFunctions: Additional global functions beyond built-in ones (can be nil)
  • postProcessorConfigs: Post-processor configurations (can be nil)

Returns an error if an unsupported engine type is specified.

type EngineType

type EngineType int

EngineType represents the template engine to use for rendering.

const (
	// EngineTypeScriggo uses the Scriggo template engine (Go template syntax).
	// This is the default and only supported engine for HAProxy configuration templating.
	EngineTypeScriggo EngineType = iota
)

func ParseEngineType

func ParseEngineType(s string) (EngineType, error)

ParseEngineType converts a string to an EngineType. Empty string defaults to EngineTypeScriggo.

func (EngineType) String

func (e EngineType) String() string

String returns the string representation of the engine type.

type FileRegistrar

type FileRegistrar interface {
	Register(args ...interface{}) (string, error)
}

FileRegistrar is an interface for dynamic file registration during template rendering. This interface is implemented by rendercontext.FileRegistry, allowing templates to register auxiliary files (certificates, maps, etc.) without creating import cycles.

The Register method signature matches the variadic calling convention used in templates:

file_registry.Register("cert", "filename.pem", "content...")

Arguments:

  • args[0]: file type (string) - "cert", "map", "file", or "crt-list"
  • args[1]: filename (string) - base filename
  • args[2]: content (string) - file content

Returns:

  • Predicted absolute path where the file will be located
  • Error if validation fails or content conflict detected

type FilterFunc

type FilterFunc func(in interface{}, args ...interface{}) (interface{}, error)

FilterFunc is a template filter function signature. Template filters transform values within template expressions:

{{ value | filter_name(arg1, arg2) }}

FilterFunc receives:

  • in: The value being filtered (left side of | operator)
  • args: Additional filter arguments from the template

Example:

func upperFilter(in interface{}, args ...interface{}) (interface{}, error) {
    str, ok := in.(string)
    if !ok {
        return nil, fmt.Errorf("upper filter requires string input")
    }
    return strings.ToUpper(str), nil
}

type GlobalFunc

type GlobalFunc func(args ...interface{}) (interface{}, error)

GlobalFunc is a template global function signature. Global functions are called directly in templates:

{{ function_name(arg1, arg2) }}

GlobalFunc receives variadic arguments from the template call.

Example:

func mergeFunc(args ...interface{}) (interface{}, error) {
    if len(args) != 2 {
        return nil, fmt.Errorf("merge requires exactly 2 arguments")
    }
    // ... merge logic
}

type HTTPFetcher

type HTTPFetcher interface {
	// Fetch fetches content from a URL with optional options and authentication.
	// Arguments:
	//   - args[0]: URL (string, required)
	//   - args[1]: options (map, optional) - {"delay": "60s", "timeout": "30s", "retries": 3, "critical": true}
	//   - args[2]: auth (map, optional) - {"type": "bearer"|"basic", "token": "...", ...}
	Fetch(args ...interface{}) (interface{}, error)
}

HTTPFetcher defines the interface for HTTP resource fetching accessible from templates. This interface enables the http.Fetch() method in Scriggo templates:

{% var content = http.Fetch("https://example.com/blocklist.txt") %}
{% var content = http.Fetch(url, map[string]any{"delay": "60s", "critical": true}) %}

Implementations are provided by pkg/controller/httpstore.HTTPStoreWrapper.

type IncludeStats

type IncludeStats struct {
	// Name is the name of the included/rendered template.
	Name string

	// Count is the number of times this template was rendered.
	// Templates rendered multiple times (e.g., in loops) have count > 1.
	Count int

	// TotalMs is the cumulative time spent rendering this template in milliseconds.
	// For templates rendered multiple times, this is the sum of all renders.
	TotalMs float64

	// AvgMs is the average time per render in milliseconds (TotalMs / Count).
	AvgMs float64

	// MaxMs is the maximum time for a single render in milliseconds.
	MaxMs float64
}

IncludeStats represents timing statistics for template includes/renders. Used for performance profiling to identify slow templates.

type PathResolver

type PathResolver struct {
	// MapsDir is the absolute path to the HAProxy maps directory.
	// Default: /etc/haproxy/maps
	MapsDir string

	// SSLDir is the absolute path to the HAProxy SSL certificates directory.
	// Default: /etc/haproxy/ssl
	SSLDir string

	// CRTListDir is the absolute path to the HAProxy crt-list files directory.
	// Default: /etc/haproxy/ssl (same as SSL certificates)
	CRTListDir string

	// GeneralDir is the absolute path to the HAProxy general files directory.
	// Default: /etc/haproxy/general
	GeneralDir string
}

PathResolver resolves auxiliary file names to absolute paths based on file type. This is used via the GetPath method in templates to automatically construct absolute paths for HAProxy auxiliary files (maps, SSL certificates, crt-list files, general files).

func (*PathResolver) GetPath

func (pr *PathResolver) GetPath(args ...interface{}) (interface{}, error)

GetPath resolves a filename to its absolute path based on the file type.

This method is called from templates via the pathResolver context variable:

{{ pathResolver.GetPath("host.map", "map") }}              → /etc/haproxy/maps/host.map
{{ pathResolver.GetPath("504.http", "file") }}             → /etc/haproxy/general/504.http
{{ pathResolver.GetPath("cert.pem", "cert") }}             → /etc/haproxy/ssl/cert.pem
{{ pathResolver.GetPath("certificate-list.txt", "crt-list") }} → /etc/haproxy/ssl/certificate-list.txt
{{ pathResolver.GetPath("", "cert") }}                     → /etc/haproxy/ssl (directory only)

Parameters:

  • args[0]: filename (string) - The base filename (without directory path), or empty string for directory only
  • args[1]: fileType (string) - File type: "map", "file", "cert", or "crt-list"

Returns:

  • Absolute path to the file, or base directory if filename is empty
  • Error if argument count is wrong, arguments are not strings, file type is invalid, or path construction fails

Note: The pathResolver must be added to the rendering context for templates to access this method. Different PathResolver instances can be used for production paths vs validation paths.

type PostProcessor

type PostProcessor interface {
	// Process applies transformation to the input string.
	// Returns the transformed output or an error if processing fails.
	Process(input string) (string, error)
}

PostProcessor processes rendered template output before it is returned. Post-processors enable generic transformations like normalization, formatting, or cleanup that apply to the final rendered content.

Post-processors are applied in sequence after template rendering completes. Each processor receives the output of the previous processor (or the initial rendered template for the first processor).

func NewPostProcessor

func NewPostProcessor(config PostProcessorConfig) (PostProcessor, error)

NewPostProcessor creates a post-processor instance from configuration.

Returns an error if:

  • The processor type is unknown
  • Required parameters are missing
  • Parameters are invalid (e.g., invalid regex pattern)

type PostProcessorConfig

type PostProcessorConfig struct {
	// Type specifies which post-processor to use.
	Type PostProcessorType `yaml:"type" json:"type"`

	// Params contains type-specific configuration as key-value pairs.
	// For regex_replace:
	//   - pattern: Regular expression pattern to match (required)
	//   - replace: Replacement string (required)
	Params map[string]string `yaml:"params" json:"params"`
}

PostProcessorConfig defines configuration for a post-processor. The Type field determines which processor implementation to use, and Params contains type-specific configuration.

type PostProcessorType

type PostProcessorType string

PostProcessorType identifies the type of post-processor.

const (
	// PostProcessorTypeRegexReplace applies regex-based find/replace.
	PostProcessorTypeRegexReplace PostProcessorType = "regex_replace"
)

type ProfilingEntry

type ProfilingEntry struct {
	Name     string        // Template name or path
	Path     string        // Source file path
	Duration time.Duration // Execution time
}

ProfilingEntry represents timing for a single template include/render. This type provides a stable API for consumers while using Scriggo's built-in profiling under the hood.

type RegexReplaceProcessor

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

RegexReplaceProcessor applies regex-based find/replace to template output.

The processor operates line-by-line, applying the regex pattern to each line independently. This enables efficient processing of large outputs and supports line-anchored patterns like ^[ ]+ for indentation normalization.

Example usage for indentation normalization:

processor, err := NewRegexReplaceProcessor("^[ ]+", "  ")
normalized, err := processor.Process(haproxyConfig)

This replaces any leading spaces with exactly 2 spaces per line.

func NewRegexReplaceProcessor

func NewRegexReplaceProcessor(pattern, replace string) (*RegexReplaceProcessor, error)

NewRegexReplaceProcessor creates a new regex replace processor.

Parameters:

  • pattern: Regular expression pattern to match (e.g., "^[ ]+" for leading spaces)
  • replace: Replacement string (e.g., " " for 2-space indentation)

Returns an error if the regex pattern is invalid.

func (*RegexReplaceProcessor) Process

func (p *RegexReplaceProcessor) Process(input string) (string, error)

Process applies the regex replacement to each line of the input.

The processor:

  1. Splits input into lines
  2. Applies regex replacement to each line independently
  3. Joins lines back together with original line endings

This line-by-line approach enables:

  • Efficient processing of large files
  • Line-anchored patterns (^ and $)
  • Predictable behavior for indentation normalization

type RenderError

type RenderError struct {
	// TemplateName is the name of the template that failed to render
	TemplateName string

	// Cause is the underlying rendering error from the template engine
	Cause error
}

RenderError represents a template rendering failure. This error occurs when a valid template fails during execution, typically due to missing context variables or runtime evaluation errors.

func NewRenderError

func NewRenderError(templateName string, cause error) *RenderError

NewRenderError creates a RenderError for a template rendering failure.

func (*RenderError) Error

func (e *RenderError) Error() string

Error implements the error interface.

func (*RenderError) Unwrap

func (e *RenderError) Unwrap() error

Unwrap returns the underlying cause for error unwrapping.

type ResourceStore

type ResourceStore interface {
	// List returns all resources from the store.
	List() []interface{}

	// Fetch returns resources matching the given keys (typically namespace, name).
	Fetch(keys ...interface{}) []interface{}

	// GetSingle returns a single resource matching the keys, or nil if not found.
	GetSingle(keys ...interface{}) interface{}
}

ResourceStore defines the interface for resource stores accessible from templates. This interface enables direct method calls in Scriggo templates:

{% for _, ing := range resources.ingresses.List() %}
{% var secret = resources.secrets.GetSingle(namespace, name) %}
{% for _, ep := range resources.endpoints.Fetch(serviceName) %}

Scriggo supports dot notation for map access, so `resources.ingresses` is equivalent to `resources["ingresses"]`.

Implementations are provided by pkg/controller/rendercontext.StoreWrapper.

type RuntimeEnvironment

type RuntimeEnvironment struct {
	// GOMAXPROCS is the maximum number of OS threads for parallel execution.
	// Used by sharding logic to calculate optimal shard count.
	GOMAXPROCS int
}

RuntimeEnvironment holds runtime information available to templates. This enables templates to adapt behavior based on the execution environment.

Templates access this via the runtimeEnvironment variable:

{%- var maxShards = runtimeEnvironment.GOMAXPROCS * 2 %}

Fields use exported names for direct template access.

type ScriggoEngine

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

ScriggoEngine provides template compilation and rendering capabilities using Scriggo. It pre-compiles all templates at initialization for optimal runtime performance and early detection of syntax errors.

Scriggo uses Go template syntax, which is different from Jinja2:

  • Loops: {% for x := range items %}...{% end %}
  • Conditionals: {% if cond %}...{% else if other %}...{% end %}
  • Variables: {{ .name }} or {{ name }} when in globals

This engine offers excellent performance and low memory usage with Go-style template syntax.

func NewScriggo

func NewScriggo(templates map[string]string, entryPoints []string, customFilters map[string]FilterFunc, customFunctions map[string]GlobalFunc, postProcessorConfigs map[string][]PostProcessorConfig) (*ScriggoEngine, error)

NewScriggo creates a new Scriggo (Go template syntax) template engine. This engine offers better performance but requires different template syntax.

Parameters:

  • templates: All template content (entry points + snippets)
  • entryPoints: Template names to compile explicitly (others discovered via render calls)
  • customFilters: Additional filters beyond built-in ones (can be nil)
  • customFunctions: Additional global functions beyond built-in ones (can be nil)
  • postProcessorConfigs: Post-processor configurations (can be nil)

With inherit_context support: Only entryPoints are compiled explicitly. Template snippets are compiled automatically when referenced via render/render_glob statements.

func NewScriggoWithProfiling

func NewScriggoWithProfiling(templates map[string]string, entryPoints []string, customFilters map[string]FilterFunc, customFunctions map[string]GlobalFunc, postProcessorConfigs map[string][]PostProcessorConfig) (*ScriggoEngine, error)

NewScriggoWithProfiling creates a new Scriggo template engine with profiling enabled. When profiling is enabled, Scriggo's built-in profiler collects timing data for function calls, macros, and includes during execution.

Parameters:

  • templates: All template content (entry points + snippets)
  • entryPoints: Template names to compile explicitly (others discovered via render calls)
  • customFilters: Additional filters beyond built-in ones (can be nil)
  • customFunctions: Additional global functions beyond built-in ones (can be nil)
  • postProcessorConfigs: Post-processor configurations (can be nil)

Note: Profiling adds minimal runtime overhead. Use NewScriggo for production without profiling.

With inherit_context support: Only entryPoints are compiled explicitly. Template snippets are compiled automatically when referenced via render/render_glob statements.

func (*ScriggoEngine) AppendTraces

func (e *ScriggoEngine) AppendTraces(other Engine)

AppendTraces appends traces from another engine to this engine's trace buffer. This is useful for aggregating traces from multiple worker engines.

func (*ScriggoEngine) DisableFilterDebug

func (e *ScriggoEngine) DisableFilterDebug()

DisableFilterDebug disables detailed filter operation logging.

func (*ScriggoEngine) DisableTracing

func (e *ScriggoEngine) DisableTracing()

DisableTracing disables template execution tracing.

func (*ScriggoEngine) EnableFilterDebug

func (e *ScriggoEngine) EnableFilterDebug()

EnableFilterDebug enables detailed filter operation logging.

func (*ScriggoEngine) EnableTracing

func (e *ScriggoEngine) EnableTracing()

EnableTracing enables template execution tracing.

func (*ScriggoEngine) EngineType

func (e *ScriggoEngine) EngineType() EngineType

EngineType returns the type of this engine.

func (*ScriggoEngine) GetProfilingResults

func (e *ScriggoEngine) GetProfilingResults() []ProfilingEntry

GetProfilingResults returns profiling data from the last render operation. Returns nil if profiling is disabled or no render has occurred. The results contain include/render call records from Scriggo's built-in profiler.

func (*ScriggoEngine) GetRawTemplate

func (e *ScriggoEngine) GetRawTemplate(templateName string) (string, error)

GetRawTemplate returns the original template string for the given name.

func (*ScriggoEngine) GetTraceOutput

func (e *ScriggoEngine) GetTraceOutput() string

GetTraceOutput returns accumulated trace output and clears the buffer.

func (*ScriggoEngine) HasTemplate

func (e *ScriggoEngine) HasTemplate(templateName string) bool

HasTemplate checks if a template with the given name exists.

func (*ScriggoEngine) IsFilterDebugEnabled

func (e *ScriggoEngine) IsFilterDebugEnabled() bool

IsFilterDebugEnabled returns true if filter debug logging is currently enabled.

func (*ScriggoEngine) IsProfilingEnabled

func (e *ScriggoEngine) IsProfilingEnabled() bool

IsProfilingEnabled returns whether profiling is enabled for this engine.

func (*ScriggoEngine) IsTracingEnabled

func (e *ScriggoEngine) IsTracingEnabled() bool

IsTracingEnabled returns whether tracing is currently enabled.

func (*ScriggoEngine) Render

func (e *ScriggoEngine) Render(templateName string, templateContext map[string]interface{}) (string, error)

Render executes a template with the given context and returns the output.

func (*ScriggoEngine) RenderWithProfiling

func (e *ScriggoEngine) RenderWithProfiling(templateName string, templateContext map[string]interface{}) (string, []IncludeStats, error)

RenderWithProfiling renders a template and returns profiling statistics.

When profiling is enabled (via NewScriggoWithProfiling), this method returns aggregated include timing statistics. When profiling is disabled, returns nil for the stats slice.

func (*ScriggoEngine) TemplateCount

func (e *ScriggoEngine) TemplateCount() int

TemplateCount returns the number of templates in the engine.

func (*ScriggoEngine) TemplateNames

func (e *ScriggoEngine) TemplateNames() []string

TemplateNames returns the names of all available templates, sorted alphabetically.

type SharedContext

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

SharedContext provides thread-safe caching with compute-once semantics. It is used for sharing data between parallel template renders within a single reconciliation cycle.

The API is intentionally minimal to prevent race conditions:

  • ComputeIfAbsent stores values atomically (uses singleflight)
  • Get provides read-only access to existing values
  • No Set method exists - prevents racy check-then-act patterns
  • The wasComputed return value enables deduplication (FirstSeen pattern)

func NewSharedContext

func NewSharedContext() *SharedContext

NewSharedContext creates a new thread-safe shared context.

func (*SharedContext) ComputeIfAbsent

func (s *SharedContext) ComputeIfAbsent(key string, compute func() interface{}) (interface{}, bool)

ComputeIfAbsent returns the value for key, computing it if not present. Uses singleflight to ensure only one goroutine computes for a given key. Other goroutines waiting for the same key will receive the computed result.

Returns (value, wasComputed) where:

  • value: the stored value (either existing or newly computed)
  • wasComputed: true only for the goroutine that actually ran compute()

Use wasComputed for deduplication (FirstSeen pattern):

_, wasFirst := shared.ComputeIfAbsent("seen:"+key, func() any { return true })
if wasFirst {
    // First occurrence
}

Use with nil fallback for read-only access:

val, _ := shared.ComputeIfAbsent("key", func() any { return nil })

IMPORTANT: The compute function is called WITHOUT holding the mutex, so it may safely call ComputeIfAbsent for other keys (nested/recursive calls).

func (*SharedContext) Get

func (s *SharedContext) Get(key string) interface{}

Get returns the value for key, or nil if not found. This is a read-only operation - use ComputeIfAbsent for initialization.

type SortDebugger

type SortDebugger interface {
	// IsFilterDebugEnabled returns true if filter debug logging is enabled.
	IsFilterDebugEnabled() bool
}

SortDebugger provides debug logging capability for sort operations. This interface decouples sorting logic from engine-specific tracing implementations.

type TemplateNotFoundError

type TemplateNotFoundError struct {
	// TemplateName is the name of the requested template
	TemplateName string

	// AvailableTemplates lists all available template names
	AvailableTemplates []string
}

TemplateNotFoundError represents a request for a non-existent template.

func NewTemplateNotFoundError

func NewTemplateNotFoundError(templateName string, availableTemplates []string) *TemplateNotFoundError

NewTemplateNotFoundError creates a TemplateNotFoundError with the list of available templates.

func (*TemplateNotFoundError) Error

func (e *TemplateNotFoundError) Error() string

Error implements the error interface.

type UnsupportedEngineError

type UnsupportedEngineError struct {
	// EngineType is the unsupported engine type
	EngineType EngineType
}

UnsupportedEngineError represents an unsupported template engine type.

func NewUnsupportedEngineError

func NewUnsupportedEngineError(engineType EngineType) *UnsupportedEngineError

NewUnsupportedEngineError creates an UnsupportedEngineError for an invalid engine type.

func (*UnsupportedEngineError) Error

func (e *UnsupportedEngineError) Error() string

Error implements the error interface.

Jump to

Keyboard shortcuts

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