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.