Flow Control

Control structures (called “actions” in template parlance) provide you, thetemplate author, with the ability to control the flow of a template’sgeneration. Helm’s template language provides the following control structures:

  • if/else for creating conditional blocks
  • with to specify a scope
  • range, which provides a “for each”-style loopIn addition to these, it provides a few actions for declaring and using namedtemplate segments:

  • define declares a new named template inside of your template

  • template imports a named template
  • block declares a special kind of fillable template areaIn this section, we’ll talk about if, with, and range. The others arecovered in the “Named Templates” section later in this guide.

If/Else

The first control structure we’ll look at is for conditionally including blocksof text in a template. This is the if/else block.

The basic structure for a conditional looks like this:

  1. {{ if PIPELINE }}
  2. # Do something
  3. {{ else if OTHER PIPELINE }}
  4. # Do something else
  5. {{ else }}
  6. # Default case
  7. {{ end }}

Notice that we’re now talking about pipelines instead of values. The reasonfor this is to make it clear that control structures can execute an entirepipeline, not just evaluate a value.

A pipeline is evaluated as false if the value is:

  • a boolean false
  • a numeric zero
  • an empty string
  • a nil (empty or null)
  • an empty collection (map, slice, tuple, dict, array)Under all other conditions, the condition is true.

Let’s add a simple conditional to our ConfigMap. We’ll add another setting ifthe drink is set to coffee:

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: {{ .Release.Name }}-configmap
  5. data:
  6. myvalue: "Hello World"
  7. drink: {{ .Values.favorite.drink | default "tea" | quote }}
  8. food: {{ .Values.favorite.food | upper | quote }}
  9. {{ if eq .Values.favorite.drink "coffee" }}mug: true{{ end }}

Since we commented out drink: coffee in our last example, the output shouldnot include a mug: true flag. But if we add that line back into ourvalues.yaml file, the output should look like this:

  1. # Source: mychart/templates/configmap.yaml
  2. apiVersion: v1
  3. kind: ConfigMap
  4. metadata:
  5. name: eyewitness-elk-configmap
  6. data:
  7. myvalue: "Hello World"
  8. drink: "coffee"
  9. food: "PIZZA"
  10. mug: true

Controlling Whitespace

While we’re looking at conditionals, we should take a quick look at the waywhitespace is controlled in templates. Let’s take the previous example andformat it to be a little easier to read:

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: {{ .Release.Name }}-configmap
  5. data:
  6. myvalue: "Hello World"
  7. drink: {{ .Values.favorite.drink | default "tea" | quote }}
  8. food: {{ .Values.favorite.food | upper | quote }}
  9. {{ if eq .Values.favorite.drink "coffee" }}
  10. mug: true
  11. {{ end }}

Initially, this looks good. But if we run it through the template engine, we’llget an unfortunate result:

  1. $ helm install --dry-run --debug ./mychart
  2. SERVER: "localhost:44134"
  3. CHART PATH: /Users/mattbutcher/Code/Go/src/helm.sh/helm/_scratch/mychart
  4. Error: YAML parse error on mychart/templates/configmap.yaml: error converting YAML to JSON: yaml: line 9: did not find expected key

What happened? We generated incorrect YAML because of the whitespacing above.

  1. # Source: mychart/templates/configmap.yaml
  2. apiVersion: v1
  3. kind: ConfigMap
  4. metadata:
  5. name: eyewitness-elk-configmap
  6. data:
  7. myvalue: "Hello World"
  8. drink: "coffee"
  9. food: "PIZZA"
  10. mug: true

mug is incorrectly indented. Let’s simply out-dent that one line, and re-run:

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: {{ .Release.Name }}-configmap
  5. data:
  6. myvalue: "Hello World"
  7. drink: {{ .Values.favorite.drink | default "tea" | quote }}
  8. food: {{ .Values.favorite.food | upper | quote }}
  9. {{ if eq .Values.favorite.drink "coffee" }}
  10. mug: true
  11. {{ end }}

When we sent that, we’ll get YAML that is valid, but still looks a little funny:

  1. # Source: mychart/templates/configmap.yaml
  2. apiVersion: v1
  3. kind: ConfigMap
  4. metadata:
  5. name: telling-chimp-configmap
  6. data:
  7. myvalue: "Hello World"
  8. drink: "coffee"
  9. food: "PIZZA"
  10. mug: true

Notice that we received a few empty lines in our YAML. Why? When the templateengine runs, it removes the contents inside of {{ and }}, but it leavesthe remaining whitespace exactly as is.

YAML ascribes meaning to whitespace, so managing the whitespace becomes prettyimportant. Fortunately, Helm templates have a few tools to help.

First, the curly brace syntax of template declarations can be modified withspecial characters to tell the template engine to chomp whitespace. {{- (withthe dash and space added) indicates that whitespace should be chomped left,while -}} means whitespace to the right should be consumed. Be careful!Newlines are whitespace!

Make sure there is a space between the - and the rest of your directive.{{- 3 }} means “trim left whitespace and print 3” while {{-3 }} means“print -3”.

Using this syntax, we can modify our template to get rid of those new lines:

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: {{ .Release.Name }}-configmap
  5. data:
  6. myvalue: "Hello World"
  7. drink: {{ .Values.favorite.drink | default "tea" | quote }}
  8. food: {{ .Values.favorite.food | upper | quote }}
  9. {{- if eq .Values.favorite.drink "coffee" }}
  10. mug: true
  11. {{- end }}

Just for the sake of making this point clear, let’s adjust the above, andsubstitute an for each whitespace that will be deleted following this rule.an at the end of the line indicates a newline character that would beremoved

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: {{ .Release.Name }}-configmap
  5. data:
  6. myvalue: "Hello World"
  7. drink: {{ .Values.favorite.drink | default "tea" | quote }}
  8. food: {{ .Values.favorite.food | upper | quote }}*
  9. **{{- if eq .Values.favorite.drink "coffee" }}
  10. mug: true*
  11. **{{- end }}

Keeping that in mind, we can run our template through Helm and see the result:

  1. # Source: mychart/templates/configmap.yaml
  2. apiVersion: v1
  3. kind: ConfigMap
  4. metadata:
  5. name: clunky-cat-configmap
  6. data:
  7. myvalue: "Hello World"
  8. drink: "coffee"
  9. food: "PIZZA"
  10. mug: true

Be careful with the chomping modifiers. It is easy to accidentally do thingslike this:

  1. food: {{ .Values.favorite.food | upper | quote }}
  2. {{- if eq .Values.favorite.drink "coffee" -}}
  3. mug: true
  4. {{- end -}}

That will produce food: "PIZZA"mug:true because it consumed newlines on bothsides.

For the details on whitespace control in templates, see the Official Gotemplate documentation

Finally, sometimes it’s easier to tell the template system how to indent for youinstead of trying to master the spacing of template directives. For that reason,you may sometimes find it useful to use the indent function ({{ indent 2"mug:true" }}).

Modifying scope using with

The next control structure to look at is the with action. This controlsvariable scoping. Recall that . is a reference to the current scope. So.Values tells the template to find the Values object in the current scope.

The syntax for with is similar to a simple if statement:

  1. {{ with PIPELINE }}
  2. # restricted scope
  3. {{ end }}

Scopes can be changed. with can allow you to set the current scope (.) to aparticular object. For example, we’ve been working with .Values.favorites.Let’s rewrite our ConfigMap to alter the . scope to point to.Values.favorites:

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: {{ .Release.Name }}-configmap
  5. data:
  6. myvalue: "Hello World"
  7. {{- with .Values.favorite }}
  8. drink: {{ .drink | default "tea" | quote }}
  9. food: {{ .food | upper | quote }}
  10. {{- end }}

(Note that we removed the if conditional from the previous exercise)

Notice that now we can reference .drink and .food without qualifying them.That is because the with statement sets . to point to .Values.favorite.The . is reset to its previous scope after {{ end }}.

But here’s a note of caution! Inside of the restricted scope, you will not beable to access the other objects from the parent scope. This, for example, willfail:

  1. {{- with .Values.favorite }}
  2. drink: {{ .drink | default "tea" | quote }}
  3. food: {{ .food | upper | quote }}
  4. release: {{ .Release.Name }}
  5. {{- end }}

It will produce an error because Release.Name is not inside of the restrictedscope for .. However, if we swap the last two lines, all will work as expectedbecause the scope is reset after {{ end }}.

  1. {{- with .Values.favorite }}
  2. drink: {{ .drink | default "tea" | quote }}
  3. food: {{ .food | upper | quote }}
  4. {{- end }}
  5. release: {{ .Release.Name }}

After looking a range, we will take a look at template variables, which offerone solution to the scoping issue above.

Looping with the range action

Many programming languages have support for looping using for loops, foreachloops, or similar functional mechanisms. In Helm’s template language, the way toiterate through a collection is to use the range operator.

To start, let’s add a list of pizza toppings to our values.yaml file:

  1. favorite:
  2. drink: coffee
  3. food: pizza
  4. pizzaToppings:
  5. - mushrooms
  6. - cheese
  7. - peppers
  8. - onions

Now we have a list (called a slice in templates) of pizzaToppings. We canmodify our template to print this list into our ConfigMap:

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: {{ .Release.Name }}-configmap
  5. data:
  6. myvalue: "Hello World"
  7. {{- with .Values.favorite }}
  8. drink: {{ .drink | default "tea" | quote }}
  9. food: {{ .food | upper | quote }}
  10. {{- end }}
  11. toppings: |-
  12. {{- range .Values.pizzaToppings }}
  13. - {{ . | title | quote }}
  14. {{- end }}

Let’s take a closer look at the toppings: list. The range function will“range over” (iterate through) the pizzaToppings list. But now somethinginteresting happens. Just like with sets the scope of ., so does a rangeoperator. Each time through the loop, . is set to the current pizza topping.That is, the first time, . is set to mushrooms. The second iteration it isset to cheese, and so on.

We can send the value of . directly down a pipeline, so when we do {{ . |title | quote }}, it sends . to title (title case function) and then toquote. If we run this template, the output will be:

  1. # Source: mychart/templates/configmap.yaml
  2. apiVersion: v1
  3. kind: ConfigMap
  4. metadata:
  5. name: edgy-dragonfly-configmap
  6. data:
  7. myvalue: "Hello World"
  8. drink: "coffee"
  9. food: "PIZZA"
  10. toppings: |-
  11. - "Mushrooms"
  12. - "Cheese"
  13. - "Peppers"
  14. - "Onions"

Now, in this example we’ve done something tricky. The toppings: |- line isdeclaring a multi-line string. So our list of toppings is actually not a YAMLlist. It’s a big string. Why would we do this? Because the data in ConfigMapsdata is composed of key/value pairs, where both the key and the value aresimple strings. To understand why this is the case, take a look at theKubernetes ConfigMap docs.For us, though, this detail doesn’t matter much.

The |- marker in YAML takes a multi-line string. This can be a usefultechnique for embedding big blocks of data inside of your manifests, asexemplified here.

Sometimes it’s useful to be able to quickly make a list inside of your template,and then iterate over that list. Helm templates have a function to make thiseasy: tuple. In computer science, a tuple is a list-like collection of fixedsize, but with arbitrary data types. This roughly conveys the way a tuple isused.

  1. sizes: |-
  2. {{- range tuple "small" "medium" "large" }}
  3. - {{ . }}
  4. {{- end }}

The above will produce this:

  1. sizes: |-
  2. - small
  3. - medium
  4. - large

In addition to lists and tuples, range can be used to iterate over collectionsthat have a key and a value (like a map or dict). We’ll see how to do thatin the next section when we introduce template variables.