Helm Flow Control and Conditionals

Helm Flow Control and Conditionals

In this blog, we will see what is Flow control in Helm.

Flow Control controls the flow of text generated. The flow of text can go in one direction or another direction, instead of the same content being generated every time. In a way, it’s like we can almost program our charts to generate their contents based on dynamic conditions.

Conditional Flow Control

Let’s explore one kind of flow control type: if/else conditional blocks.

A complex if/else conditional block can look like this:

{{ if VALUE or PIPELINE passed here is TRUE }}
  # Print out this text
{{ else if OTHER VALUE or OTHER PIPELINE passed here is TRUE}}
  # Print out this other text
{{ else }}
  # If none of the conditions above are met, then print out this default text
{{ end }}

Of course, an if/else conditional block does not have to be this complex (with so many else and else ifs). In practice, a simple conditional flow control block can look something like this:

{{ if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{ end }}

which is what we’ll actually use. This block can be interpreted like this: if there is no autoscaling.enabled value that the user-defined in his values.yaml file (or it is defined as false), then print out a replicas: line in the manifest sent out to Kubernetes. If there is an autoscaling.enabled value defined as true, then just skip this, the replicas: line will not be printed.

Notice how in an IF statement we can use either a simple value or an entire pipeline. So in an IF statement, we can do something like, extract a value from somewhere, pass it through a pipeline of functions, transform the data in some ways, then evaluate if the end result is true or false. But how does it decide if it is true or false?

If the pipeline being evaluated returns a boolean value like true or false, it’s easy to understand how that is interpreted. Otherwise, if it’s a number, 0 is interpreted as false, non-zero numbers as true. If it’s a string, an empty one is false, while if it contains even a single letter, it is true. And the same can be said for other kinds of objects, like arrays and so on: if they’re empty, this is equivalent to being false, if they’re non-empty, the condition is evaluated as true.

Let’s use this in our deployments.yaml file. Our final content should look like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-nginx
  labels:
    app: nginx
spec:
  {{ if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{ end }}
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository | default "nginx" }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 80
              protocol: TCP

So all we did is basically wrap around our pre-existing line

replicas: {{ .Values.replicaCount }}

between the newly added lines:

{{ if not .Values.autoscaling.enabled }}

and

{{ end }}

We’ll also need to edit our values.yaml file and define this new autoscaling value that our if statement will verify.

Scroll to the end and add these lines:

autoscaling:
  enabled: false

Make sure the text is properly indented (there should be two empty spaces before the enabled: false line). Also, change replicaCount to 3. The final content should look like this:

replicaCount: 3

image:
  repository:
  pullPolicy: IfNotPresent
  tag: "1.16.0"

autoscaling:
  enabled: false

So, with autoscaling disabled (set to false) we expect our if statement to print out a replicas line in the generated manifest.

helm template ./nginx

And we see that indeed it does (also notice the weird empty lines added before and after the replicas line, we’ll see how to fix that too):

---
# Source: nginx/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: RELEASE-NAME-nginx
  labels:
    app: nginx
spec:
  
  replicas: 3
  
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: "nginx:1.16.0"
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
              protocol: TCP

But this is the same as what happened before. Let’s see if our if statement does its job when autoscaling is enabled.

Let’s see the generated manifest this time by running helm template ./nginx

---
# Source: nginx/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: RELEASE-NAME-nginx
  labels:
    app: nginx
spec:
  
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: "nginx:1.16.0"
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
              protocol: TCP

We can see no more replicas line.

Controlling Whitespace/Newlines Output

We noticed how our if conditional block generates some rather weird output (empty lines) around the replicas line. If we remember, our if and end statement is wrapped around that line.

{{ if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{ end }}

So when seeing the new empty lines added in the generated manifest, we can imagine that those have something to do with that if and end line above and below the replicas line.

spec:

  replicas: 3
  
  selector:
    matchLabels:

The templating language we use in our template files has a simple method to deal with this. We just add a - sign next to the curly brackets.

This means remove whitespace/newlines to the left:

{{-

And this means remove whitespace/newlines to the right:

-}}

Let’s edit our deployment template file again and add the “-” signs to their proper places, at the beginning of the if statement and the end statement. The final content will look like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-nginx
  labels:
    app: nginx
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository | default "nginx" }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 80
              protocol: TCP

Let’s see how our generated manifest changes using helm template ./nginx

---
# Source: nginx/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: RELEASE-NAME-nginx
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: "nginx:1.16.0"
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
              protocol: TCP

Read the official documentation here

Checkout the Helm for the Absolute Beginners course here

Checkout the Complete Kubernetes learning path here