github.com/koderover/helm@v2.17.0+incompatible/docs/chart_template_guide/control_structures.md (about) 1 # Flow Control 2 3 Control structures (called "actions" in template parlance) provide you, the template author, with the ability to control the flow of a template's generation. Helm's template language provides the following control structures: 4 5 - `if`/`else` for creating conditional blocks 6 - `with` to specify a scope 7 - `range`, which provides a "for each"-style loop 8 9 In addition to these, it provides a few actions for declaring and using named template segments: 10 11 - `define` declares a new named template inside of your template 12 - `template` imports a named template 13 - `block` declares a special kind of fillable template area 14 15 In this section, we'll talk about `if`, `with`, and `range`. The others are covered in the "Named Templates" section later in this guide. 16 17 ## If/Else 18 19 The first control structure we'll look at is for conditionally including blocks of text in a template. This is the `if`/`else` block. 20 21 The basic structure for a conditional looks like this: 22 23 ```yaml 24 {{ if PIPELINE }} 25 # Do something 26 {{ else if OTHER PIPELINE }} 27 # Do something else 28 {{ else }} 29 # Default case 30 {{ end }} 31 ``` 32 33 Notice that we're now talking about _pipelines_ instead of values. The reason for this is to make it clear that control structures can execute an entire pipeline, not just evaluate a value. 34 35 A pipeline is evaluated as _false_ if the value is: 36 37 - a boolean false 38 - a numeric zero 39 - an empty string 40 - a `nil` (empty or null) 41 - an empty collection (`map`, `slice`, `tuple`, `dict`, `array`) 42 43 In any other case, the condition is evaluated to _true_ and the pipeline is executed. 44 45 Let's add a simple conditional to our ConfigMap. We'll add another setting if the drink is set to coffee: 46 47 ```yaml 48 apiVersion: v1 49 kind: ConfigMap 50 metadata: 51 name: {{ .Release.Name }}-configmap 52 data: 53 myvalue: "Hello World" 54 drink: {{ .Values.favorite.drink | default "tea" | quote }} 55 food: {{ .Values.favorite.food | upper | quote }} 56 {{ if and .Values.favorite.drink (eq .Values.favorite.drink "coffee") }}mug: true{{ end }} 57 ``` 58 59 Note that `.Values.favorite.drink` must be defined or else it will throw an error when comparing it to "coffee". Since we commented out `drink: coffee` in our last example, the output should not include a `mug: true` flag. But if we add that line back into our `values.yaml` file, the output should look like this: 60 61 ```yaml 62 # Source: mychart/templates/configmap.yaml 63 apiVersion: v1 64 kind: ConfigMap 65 metadata: 66 name: eyewitness-elk-configmap 67 data: 68 myvalue: "Hello World" 69 drink: "coffee" 70 food: "PIZZA" 71 mug: true 72 ``` 73 74 ## Controlling Whitespace 75 76 While we're looking at conditionals, we should take a quick look at the way whitespace is controlled in templates. Let's take the previous example and format it to be a little easier to read: 77 78 ``` 79 apiVersion: v1 80 kind: ConfigMap 81 metadata: 82 name: {{ .Release.Name }}-configmap 83 data: 84 myvalue: "Hello World" 85 drink: {{ .Values.favorite.drink | default "tea" | quote }} 86 food: {{ .Values.favorite.food | upper | quote }} 87 {{if eq .Values.favorite.drink "coffee"}} 88 mug: true 89 {{end}} 90 ``` 91 92 Initially, this looks good. But if we run it through the template engine, we'll get an unfortunate result: 93 94 ```console 95 $ helm install --dry-run --debug ./mychart 96 SERVER: "localhost:44134" 97 CHART PATH: /Users/mattbutcher/Code/Go/src/k8s.io/helm/_scratch/mychart 98 Error: YAML parse error on mychart/templates/configmap.yaml: error converting YAML to JSON: yaml: line 9: did not find expected key 99 ``` 100 101 What happened? We generated incorrect YAML because of the whitespacing above. 102 103 ```yaml 104 # Source: mychart/templates/configmap.yaml 105 apiVersion: v1 106 kind: ConfigMap 107 metadata: 108 name: eyewitness-elk-configmap 109 data: 110 myvalue: "Hello World" 111 drink: "coffee" 112 food: "PIZZA" 113 mug: true 114 ``` 115 116 `mug` is incorrectly indented. Let's simply out-dent that one line, and re-run: 117 118 ```yaml 119 apiVersion: v1 120 kind: ConfigMap 121 metadata: 122 name: {{ .Release.Name }}-configmap 123 data: 124 myvalue: "Hello World" 125 drink: {{ .Values.favorite.drink | default "tea" | quote }} 126 food: {{ .Values.favorite.food | upper | quote }} 127 {{if eq .Values.favorite.drink "coffee"}} 128 mug: true 129 {{end}} 130 ``` 131 132 When we sent that, we'll get YAML that is valid, but still looks a little funny: 133 134 ```yaml 135 # Source: mychart/templates/configmap.yaml 136 apiVersion: v1 137 kind: ConfigMap 138 metadata: 139 name: telling-chimp-configmap 140 data: 141 myvalue: "Hello World" 142 drink: "coffee" 143 food: "PIZZA" 144 145 mug: true 146 147 ``` 148 149 Notice that we received a few empty lines in our YAML. Why? When the template engine runs, it _removes_ the contents inside of `{{` and `}}`, but it leaves the remaining whitespace exactly as is. 150 151 YAML ascribes meaning to whitespace, so managing the whitespace becomes pretty important. Fortunately, Helm templates have a few tools to help. 152 153 First, the curly brace syntax of template declarations can be modified with special characters to tell the template engine to chomp whitespace. `{{- ` (with the 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!_ 154 155 > 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". 156 157 Using this syntax, we can modify our template to get rid of those new lines: 158 159 ```yaml 160 apiVersion: v1 161 kind: ConfigMap 162 metadata: 163 name: {{ .Release.Name }}-configmap 164 data: 165 myvalue: "Hello World" 166 drink: {{ .Values.favorite.drink | default "tea" | quote }} 167 food: {{ .Values.favorite.food | upper | quote }} 168 {{- if eq .Values.favorite.drink "coffee"}} 169 mug: true 170 {{- end}} 171 ``` 172 173 Just for the sake of making this point clear, let's adjust the above, and substitute an `*` for each whitespace that will be deleted following this rule. an `*` at the end of the line indicates a newline character that would be removed 174 175 ```yaml 176 apiVersion: v1 177 kind: ConfigMap 178 metadata: 179 name: {{ .Release.Name }}-configmap 180 data: 181 myvalue: "Hello World" 182 drink: {{ .Values.favorite.drink | default "tea" | quote }} 183 food: {{ .Values.favorite.food | upper | quote }}* 184 **{{- if eq .Values.favorite.drink "coffee"}} 185 mug: true* 186 **{{- end}} 187 188 ``` 189 190 Keeping that in mind, we can run our template through Helm and see the result: 191 192 ```yaml 193 # Source: mychart/templates/configmap.yaml 194 apiVersion: v1 195 kind: ConfigMap 196 metadata: 197 name: clunky-cat-configmap 198 data: 199 myvalue: "Hello World" 200 drink: "coffee" 201 food: "PIZZA" 202 mug: true 203 ``` 204 205 Be careful with the chomping modifiers. It is easy to accidentally do things like this: 206 207 ```yaml 208 food: {{ .Values.favorite.food | upper | quote }} 209 {{- if eq .Values.favorite.drink "coffee" -}} 210 mug: true 211 {{- end -}} 212 213 ``` 214 215 That will produce `food: "PIZZA"mug:true` because it consumed newlines on both sides. 216 217 > For the details on whitespace control in templates, see the [Official Go template documentation](https://godoc.org/text/template) 218 219 Finally, sometimes it's easier to tell the template system how to indent for you instead 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"}}`). 220 221 ## Modifying scope using `with` 222 223 The next control structure to look at is the `with` action. This controls variable scoping. Recall that `.` is a reference to _the current scope_. So `.Values` tells the template to find the `Values` object in the current scope. 224 225 The syntax for `with` is similar to a simple `if` statement: 226 227 ```yaml 228 {{ with PIPELINE }} 229 # restricted scope 230 {{ end }} 231 ``` 232 233 Scopes can be changed. `with` can allow you to set the current scope (`.`) to a particular object. For example, we've been working with `.Values.favorites`. Let's rewrite our ConfigMap to alter the `.` scope to point to `.Values.favorites`: 234 235 ```yaml 236 apiVersion: v1 237 kind: ConfigMap 238 metadata: 239 name: {{ .Release.Name }}-configmap 240 data: 241 myvalue: "Hello World" 242 {{- with .Values.favorite }} 243 drink: {{ .drink | default "tea" | quote }} 244 food: {{ .food | upper | quote }} 245 {{- end }} 246 ``` 247 248 (Note that we removed the `if` conditional from the previous exercise) 249 250 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 }}`. 251 252 But here's a note of caution! Inside of the restricted scope, you will not be able to access the other objects from the parent scope. This, for example, will fail: 253 254 ```yaml 255 {{- with .Values.favorite }} 256 drink: {{ .drink | default "tea" | quote }} 257 food: {{ .food | upper | quote }} 258 release: {{ .Release.Name }} 259 {{- end }} 260 ``` 261 262 It will produce an error because `Release.Name` is not inside of the restricted scope for `.`. However, if we swap the last two lines, all will work as expected because the scope is reset after `{{end}}`. 263 264 ```yaml 265 {{- with .Values.favorite }} 266 drink: {{ .drink | default "tea" | quote }} 267 food: {{ .food | upper | quote }} 268 {{- end }} 269 release: {{ .Release.Name }} 270 ``` 271 272 After looking at `range`, we will take a look at template variables, which offers one solution to the scoping issue above. 273 274 ## Looping with the `range` action 275 276 Many programming languages have support for looping using `for` loops, `foreach` loops, or similar functional mechanisms. In Helm's template language, the way to iterate through a collection is to use the `range` operator. 277 278 To start, let's add a list of pizza toppings to our `values.yaml` file: 279 280 ```yaml 281 favorite: 282 drink: coffee 283 food: pizza 284 pizzaToppings: 285 - mushrooms 286 - cheese 287 - peppers 288 - onions 289 ``` 290 291 Now we have a list (called a `slice` in templates) of `pizzaToppings`. We can modify our template to print this list into our ConfigMap: 292 293 ```yaml 294 apiVersion: v1 295 kind: ConfigMap 296 metadata: 297 name: {{ .Release.Name }}-configmap 298 data: 299 myvalue: "Hello World" 300 {{- with .Values.favorite }} 301 drink: {{ .drink | default "tea" | quote }} 302 food: {{ .food | upper | quote }} 303 {{- end }} 304 toppings: |- 305 {{- range .Values.pizzaToppings }} 306 - {{ . | title | quote }} 307 {{- end }} 308 309 ``` 310 311 Let's take a closer look at the `toppings:` list. The `range` function will "range over" (iterate through) the `pizzaToppings` list. But now something interesting happens. Just like `with` sets the scope of `.`, so does a `range` operator. 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 is set to `cheese`, and so on. 312 313 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 to `quote`. If we run this template, the output will be: 314 315 ```yaml 316 # Source: mychart/templates/configmap.yaml 317 apiVersion: v1 318 kind: ConfigMap 319 metadata: 320 name: edgy-dragonfly-configmap 321 data: 322 myvalue: "Hello World" 323 drink: "coffee" 324 food: "PIZZA" 325 toppings: |- 326 - "Mushrooms" 327 - "Cheese" 328 - "Peppers" 329 - "Onions" 330 ``` 331 332 Now, in this example we've done something tricky. The `toppings: |-` line is declaring a multi-line string. So our list of toppings is actually not a YAML list. It's a big string. Why would we do this? Because the data in ConfigMaps `data` is composed of key/value pairs, where both the key and the value are simple strings. To understand why this is the case, take a look at the [Kubernetes ConfigMap docs](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/). For us, though, this detail doesn't matter much. 333 334 > The `|-` marker in YAML takes a multi-line string. This can be a useful technique for embedding big blocks of data inside of your manifests, as exemplified here. 335 336 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 that's called just that: `list`. 337 338 ```yaml 339 sizes: |- 340 {{- range list "small" "medium" "large" }} 341 - {{ . }} 342 {{- end }} 343 ``` 344 345 The above will produce this: 346 347 ```yaml 348 sizes: |- 349 - small 350 - medium 351 - large 352 ``` 353 354 In addition to lists, `range` can be used to iterate over collections that have a key and a value (like a `map` or `dict`). We'll see how to do that in the next section when we introduce template variables.