Helm templating: index for subkeys

138 Views Asked by At

I'd like to impement the following scenario.

Global variable

global:
  env:
    name: prod

Chart specific variable

env:
  name: dev

I want to have precedence of chart specific > global variable

eg: In my chart, I want to use something like

      containers:
          - name: "{{- include "tester.getValueWithPrecedence" (list . "env.name") -}}"

And this is the _helpers.tpl function

{{- define "tester.getValueWithPrecedence" -}}
{{- $top := index . 0 }}
{{- $var := index . 1 }}
{{- if hasKey $top.Values $var -}}
{{- index $top.Values $var -}}
{{- else if hasKey $top.Values.global $var -}}
{{- index $top.Values.global $var -}}
{{- end -}}
{{- end -}}

I think the problem is env.name key is not interpreted as subkey, but a string.

Is there any way to achieve this?

Any help is much appreciated.

2

There are 2 best solutions below

0
Rajesh Rajendran On BEST ANSWER

I figured out a simpler approach.

{{/* Define getmethekey function */}}
{{- define "tester.getmethekey" -}}
{{- $root := first . -}}
{{- $keys := rest . -}}
{{- $value := $root.Values -}}

{{- range $index, $key := $keys -}}
  {{- if kindIs "map" $value -}}
    {{- if hasKey $value $key -}}
      {{- $value = index $value $key -}}
    {{- else -}}
      {{- $value = "" -}}
    {{- end -}}
  {{- else -}}
    {{- $value = "" -}}
  {{- end -}}
{{- end -}}

{{- $value -}}
{{- end -}}

and the input will be

          - name: "{{- include "tester.getmethekey" (list . "env" "name") -}}"

Values.yaml

env:
  name: rajesh
0
David Maze On

This is hard to do in exactly the way you describe it.

The core problem, as you note, is that both hasKey and index expect their argument as a single key in the map and not a dot-separated string. index can take multiple arguments – index .Values "env" "name" – but not a single list-typed argument, and there's not a good way to expand a list into multiple arguments. (Otherwise the dig template function could be part of an answer.)


You can still write a template function to do this, but it's going to be a little involved. You can write a recursive template that steps through a list of path components, and invoke it with a preferred dictionary and a fallback.

{{- include "f" (list .Values .Values.global (list "env" "name")) }}
{{- include "f" (list .Values.env .Values.global.env (list "name")) }}

The implementation trick that will help is that pluck will take a single path step and multiple dictionaries (again, as positional parameters and not a list), and return that step into all of the dictionaries that contain it. That means there are three cases on pluck's return value:

  1. It returns nothing, and we miss in both dictionaries;
  2. It returns exactly one dictionary; or
  3. It returns a sub-key from both dictionaries.

Once we've figured out which of these cases we're in, similarly, either we're at the end of the list of we're not, and we can recurse or return the value accordingly.

So I might write (untested):

{{- define "f" -}}
  {{- $first := index . 0 }}
  {{- $second := index . 1 }}
  {{- $path := index . 2 }}
  {{- $result := pluck (first $path) $first $second }}
  {{- if eq 0 (len $result) }}
    {{- "" }}
  {{- else if eq 1 (len $path) }}
    {{- first $result }}
  {{- else if eq 1 (len $result) }}
    {{- include "f" (list (first $result) dict (rest $path) }}
  {{- else }}
    {{- include "f" (list (index $result 0) (index $result 1) (rest $path) }}
  {{- end }}
{{- end }}

The other important thing to note here is, no matter what's in .Values, a defined function only ever produces a string as its result. In this example I return an empty string for the "not found" case.


Depending on how often you're using this, it might not be worth the effort of writing out this very generic function.

env:
  - name: ENVIRONMENT
    value: {{ dig "env" "name" (dig "global" "env" "name" .Values) .Values }}

Depending on your use case, it also may help you to know that per-deployment helm install -f values override the chart's values.yaml file. So you don't need to write a default value in values.yaml, but check to see if the user has provided an override at a different path; you can use the same .Values.env.name and the user override will take precedence. The global name hints at a parent-child chart relationship, and again the parent-chart values will take precedence over the defaults in the child's values.yaml. There are other reasonable setups where you do need the more general approach you're asking about, though.