Yaron Idan
Yaron Idan

Reputation: 6765

Creating a filtered list using helm template helpers

I'm trying to use a helm template helper to filter out values from a list in my values.yaml file, based on the value of one of the keys in each list member.

My chart is currently comprised of these files -
values.yaml -

namespaces:
- name: filter
  profiles:
  - nonProduction
- name: dont-filter
  profiles:
  - production  
clusterProfile: production

templates/namespaces.yaml

apiVersion: v1
kind: List
items:
{{ $filteredList := include "filteredNamespaces" . }}
{{ range $filteredList }}
  {{ .name }}
{{- end -}}

templates/_profile-match.tpl

{{/* vim: set filetype=mustache: */}}
{{- define "filteredNamespaces" -}}
  {{ $newList := list }}
  {{- range .Values.namespaces }}
    {{- if has $.Values.clusterProfile .profiles -}}
      {{ $newList := append $newList . }}
    {{- end -}}
  {{ end -}}
  {{ $newList }}
{{- end -}}

The problem is that within my helper file, the $newList variable is only populated within the scope of the range loop, and I end up with an empty list returning to the namespaces.yaml template.
Is there any way around this issue? Am I taking the wrong approach for solving this?

Upvotes: 8

Views: 14236

Answers (3)

Bruno Bressi
Bruno Bressi

Reputation: 1

We faced a similar problem and thanks to the answer provided from nichoio we were able to solve this. Just for google's sake, I'll provide it here as well :)

Our values.yaml included all of our secret references, and we wanted to filter those secrets, based upon which service is currently being deployed.

We added a new array in the values.yaml (named selectedSecrets), which was a list of secrets that are needed for the current deployment. Then, with the help of a function, only the secret references are passed to the deployment, which are included in the selectedSecrets. Please note, that the following function returns the original, complete list of secretRefs if no selectedSecrets are present.

{{- define "FilteredSecrets" -}}

    {{ $result := .Values.secretRefs }}
    {{ $selectedSecrets := .Values.selectedSecrets }}
    {{- if gt (len $selectedSecrets) 0 -}}
        {{ $result = list }}
        {{ range $secret := .Values.secretRefs }}
            {{ if has $secret.name $selectedSecrets }}
                {{ $result = append $result $secret }}
            {{- end -}}
        {{- end }}
    {{- end -}}
    {{ toJson $result }}
{{- end }}

The relevant part from the deployment:

containers:
  - env:
    {{- range include "FilteredSecrets" . | fromJsonArray }}
    {{- range $env := .envs }}
    - name: {{ $env.name }}
      valueFrom:
      secretKeyRef:
        key: {{ $env.name }}
        name: {{ .name }}
        optional: {{ $env.optional }}
    {{- end }}
    {{- end }}

Upvotes: 0

nichoio
nichoio

Reputation: 7667

I had to deal with a very similar problem. I'm giving a minimal example how to filter and use the generated template as a list (Helm 3).

_helpers.tpl:

{{- define "FilteredList" -}}

  {{ $newList := list }}
  {{- range .Values.my_list }}
    {{ $newList = append $newList .pvc }}
  {{- end }}

  {{ toJson $newList }}
{{- end }}

pvc.yaml:

{{- range include "FilteredList" . | fromJsonArray }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: {{ . }}
spec:
  resources:
    requests:
      storage: 1G
---
{{- end }}

values.yaml:

my_list:
  - name: foo
    pvc: pvc-one
  - name: foo2
    pvc: pvc-two
  - name: foo3
    pvc: pvc-three

Generates 3 PVC resources as you would expect.
Mind 2 things here, which differ from the question and accepted answer:

  • When appending and overriding the list, we use = instead of :=. Note the example the Helm 3 docs for "append" provide: $new = append $myList 6.
    I actually don't know why = is required, a quick research from my side was without any result.

  • Recover the list from the JSON string using fromJsonArray. This function is not documented yet which is very unfortunate and the main reason i provide this extra answer. You can find it in Helm's source however.

Upvotes: 10

David Maze
David Maze

Reputation: 159545

For all that Go templates are almost general-purpose functions, they have a couple of limitations. One of those limitations is that they only ever return a string; you can't write basic functional helpers like map or filter because you can't return the resulting list.

The more straightforward approach to doing filtering as you've shown is to move it up to the point of the caller (and possibly repeat the condition if it's needed in multiple places):

items:
{{- range .Values.namespaces }}
{{- if has $.Values.clusterProfile .profiles }}
  - {{ .name }}
{{- end }}
{{- end }}

A hacky way to make this work as you have it is to marshal the list to some other string-based format, like JSON:

{{- define "filteredNamespaces" -}}
...
{{ toJson $newList }}
{{- end -}}

{{- range include "filteredNamespaces" . | fromJson -}}...{{- end -}}

Also remember that you can inject Helm values files using the helm install -f option. So rather than listing every permutation of option and then filtering out the ones you don't want, you could restructure this so that namespaces: only contains the list of namespaces you actually want to use, but then you use a different values file for each profile.

Upvotes: 13

Related Questions