# 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 ```go {{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 ```go 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 ```go 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 ```go 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 ```go // 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 ```go // 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: ```go // 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: ```go // 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: ```go // 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 ```go // 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 ```go // 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 ```go {{ .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: ```go // 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: ```go // 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: ```go // 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 }} ``` ```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 }} // 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