Template Functions and Pipelines
So far, we’ve seen how to place information into a template. But that information is placed into the template unmodified. Sometimes we want to transform the supplied data in a way that makes it more usable to us.
Let’s start with a best practice: When injecting strings from the .Values
object into the template, we ought to quote these strings. We can do that by calling the quote
function in the template directive:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ quote .Values.favorite.drink }}
food: {{ quote .Values.favorite.food }}
Template functions follow the syntax functionName arg1 arg2…
. In the snippet above, quote .Values.favorite.drink
calls the quote
function and passes it a single argument.
Helm has over 60 available functions. Some of them are defined by the Go template language itself. Most of the others are part of the Sprig template library. We’ll see many of them as we progress through the examples.
While we talk about the “Helm template language” as if it is Helm-specific, it is actually a combination of the Go template language, some extra functions, and a variety of wrappers to expose certain objects to the templates. Many resources on Go templates may be helpful as you learn about templating.
Pipelines
One of the powerful features of the template language is its concept of pipelines. Drawing on a concept from UNIX, pipelines are a tool for chaining together a series of template commands to compactly express a series of transformations. In other words, pipelines are an efficient way of getting several things done in sequence. Let’s rewrite the above example using a pipeline.
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ .Values.favorite.drink | quote }}
food: {{ .Values.favorite.food | quote }}
In this example, instead of calling quote ARGUMENT
, we inverted the order. We “sent” the argument to the function using a pipeline (|
): .Values.favorite.drink | quote
. Using pipelines, we can chain several functions together:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ .Values.favorite.drink | quote }}
food: {{ .Values.favorite.food | upper | quote }}
Inverting the order is a common practice in templates. You will see
.val | quote
more often thanquote .val
. Either practice is fine.
When evaluated, that template will produce this:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: trendsetting-p-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
Note that our original pizza
has now been transformed to "PIZZA"
.
When pipelining arguments like this, the result of the first evaluation (.Values.favorite.drink
) is sent as the last argument to the function. We can modify the drink example above to illustrate with a function that takes two arguments: repeat COUNT STRING
:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ .Values.favorite.drink | repeat 5 | quote }}
food: {{ .Values.favorite.food | upper | quote }}
The repeat
function will echo the given string the given number of times, so we will get this for output:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: melting-porcup-configmap
data:
myvalue: "Hello World"
drink: "coffeecoffeecoffeecoffeecoffee"
food: "PIZZA"
Using the default function
One function frequently used in templates is the default
function: default DEFAULT_VALUE GIVEN_VALUE
. This function allows you to specify a default value inside of the template, in case the value is omitted. Let’s use it to modify the drink example above:
drink: {{ .Values.favorite.drink | default "tea" | quote }}
If we run this as normal, we’ll get our coffee
:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: virtuous-mink-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
Now, we will remove the favorite drink setting from values.yaml
:
favorite:
#drink: coffee
food: pizza
Now re-running helm install —dry-run —debug ./mychart
will produce this YAML:
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: fair-worm-configmap
data:
myvalue: "Hello World"
drink: "tea"
food: "PIZZA"
In an actual chart, all static default values should live in the values.yaml, and should not be repeated using the default
command (otherwise they would be redundant). However, the default
command is perfect for computed values, which can not be declared inside values.yaml. For example:
drink: {{ .Values.favorite.drink | default (printf "%s-tea" (include "fullname" .)) }}
In some places, an if
conditional guard may be better suited than default
. We’ll see those in the next section.
Template functions and pipelines are a powerful way to transform information and then insert it into your YAML. But sometimes it’s necessary to add some template logic that is a little more sophisticated than just inserting a string. In the next section we will look at the control structures provided by the template language.
Operators are functions
Operators are implemented as functions that return a boolean value. To use eq
, ne
, lt
, gt
, and
, or
, not
etcetera place the operator at the front of the statement followed by its parameters just as you would a function. To chain multiple operations together, separate individual functions by surrounding them with parentheses.
{{/* include the body of this if statement when the variable .Values.fooString exists and is set to "foo" */}}
{{ if and .Values.fooString (eq .Values.fooString "foo") }}
{{ ... }}
{{ end }}
{{/* include the body of this if statement when the variable .Values.anUnsetVariable is set or .values.aSetVariable is not set */}}
{{ if or .Values.anUnsetVariable (not .Values.aSetVariable) }}
{{ ... }}
{{ end }}
Now we can turn from functions and pipelines to flow control with conditions, loops, and scope modifiers.