Templating
The templating mechanism in werf is the same as in Helm. It uses the Go text/template template engine, enhanced with the Sprig and Helm feature set.
Template files
Template files are located in the templates
directory of the chart.
The templates/*.yaml
files are used to generate final, deployment-ready Kubernetes manifests. Each of these files can be used to generate multiple Kubernetes resource manifests. For this, insert a ---
separator between the manifests.
The templates/_*.tpl
files only contain named templates for using in other files. Kubernetes manifests cannot be generated using the `*.tpl’ files alone.
Actions
Actions is the key element of the templating process. Actions can only return strings. The action must be wrapped in double curly braces:
{{ print "hello" }}
Output:
hello
Variables
Variables are used to store or refer to data of any type.
This is how you can declare a variable and assign a value to it:
{{ $myvar := "hello" }}
This is how you can assign a new value to an existing variable:
{{ $myvar = "helloworld" }}
Here’s an example of how to use a variable:
{{ $myvar }}
Output:
helloworld
Here’s how to use predefined variables:
{{ $.Values.werf.env }}
You can also substitute the data without first declaring a variable:
labels:
app: {{ "myapp" }}
Output:
labels:
app: myapp
You can also store function or pipeline results in variables:
{{ $myvar := 1 | add 1 1 }}
{{ $myvar }}
Output:
3
Variable scope
Scope limits the visibility of variables. By default, the scope is limited to the template file.
The scope can change for some blocks and functions. For example, the if
statement creates a different scope, and the variables declared in the if
statement will not be accessible outside it:
{{ if true }}
{{ $myvar := "hello" }}
{{ end }}
{{ $myvar }}
Output:
Error: ... undefined variable "$myvar"
To get around this limitation, declare the variable outside the statement and assign a value to it inside the statement:
{{ $myvar := "" }}
{{ if true }}
{{ $myvar = "hello" }}
{{ end }}
{{ $myvar }}
Output:
hello
Data types
Available data types:
Data type | Example |
---|---|
Boolean | {{ true }} |
String | {{ "hello" }} |
Integer | {{ 1 }} |
Floating-point number | {{ 1.1 }} |
List with elements of any type (ordered) | {{ list 1 2 3 }} |
Dictionary with string keys and values of any type (unordered) | {{ dict "key1" 1 "key2" 2 }} |
Special objects | {{ $.Files }} |
Null | {{ nil }} |
Functions
werf has an extensive library of functions for use in templates. Most of them are Helm functions.
Functions can only be used in actions. Functions can have arguments and can return data of any type. For example, the add function below takes three numeric arguments and returns a number:
{{ add 3 2 1 }}
Output:
6
Note that the action result is always converted to a string regardless of the data type returned by the function.
A function may have arguments of the following types:
-
regular values:
1
-
calls of other functions:
add 1 1
-
pipes:
1 | add 1
-
combination of the above types:
1 | add (add 1 1)
Put the argument in parentheses ()
if it is a call to another function or pipeline:
{{ add 3 (add 1 1) (1 | add 1) }}
To ignore the result returned by the function, simply assign it to the $_
variable:
{{ $_ := set $myDict "mykey" "myvalue"}}
Pipelines
Pipelines allow you to pass the result of the first function as the last argument to the second function, and the result of the second function as the last argument to the third function, and so on:
{{ now | unixEpoch | quote }}
Here, the result of the now
function (gets the current date) is passed as an argument to the unixEpoch
function (converts the date to Unix time). The resulting value is then passed to the quote
function (adds quotation marks).
Output:
"1671466310"
The use of pipelines is optional; you can rewrite them as follows:
{{ quote (unixEpoch (now)) }}
… however, we recommend using the pipelines.
Logic gates and comparisons
The following logic gates are available:
Operation | Function | Example |
---|---|---|
Not | not <arg> |
{{ not false }} |
And | and <arg> <arg> [<arg>, ...] |
{{ and true true }} |
Or | or <arg> <arg> [<arg>, ...] |
{{ or false true }} |
The following comparison operators are available:
comparison | Function | Example |
---|---|---|
Equal | eq <arg> <arg> [<arg>, ...] |
{{ eq "hello" "hello" }} |
Not equal | neq <arg> <arg> [<arg>, ...] |
{{ neq "hello" "world" }} |
Less than | lt <arg> <arg> |
{{ lt 1 2 }} |
Greater than | gt <arg> <arg> |
{{ gt 2 1 }} |
Less than or equal | le <arg> <arg> |
{{ le 1 2 }} |
Greater than or equal | ge <arg> <arg> |
{{ ge 2 1 }} |
Example of combining various operators
{{ and (eq true true) (neq true false) (not (empty "hello")) }}
Conditionals
The if/else
conditionals allows to perform templating only if specific conditions are met/not met, for example:
{{ if $.Values.app.enabled }}
# ...
{{ end }}
A condition is considered failed if the result of its calculation is either of:
-
boolean
false
; -
zero
0
; -
an empty string
""
; -
an empty list
[]
; -
an empty dictionary
{}
; -
null:
nil
.
In all other cases the condition is considered satisfied. A condition may include data, a variable, a function, or a pipeline.
Example:
{{ if eq $appName "backend" }}
app: mybackend
{{ else if eq $appName "frontend" }}
app: myfrontend
{{ else }}
app: {{ $appName }}
{{ end }}
Simple conditionals can be implemented not only with if/else
, but also with the ternary
function. For example, the following ternary
expression:
{{ ternary "mybackend" $appName (eq $appName "backend") }}
… is similar to the `if/else’ construction below:
{{ if eq $appName "backend" }}
app: mybackend
{{ else }}
app: {{ $appName }}
{{ end }}
Cycles
Cycling through lists
The range
cycles allow you to cycle through the list items and do the necessary templating at each iteration:
{{ range $urls }}
{{ . }}
{{ end }}
Output:
https://example.org
https://sub.example.org
The .
relative context always points to the list element that corresponds to the current iteration; the pointer can also be assigned to an arbitrary variable:
{{ range $elem := $urls }}
{{ $elem }}
{{ end }}
The output is the same:
https://example.org
https://sub.example.org
Here’s how you can get the index of an element in the list:
{{ range $i, $elem := $urls }}
{{ $elem }} has an index of {{ $i }}
{{ end }}
Output:
https://example.org has an index of 0
https://sub.example.org has an index of 1
Cycling through dictionaries
The range
cycles allow you to cycle through the dictionary keys and values and do the necessary templating at each iteration:
# values.yaml:
apps:
backend:
image: openjdk
frontend:
image: node
# templates/app.yaml:
{{ range $.Values.apps }}
{{ .image }}
{{ end }}
Output:
openjdk
node
The .
relative context always points to the value of the dictionary element that corresponds to the current iteration; the pointer can also be assigned to an arbitrary variable:
{{ range $app := $.Values.apps }}
{{ $app.image }}
{{ end }}
The output is the same:
openjdk
node
Here’s how you can get the key of the dictionary element:
{{ range $appName, $app := $.Values.apps }}
{{ $appName }}: {{ $app.image }}
{{ end }}
Output:
backend: openjdk
frontend: node
Cycle control
The continue
statement allows you to skip the current cycle iteration. To give you an example, let’s skip the iteration for the https://example.org
element:
{{ range $url := $urls }}
{{ if eq $url "https://example.org" }}{{ continue }}{{ end }}
{{ $url }}
{{ end }}
In contrast, the break
statement lets you both skip the current iteration and terminate the whole cycle:
{{ range $url := $urls }}
{{ if eq $url "https://example.org" }}{{ break }}{{ end }}
{{ $url }}
{{ end }}
Context
Root context ($)
The root context is the dictionary to which the $
variable refers. You can use it to access values and some special objects. The root context has global visibility within the template file (except for the define
block and some functions).
Example of use:
{{ $.Values.mykey }}
Output:
myvalue
You can add custom keys/values to the root context. They will also be available throughout the template file:
{{ $_ := set $ "mykey" "myvalue"}}
{{ $.mykey }}
Output:
myvalue
The root context remains intact even in blocks that change the relative context (except for define
):
{{ with $.Values.backend }}
- command: {{ .command }}
image: {{ $.Values.werf.image.backend }}
{{ end }}
Functions like tpl
or include
can lose the root context. You can pass the root context as an argument to them to restore access to it:
{{ tpl "{{ .Values.mykey }}" $ }}
Output:
myvalue
Relative context (.)
The relative context is any type of data referenced by the .
variable. By default, the relative context points to the root context.
Some blocks and functions can modify the relative context. In the example below, in the first line, the relative context points to the root context $
, while in the second line, it points to $.Values.containers
:
{{ range .Values.containers }}
{{ . }}
{{ end }}
Use the with
block to modify the relative context:
{{ with $.Values.app }}
image: {{ .image }}
{{ end }}
Reusing templates
Named templates
To reuse templating, declare named templates in the define
blocks in the templates/_*.tpl
files:
# templates/_helpers.tpl:
{{ define "labels" }}
app: myapp
team: alpha
{{ end }}
Next, insert the named templates into the templates/*.(yaml|tpl)
files using the include
function:
# templates/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
selector:
matchLabels: {{ include "labels" nil | nindent 6 }}
template:
metadata:
labels: {{ include "labels" nil | nindent 8 }}
Output:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
selector:
matchLabels:
app: myapp
team: alpha
template:
metadata:
labels:
app: myapp
team: alpha
The name of the named template to use in the include
function may be dynamic:
{{ include (printf "%s.labels" $prefix) nil }}
Named templates are globally visible - once declared in a parent or any child chart, a named template becomes available in all charts at once: in both parent and child charts. Make sure there are no named templates with the same name in the parent and child charts.
Parameterizing named templates
The include
function that inserts named templates takes a single optional argument. This argument can be used to parameterize a named template, where that argument becomes the .
relative context:
{{ include "labels" "myapp" }}
{{ define "labels" }}
app: {{ . }}
{{ end }}
Output:
app: myapp
To pass several arguments at once, use a list containing multiple arguments:
{{ include "labels" (list "myapp" "alpha") }}
{{ define "labels" }}
app: {{ index . 0 }}
team: {{ index . 1 }}
{{ end }}
…or a dictionary:
{{ include "labels" (dict "app" "myapp" "team" "alpha") }}
{{ define "labels" }}
app: {{ .app }}
team: {{ .team }}
{{ end }}
Optional positional arguments can be handled as follows:
{{ include "labels" (list "myapp") }}
{{ include "labels" (list "myapp" "alpha") }}
{{ define "labels" }}
app: {{ index . 0 }}
{{ if gt (len .) 1 }}
team: {{ index . 1 }}
{{ end }}
{{ end }}
Optional non-positional arguments can be handled as follows:
{{ include "labels" (dict "app" "myapp") }}
{{ include "labels" (dict "team" "alpha" "app" "myapp") }}
{{ define "labels" }}
app: {{ .app }}
{{ if hasKey . "team" }}
team: {{ .team }}
{{ end }}
{{ end }}
Pass nil
to a named template that does not require parametrizing:
{{ include "labels" nil }}
The result of running include
The include
function that inserts a named template returns text data. To return structured data, you need to deserialize the result of include
using the fromYaml
function:
{{ define "commonLabels" }}
app: myapp
{{ end }}
{{ $labels := include "commonLabels" nil | fromYaml }}
{{ $labels.app }}
Output:
myapp
Note that
fromYaml
does not support lists. For lists, use the dedicatedfromYamlArray
function.
You can use the toYaml
and toJson
functions for data serialization, and the fromYaml/fromYamlArray
and fromJson/fromJsonArray
functions for deserialization.
Named template context
The named templates declared in templates/_*.tpl
cannot use the root and relative contexts of the file into which they are included by the include
function. You can fix this by passing the root and/or relative context as include
arguments:
{{ include "labels" $ }}
{{ include "labels" . }}
{{ include "labels" (list $ .) }}
{{ include "labels" (list $ . "myapp") }}
include in include
You can also use the include
function in the define
blocks to include named templates:
{{ define "doSomething" }}
{{ include "doSomethingElse" . }}
{{ end }}
You can even call the include
function to include a named template from this very template, i.e., recursively:
{{ define "doRecursively" }}
{{ if ... }}
{{ include "doRecursively" . }}
{{ end }}
{{ end }}
tpl templating
The tpl
function allows you to process any line in real time. It takes one argument (the root context).
In the example below, we use the tpl
function to retrieve the deployment name from the values.yaml file:
# values.yaml:
appName: "myapp"
deploymentName: "{{ .Values.appName }}-deployment"
# templates/app.yaml:
{{ tpl $.Values.deploymentName $ }}
Output:
myapp-deployment
And here’s how you can process arbitrary files that don’t support Helm templating:
{{ tpl ($.Files.Get "nginx.conf") $ }}
You can add arguments as new root context keys to the tpl
function to pass additional arguments:
{{ $_ := set $ "myarg" "myvalue"}}
{{ tpl "{{ $.myarg }}" $ }}
Indentation control
Use the nindent
function to set the indentation:
containers: {{ .Values.app.containers | nindent 6 }}
Output:
containers:
- name: backend
image: openjdk
And here’s how you can mix it with other data:
containers:
{{ .Values.app.containers | nindent 6 }}
- name: frontend
image: node
Output:
containers:
- name: backend
image: openjdk
- name: frontend
image: node
Use -
after {{
and/or before }}
to remove extra spaces before and/or after the action result, for example:
{{- "hello" -}} {{ "world" }}
Output:
helloworld
Comments
werf supports two types of comments — template comments {{ /* */ }}
} and manifest comments #
.
Template comments
The template comments are stripped off during manifest generation:
{{ /* This comment will be stripped off */ }}
app: myApp
Comments can be multi-line:
{{ /*
Hello
World
/* }}
Template actions are ignored in such comments
{{ /*
{{ print "This template action will be ignored" }}
/* }}
Manifest comments
The manifest comments are retained during manifest generation:
# This comment will stay in place
app: myApp
Only single-line comments of this type are supported:
# For multi-line comments, use several
# single-line comments in a row
The template actions encountered in them are carried out:
# {{ print "This template action will be carried out" }}
Debugging
Use werf render
to render and display ready-to-use Kubernetes manifests. The --debug
option displays manifests even if they are not valid YAML.
Here’s how you can display the variable contents:
output: {{ $appName | toYaml }}
Display the contents of a list or dictionary variable:
output: {{ $dictOrList | toYaml | nindent 2 }}
Display the variable’s data type:
output: {{ kindOf $myvar }}
Display some string and stop template rendering:
{{ fail (printf "Data type: %s" (kindOf $myvar)) }}