Caculating the global index for two ranges in Kubernetes Sprig/helm templates?

9/18/2018

In a Helm Chart I have to following values

dataCenters:
  - name: a
    replicas: 3
  - name: b
    replicas: 2

When generating the template I would like my output to be like the following

server.1 = a-1
server.2 = a-2
server.3 = a-3
server.4 = b-1
server.5 = b-2

I tried this code

{{- $index := 0 -}}
{{ range $dc := .Values.cluster.dataCenters -}}
{{ range $seq := (int $dc.replicas | until) -}}
{{- $index := (add $index 1) -}}
server.{{ $index }}={{ $dc.name }}-{{ $seq }}
{{ end -}}
{{ end -}}

however in helm templates I don't thing you can reassign the value of the index as my 4th line is attempting and because of that I get out

server.1 = a-1
...
server.1 = b-2

How does one calculates the global index 0 to 4 (1 to 5 in my situation) using the Sprig/Helm templating language?

-- Dan M
kubernetes
kubernetes-helm
yaml

1 Answer

9/19/2018

I have a way to do it that involves some trickery, heavily inspired by functional programming experience.

A Go/Helm template takes a single parameter, but the sprig library gives you the ability to create lists, and the text/template index function lets you pick things out of a list. That lets you write a "function" template that takes multiple parameters, packed into a list.

Say we want to write out a single line of this output. We need to keep track of which server number we're at (globally), which replica number we're at (within the current data center), the current data center record, and the records we haven't emitted yet. If we're past the end of the current list, then print the records for the rest of the data centers; otherwise print a single line for the current replica and repeat for the next server/replica index.

{{ define "emit-dc" -}}
  {{ $server := index . 0 -}}
  {{ $n := index . 1 -}}
  {{ $dc := index . 2 -}}
  {{ $dcs := index . 3 -}}
  {{ if gt $n (int64 $dc.replicas) -}}
    {{ template "emit-dcs" (list $server $dcs) -}}
  {{ else -}}
server.{{ $server }}: {{ $dc.name }}-{{ $n }}
{{ template "emit-dc" (list (add1 $server) (add1 $n) $dc $dcs) -}}
  {{ end -}}
{{ end -}}

At the top level, we know the index of the next server number, plus the list of data centers. If that list is empty, we're done. Otherwise we can start emitting rows from the first data center in the list.

{{ define "emit-dcs" -}}
  {{ $server := index . 0 -}}
  {{ $dcs := index . 1 -}}
  {{ if ne 0 (len $dcs) -}}
    {{ template "emit-dc" (list $server 1 (first $dcs) (rest $dcs)) -}}
  {{ end -}}
{{ end -}}

Then in your actual resource definition (say, your ConfigMap definition) you can invoke this template with the first server number:

{{ template "emit-dcs" (list 1 .Values.dataCenters) -}}

Copy this all into a dummy Helm chart and you can verify the output:

% helm template .
---
# Source: x/templates/test.yaml
server.1: a-1
server.2: a-2
server.3: a-3
server.4: b-1
server.5: b-2

I suspect this trick won't work well if the number of servers goes much above the hundreds (the Go templating engine almost certainly isn't tail recursive), and this is somewhat trying to impose standard programming language methods on a templating language that isn't quite designed for it. But...it works.

-- David Maze
Source: StackOverflow