Reference Other Values in Helm Chart Values File

Jacky Jiang
ITNEXT
Published in
4 min readFeb 27, 2022

--

Photo by Vardan Papikyan on Unsplash

Background

Helm offers a simple template language that allows us to reference config values that define in a “values file” easily.

apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
data:
myvalue: "Hello {{ .Values.name }}"

e.g. In the Helm chart template above, we reference a config value “name” that is defined in the “values file”. Assuming the value of config field “name” is a string “world”, the template will be rendered as the following eventually:

apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
data:
myvalue: "Hello world"

It looks neat & simple to reference a “value” in a Helm chart template. But how could we reference other values in the Helm chart “values file”?

Why?

Here is a sample “values file” that supplies a list of API endpoint URLs:

apiOneUrl: http://example.com/apiOne/v0
apiTwoUrl: http://example.com/apiTwo/v3
apiThreeUrl: http://example.com/apiThree/v7

You might find all config fields contain the string “http://example.com/”. It would be nice if we can supply the “values file” as the following:

baseUrl: http://example.com
apiOneUrl: "{{ .Values.baseUrl }}/apiOne/v0"
apiTwoUrl: "{{ .Values.baseUrl }}/apiTwo/v3"
apiThreeUrl: "{{ .Values.baseUrl }}/apiThree/v7"

It will not only reduce redundancy and save a lot of typings but also make it a lot easier to update config values in future. However, as the “values file” won’t be processed by Helm’s template engine, if you try to reference .Values.apiOneUrl in your template, you will only get the original string {{ .Values.baseUrl }}/apiOne/v0 as the result.

tpl Function to Rescue

The Helm chart tpl function allows developers to evaluate strings as templates inside a template, which can just be our saver here. The function expects 2 parameters:

  • The first parameter is a template string to be processed.
  • The last parameter is the context data to be used during the template string processing. We can often pass . which is current context data to make all config values are available during the template processing.

If you try the following in your template:

MyApiOneUrl: {{ tpl .Values.apiOneUrl . }}

you will find .Values.apiOneUrl ‘s value "{{ .Values.baseUrl }}/apiOne/v0" will be converted into "http://example.com//apiOne/v0” and the above template will be rendered as:

MyApiOneUrl: http://example.com/apiOne/v0

Problem with Non-string Types

The solution works well with simple string type config values. However, what if the config value is a non-string type value (e.g. a “map” or “list”)?

Consider the following “values file”:

baseUrl: http://example.com
apiUrls:

apiOneUrl: "{{ .Values.baseUrl }}/apiOne/v0"
apiTwoUrl: "{{ .Values.baseUrl }}/apiTwo/v3"
apiThreeUrl: "{{ .Values.baseUrl }}/apiThree/v7"

If you try the following in your template:

MyApiUrls: {{ tpl .Values.apiUrls . }}

You will get the following template error:

wrong type for value; expected string; got map[string]interface {}

As the tpl function expects a template string as the first parameter, the error message does make sense. But how could we solve the issue and extend our solution to this common use case?

One solution is to use toYaml function to convert the non-string type value (“map”, in this case) to a “YAML” string before passing to the tpl function:

MyApiUrls:
{{
tpl (.Values.apiUrls | toYaml) . | indent 2 }}

If you try the template above, you will find that it will be correctly rendered to:


MyApiUrls:
apiOneUrl
: http://example.com/apiOne/v0
apiTwoUrl: http://example.com/apiTwo/v3
apiThreeUrl: http://example.com/apiThree/v7

The Complete Solution

To make the solution portable, we can define a “named template” to detect the config value type & apply the different logic accordingly:

{{ define "render-value" }}
{{- if kindIs "string" .value }}
{{- tpl .value .context }}
{{- else }}
{{- tpl (.value | toYaml) .context }}
{{- end }}
{{- end }}

To use in a template, you can invoke the “named template” using include function:

MyApiOneUrl: {{ include "render-value" ( dict "value" .Values.apiOneUrl "context" .) }}
MyApiUrls:
{{ include "render-value" ( dict "value" .Values.apiUrls "context" .) | indent 2}}

The template above will be rendered into the following:

MyApiOneUrl: http://example.com/apiOne/v0
MyApiUrls:
apiOneUrl
: http://example.com/apiOne/v0
apiTwoUrl: http://example.com/apiTwo/v3
apiThreeUrl: http://example.com/apiThree/v7

If you want to save the effort of defining your own “named template”, you can also use Bitnami’s “common” library chart. To use it, just add the following dependency to your Chart.yaml :

dependencies:
- name: common
version: 1.11.1
repository: https://charts.bitnami.com/bitnami

You then will be able to invoke the included “named template” with similar logic as the followings:

MyApiOneUrl: {{ include "common.tplvalues.render" ( dict "value" .Values.apiOneUrl "context" .) }}

Summary

Helm offers a simple yet powerful template engine that allows us to merge configuration values into templates. However, we might, sometimes, want to extend this capability to the configuration file: the Helm’s “values file” as well. This article introduces a tpl function-based solution and its more generic “named template” implementation.

If you are new to Helm’s “named template” feature, you might also want to have a read my relevant blog to find out more about what “named template” could achieve.

--

--