Go Templates Guide

Basic Syntax

Delimiters

  • {{ }} - standard evaluation
  • {{- }} - trim whitespace left
  • {{- -}} - trim whitespace both sides
  • {{ -}} - trim whitespace right

Variables and References

  • $x := value - variable assignment
  • $ - root/global context
  • . - current context
  • .Field - field access
  • $.Field - root context field access
  • $x.Field - variable field access

Pipeline and Functions

  • x | f - pipe value to function
  • | default "val" - set default value
  • | nindent N - indent N spaces with newline
  • | toYaml - convert to YAML
  • | quote - quote string

Control Structures

{{if x}} {{else}} {{end}}
{{range x}} {{end}}
{{with x}} {{end}}  // scope assignment
{{define "name"}} {{end}}  // define template
{{template "name" .}}  // include template
{{block "name" .}} {{end}}  // define with fallback

Built-in Functions

Operators

  • Comparison: eq, ne, lt, gt, le, ge
  • Boolean: and, or, not
  • Formatting: print, printf
  • Collections: len, index

Control Flow

ternary $condition "yes" "no"   // conditional value selection
coalesce $val1 $val2 $val3      // first non-empty value
required "msg" $value           // fail if value empty
fail "error message"           // explicit failure

String Operations

cat "hello" "world"            // string concatenation
join "," $slice               // join slice with separator
split "," $string            // split string into slice
trim $string                 // remove whitespace
trimAll "$" $string         // remove all occurrences
upper $string               // uppercase
lower $string               // lowercase

Collection Operations

hasKey $map "key"             // check map key existence
keys $map                     // get map keys as slice
values $map                   // get map values as slice
compact $slice               // remove empty values
uniq $slice                 // remove duplicates
reverse $slice              // reverse order
shuffle $slice              // randomize order

Scoping and Context

Scope Changes

// With block - changes context
{{ with $x := .Values }}
  {{ .property }}     // . is now $x
  {{ $.Release.Name }} // $ still refers to root
{{ end }}

// Range block - iteration context
{{ range $i, $v := .Values.list }}
  {{ $v }}           // current item
  {{ $.Release.Name }} // root context
{{ end }}

// Define block - template scope
{{ define "my.template" }}
  {{ . }}           // context passed to template
  {{ $ }}           // root of template call
{{ end }}

Template Inheritance

Define vs Block

// Define - only for creating reusable templates
{{ define "base" }}
  <h1>{{ .title }}</h1>
  {{ template "content" . }}  // content must be defined elsewhere
{{ end }}

// Block - provides default content that can be overridden
{{ block "content" . }}
  Default content here
{{ end }}

// Child template overriding a block
{{ define "child" }}
  {{ template "base" . }}
  {{ define "content" }}     // Override the content block
    Custom child content
  {{ end }}
{{ end }}

// Practical example with multiple blocks
{{ define "base" }}
  <html>
    <head>
      {{ block "title" . }}Default Title{{ end }}
      {{ block "meta" . }}
        <meta charset="utf-8">
      {{ end }}
    </head>
    <body>
      {{ block "content" . }}
        <p>Default content</p>
      {{ end }}
      {{ block "footer" . }}
        <footer>Default footer</footer>
      {{ end }}
    </body>
  </html>
{{ end }}

// Child template overriding specific blocks
{{ define "homepage" }}
  {{ template "base" . }}
  {{ define "title" }}Home - My Site{{ end }}
  {{ define "content" }}
    <h1>Welcome!</h1>
  {{ end }}
{{ end }}

Type System

Basic Types

  • string
  • bool
  • int, float64
  • nil

Collections

  • slice/array - indexed sequences
  • map - key/value store
  • struct - dot-accessible fields

Debugging Templates

Type Inspection

When debugging templates, understanding the types of values is crucial:

// Get basic type information
kindOf $x           // returns "string", "map", "slice", etc.
typeOf $x           // returns detailed Go type
typeIs "string" $x  // boolean type check

Value Inspection

Use printf with different format verbs to inspect values:

// Debug printing with different detail levels
printf "%v" $x      // basic value format
printf "%+v" $x     // adds field names for structs
printf "%#v" $x     // Go-syntax representation
printf "%T" $x      // type information

// Debugging context changes
{{ range $i, $v := .Values.list }}
  {{- printf "Index: %d, Value: %v, Type: %T" $i $v $v }}
  {{- printf "Current context: %v" . }}
  {{- printf "Root context: %v" $ }}
{{ end }}

Common Debugging Patterns

Here are some patterns for troubleshooting template issues:

// Check if map key exists before accessing
{{ if hasKey .Values "mykey" }}
  {{ printf "Found key with value: %v" .Values.mykey }}
{{ else }}
  {{ printf "Key 'mykey' not found in Values" }}
{{ end }}

// Validate nested structure
{{ if and .Values.config (hasKey .Values.config "nested") }}
  {{ printf "Nested config: %v" .Values.config.nested }}
{{ else }}
  {{ fail "Missing required nested configuration" }}
{{ end }}

// Debug type mismatches
{{ $val := .Values.someValue }}
{{ if not (typeIs "string" $val) }}
  {{ printf "Expected string, got %s (%T)" (kindOf $val) $val }}
{{ end }}

Type Operations

// Type information
kindOf $x           // basic type category
typeOf $x           // detailed Go type
typeIs "string" $x  // type checking

// Debugging
printf "%#v" $x     // detailed value format
printf "%T" $x      // type information
printf "%v" $x      // default format

// Zero/nil checking
if $x               // false for zero values
if kindIs "invalid" $x  // checks for nil

Type Conversions

// String conversions
toString 42         // "42"
print (toString .)  // convert current context

// Numeric conversions
toInt "42"         // 42
toInt64 "42"       // 42 (int64)
toFloat64 "42.1"   // 42.1

// Data structure conversions
toJson .           // convert to JSON string
fromJson "..."     // parse JSON to object
toYaml .          // convert to YAML string
fromYaml "..."    // parse YAML to object

// Bool conversion
toBool 1           // true
toBool "true"      // true
toBool "yes"       // true

Advanced Examples

Complex Pipeline Transformations

{{ .Values.data | 
  split "," |      // split into slice
  compact |        // remove empty
  uniq |          // remove duplicates
  join "," |      // rejoin
  quote           // add quotes
}}

### Handling Nested Data Structures

#### Iterating Over Nested Data
Working with deeply nested structures often requires careful handling of context and scopes. Here's how to traverse complex data safely:

```go
// Processing deeply nested maps/lists
{{ range $svcName, $svc := .Values.services }}
  {{ range $envName, $env := $svc.environments }}
    {{ range $cfgName, $cfg := $env.config }}
      // Access with full path for clarity
      {{ $value := index $.Values.services $svcName "environments" $envName "config" $cfgName }}
      
      // Or use nested context
      {{ $value := $cfg }}
    {{ end }}
  {{ end }}
{{ end }}

Accessing Values Dynamically

When you need to access nested values using dynamic paths (e.g., from configuration), you can split the path and traverse:

// Dynamic access to nested JSON/YAML
{{ $path := split "services.frontend.port" "." }}
{{ $value := $.Values }}
{{ range $path }}
  {{ $value = index $value . }}
{{ end }}
{{ $value }}  // Final value

Building Nested Structures

Sometimes you need to construct nested structures from flat data. This pattern is useful when working with configuration management:

// Building nested structures
{{ $config := dict }}
{{ range $path, $value := .Values.flat }}
  {{ $parts := split "." $path }}
  {{ $current := $config }}
  {{ range $i, $part := $parts }}
    {{ if eq (add $i 1) (len $parts) }}
      {{ $_ := set $current $part $value }}
    {{ else }}
      {{ if not (hasKey $current $part) }}
        {{ $_ := set $current $part dict }}
      {{ end }}
      {{ $current = index $current $part }}
    {{ end }}
  {{ end }}
{{ end }}

Merging Default Values

When working with configuration, you often need to merge default values with overrides while preserving nested structures:

// Handling nested defaults
{{ define "merge" }}
  {{ $default := index . 0 }}
  {{ $override := index . 1 }}
  {{ range $k, $v := $default }}
    {{ if hasKey $override $k }}
      {{ if kindIs "map" $v }}
        {{ $newV := dict }}
        {{ template "merge" (list $v (index $override $k) $newV) }}
        {{ $_ := set $override $k $newV }}
      {{ end }}
    {{ else }}
      {{ $_ := set $override $k $v }}
    {{ end }}
  {{ end }}
{{ end }}
// Processing deeply nested maps/lists
{{ range $svcName, $svc := .Values.services }}
  {{ range $envName, $env := $svc.environments }}
    {{ range $cfgName, $cfg := $env.config }}
      // Access with full path for clarity
      {{ $value := index $.Values.services $svcName "environments" $envName "config" $cfgName }}
      
      // Or use nested context
      {{ $value := $cfg }}
    {{ end }}
  {{ end }}
{{ end }}

// Dynamic access to nested JSON/YAML
{{ $path := split "services.frontend.port" "." }}
{{ $value := $.Values }}
{{ range $path }}
  {{ $value = index $value . }}
{{ end }}
{{ $value }}  // Final value

// Building nested structures
{{ $config := dict }}
{{ range $path, $value := .Values.flat }}
  {{ $parts := split "." $path }}
  {{ $current := $config }}
  {{ range $i, $part := $parts }}
    {{ if eq (add $i 1) (len $parts) }}
      {{ $_ := set $current $part $value }}
    {{ else }}
      {{ if not (hasKey $current $part) }}
        {{ $_ := set $current $part dict }}
      {{ end }}
      {{ $current = index $current $part }}
    {{ end }}
  {{ end }}
{{ end }}

// Handling nested defaults
{{ define "merge" }}
  {{ $default := index . 0 }}
  {{ $override := index . 1 }}
  {{ range $k, $v := $default }}
    {{ if hasKey $override $k }}
      {{ if kindIs "map" $v }}
        {{ $newV := dict }}
        {{ template "merge" (list $v (index $override $k) $newV) }}
        {{ $_ := set $override $k $newV }}
      {{ end }}
    {{ else }}
      {{ $_ := set $override $k $v }}
    {{ end }}
  {{ end }}
{{ end }}

### Error Handling Patterns

Error handling in Go templates provides two main mechanisms:
- `fail` - Immediately stops template execution with an error
- `required` - Checks for empty/nil values and fails with a custom message

Both can be used for validation, but `fail` gives more control over error conditions.
```go
// Basic required and fail usage
{{ required "database password is required" .Values.dbPassword }}
{{ if not .Values.apiKey }}
  {{ fail "API key must be provided" }}
{{ end }}

// Combining fail with coalesce for fallback logic
{{ $endpoint := coalesce .Values.endpoint .Values.fallbackEndpoint | required "endpoint is required" }}
{{ $timeout := coalesce .Values.timeout "30s" }}  // with default value

// Required inside nested range
{{ range .Values.services }}
  {{ $name := required "service name is required" .name }}
  {{ $port := required (printf "port is required for service %s" $name) .port }}
{{ end }}

// Complex validation with fail and coalesce
{{ $dbConfig := dict }}
{{ $dbConfig = set $dbConfig "host" (coalesce .Values.db.host "localhost") }}
{{ $dbConfig = set $dbConfig "port" (coalesce .Values.db.port 5432) }}
{{ $dbConfig = set $dbConfig "password" (required "database password is required" .Values.db.password) }}
{{ if not (hasKey $dbConfig "host") }}
  {{ fail "database host configuration is incomplete" }}
{{ end }}

// Type-safe processing with detailed error
{{- if typeIs "string" $x -}}
  {{- $num := toInt $x -}}
  {{- printf "%d" $num -}}
{{- else -}}
  {{- fail (printf "expected string value, got %s" (kindOf $x)) -}}
{{- end -}}

Key Points

  • No explicit type declarations
  • Dynamic/loose typing
  • Automatic type coercion in many cases
  • No user-defined types
  • No generics/parametric types
  • Limited error handling for type mismatches
  • Conversions return errors on failure
  • No implicit type coercion in most cases
  • Limited set of conversions available
  • toYaml/fromYaml are Helm-specific
  • Complex conversions often require multiple steps using pipes