Helm optional nested variables
A technique I've used successfully is to use a variable to hold the value of the outer block, which then can use templating constructs like default
and Sprig's dict helper.
{{- $foo := .Values.foo | default dict -}}
Bar is {{ $foo.bar | default "not in the values file" }}
This provides a fallback dictionary if foo
isn't in the file, so then $foo
is always defined and you can look up $foo.bar
in it.
Most charts will default the parent object to a empty map in values.yaml
so it always exists.
foo: {}
Then {{ if .Values.foo.bar }}
works.
If that's not possible, test both keys:
{{ if .Values.foo }}
{{ if .Values.foo.bar }}
{{ .Values.foo.bar }}
{{ end }}
{{ end }}
Using the and
function doesn't work in this case due to and
evaluating all parameters, even if the first is falsey.
There is also the hasKey
function included from sprig if you ever need to check the existence of a falsey or empty value:
{{ if hasKey .Values.foo "bar" }}
Simple workaround
Wrap each nullable level with parentheses ()
.
{{ ((.Values.foo).bar) }}
Or
{{ if ((.Values.foo).bar) }}
{{ .Values.foo.bar }}
{{ end }}
How does it work?
Helm uses the go text/template
and inherits the behaviours from there.
Each pair of parentheses ()
can be considered a pipeline
.
From the doc (https://pkg.go.dev/text/template#hdr-Actions)
It is:
The default textual representation (the same as would be printed by fmt.Print)...
With the behaviour:
If the value of the pipeline is empty, no output is generated... The empty values are false, 0, any nil pointer or interface value, and any array, slice, map, or string of length zero.
As such, by wrapping each nullable level with parentheses, when they are chained, the predecessor nil pointer gracefully generates no output to the successor and so on, achieving the nested nullable fields workaround.