Reputation: 6765
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
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
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
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