34

Using Helm templates, I'm trying to generate a list of server names based on a number in values.yaml. The dot for this template is set to the number (its a float64).

{{- define "zkservers" -}}
{{- $zkservers := list -}}
{{- range int . | until -}}
{{- $zkservers := print "zk-" . ".zookeeper" | append $zkservers -}}
{{- end -}}
{{- join "," $zkservers -}}
{{- end -}}

For an input of, say, 3 I'm expecting this to produce:

zk-0.zookeeper,zk-1.zookeeper,zk-2.zookeeper

It produces nothing.

I understand that the line within the range block is a no-op since the variable $zkservers is a new variable each time the loop iterates. It is not the same variable as the $zkservers in the outer scope.

I hope the intention is clear of what I want to do. I am at a loss how to do it.

Anyone know how to do this with Helm templates?

alfred
  • 639
  • 1
  • 6
  • 11

8 Answers8

69

For those from 2020+ it now can be achieved as simple as that:

{{ join "," .Values.some.array }}

It produces the correct output: value: "test1,test2,test3"

At least it works with (more or less) recent versions of helm-cli:

Client: &version.Version{SemVer:"v2.16.2", GitCommit:"bbdfe5e7803a12bbdf97e94cd847859890cf4050", GitTreeState:"clean"}
Simon.S.A.
  • 6,240
  • 7
  • 22
  • 41
Illya Doos
  • 814
  • 1
  • 8
  • 5
  • 3
    This is definitely the best answer as of 2020 – Miguel Ferreira May 12 '20 at 06:24
  • 4
    This does not cover how you would concatenate for each element `zk-i.zookeepr` where `i` would be the original array element – David S. Sep 30 '20 at 15:09
  • 2
    And it continues to be the correct answer in 2021 – Dark_eye Jan 21 '21 at 23:32
  • 4
    Strange I don't see this documented on the helm function list. https://helm.sh/docs/chart_template_guide/function_list – Paul Miller Jan 22 '21 at 17:23
  • 2
    @PaulMiller Indeed. I think it's better to turn to `sprig` library documentation when it comes to helm3 templating, since most functions are taken from there AFAIK: https://godoc.org/github.com/Masterminds/sprig Helm maintainers also mention the original docs at the bottom of the document you've provided: `Note, the documentation for many of these functions come from Sprig. Sprig is a template function library available to Go applications.` – Illya Doos Jan 26 '21 at 10:01
  • 1
    Continues to work in 2022 as well! – Arpit Aug 25 '22 at 11:00
35

Another quick way of doing it:

{{- define "helm-toolkit.utils.joinListWithComma" -}}
{{- $local := dict "first" true -}}
{{- range $k, $v := . -}}{{- if not $local.first -}},{{- end -}}{{- $v -}}{{- $_ := set $local "first" false -}}{{- end -}}
{{- end -}}

If you give this input like:

test:
- foo
- bar

And call with:

{{ include "helm-toolkit.utils.joinListWithComma" .Values.test }}

You'll get the following rendered:

foo,bar

This is from OpenStack-Helm's Helm-toolkit chart, which is a collection of utilities for similar purposes.

Pete Birley
  • 351
  • 1
  • 3
  • 3
18

I faced with the same problem and your solution with dictionary saved my day. It's a good workaround and it can be just a little simpler:

{{- define "zkservers" -}}
{{- $zk := dict "servers" (list) -}}
{{- range int . | until -}}
{{- $noop := printf "zk-%d.zookeeper" . | append $zk.servers | set $zk "servers" -}}
{{- end -}}
{{- join "," $zk.servers -}}
{{- end -}}
DmiBay
  • 181
  • 1
  • 6
  • Found https://github.com/Masterminds/sprig/tree/master/docs which helps explain how this template language works, but still struggling with `| append $zk.servers | set $zk "servers"` – Ed Randall Jun 12 '20 at 21:23
  • It's not clear why `$dot` has to be a `dict` which contains the list? Is that because you can't reassign the value of `$zk` within the `range` block? – Shannon Oct 19 '22 at 21:27
10

You're currently defining a new varialbe in the scope of the list while you want to alter the existing value.

In order to fix your bug, you only need to change the way you assign the value to $zkservers:

    - {{- $zkservers := print "zk-" . ".zookeeper" | append $zkservers -}}
    + {{- $zkservers = print "zk-" . ".zookeeper" | append $zkservers -}}  

which gives you the following script:

{{- define "zkservers" -}}
{{- $zkservers := list -}}
{{- range int . | until -}}
{{- $zkservers = print "zk-" . ".zookeeper" | append $zkservers -}}
{{- end -}}
{{- join "," $zkservers -}}
{{- end -}}

With this slight modification, {{ include "zkservers" 3 }}gives you the output zk-0.zookeeper,zk-1.zookeeper,zk-2.zookeeper

Pierre Michard
  • 1,341
  • 1
  • 12
  • 16
7

I was doing the same but on elasticsearch helm chart. Please find my example

values.yml:

...
replicaCount: 3
...

StatefulSets.yaml:

...
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.registry_address }}/{{ .Values.image.name }}:{{ .Chart.AppVersion }}"
          env:
            - name: ELASTICSEARCH_CLUSTER_NAME
              value: "{{ .Values.env.ELASTICSEARCH_CLUSTER_NAME }}"
            - name: ELASTICSEARCH_DISCOVERY_ZEN_PING_UNICAST_HOSTS
              value: "{{ template "nodes" .Values }}"
...

_helpers.tpl:

{{/* vim: set filetype=mustache: */}}

{{- define "nodes" -}}
{{- $nodeCount := .replicaCount | int }}
  {{- range $index0 := until $nodeCount -}}
    {{- $index1 := $index0 | add1 -}}
elasticsearch-{{ $index0 }}.elasticsearch{{ if ne $index1 $nodeCount }},{{ end }}
  {{- end -}}
{{- end -}}

This produces a comma separated list:

...
            - name: ELASTICSEARCH_DISCOVERY_ZEN_PING_UNICAST_HOSTS
              value: "elasticsearch-0.elasticsearch,elasticsearch-1.elasticsearch,elasticsearch-2.elasticsearch"

...
3

I got it working using:

{{- define "zkservers" -}}
{{- $dot := dict "nodes" (int .) "servers" (list) -}}
{{- template "genservers" $dot -}}
{{- join "," $dot.servers -}}
{{- end -}}

{{- define "genservers" -}}
{{- range until .nodes -}}
{{- $noop := print "zk-" . ".zookeeper" | append $.servers | set $ "servers" -}}
{{- end -}}
{{- end -}}

Seems a little bit verbose for what should normally be a simple one/two liner :)

alfred
  • 639
  • 1
  • 6
  • 11
3

How about just rendering and then trimming?

{{- include "template" . | trimSuffix "," -}}
Aram
  • 696
  • 4
  • 16
3

Not sure why this was not yet mentioned.

Using join for lists of primitive type items should be most forward. However, if you're iterating over a list of maps and want a character (comma) separated string of a certain property of the list's items, this seems to be the most obvious solution:

 value: {{ range $i, $value := .Values.listOfMaps }}{{ if ne $i 0 }},{{ end }}{{ $value.someProperty }}{{ end }}
dpr
  • 10,591
  • 3
  • 41
  • 71