Minimal Helm resource generator
At work we run a lot of deployments/jobs in a given Kubernetes cluster, and many of them are slight copies of each other. A good example of this is running a series of cronjobs: as a rule you want them to run with the same setup, except for the command and the time specifier.
For these cases of “many of the same thing”, this is the base Helm template that we tend to follow. Here presented in a pseudocode fashion:
{{- define "greet" }}
{{- $root := index . 0 }}
{{- $item := index . 1 }}
{{- $prefix := $item.prefix | default $root.Values.prefix }}
{{- $prefix }} {{ $root.Values.name }}
{{- end }}
{{- range $key, $value := .Values.customizations }}
---
kind: Greeter
apiVersion: 1
metadata:
name: {{ $key }}
spec:
phrase: {{ include "greet" (list $ $value) }}
{{- end }}
If you run the above with the following values:
name: "Joe"
prefix: "Hello"
customizations:
informal:
prefix: "Heya"
normal: {}
formal:
prefix: "Dear"
You would get this result:
---
kind: Greeter
apiVersion: 1
metadata:
name: formal
spec:
phrase: Dear Joe
---
kind: Greeter
apiVersion: 1
metadata:
name: informal
spec:
phrase: Heya Joe
---
kind: Greeter
apiVersion: 1
metadata:
name: normal
spec:
phrase: Hello Joe
This allows us to generate big quantities of Kubernetes manifests while keeping our value files free from lots of repetition.
Explanation
The customizations here are driven by a map (a.k.a. dictionary in Python): one “Greeter” in the results is generated for every entry in the “customizations” map.
Each map entry is a tuple of (name, value). The name is used to name the Greeter (see metadata.name
). The value is passed to the greet function.
In the Go template language, you can define functions. Even though functions can only take one parameter, we can get around this limitation by passing a list object. This list object is constructed when calling include
and deconstructed just after the define
definition.
The greet
function in our example needs to be aware of the original template context that this chart was rendered with. That original template context, which in the default block would be called .
, within a range
block is renamed to $
. That is why the arguments of the include
functions are (list $ $value)
, but outside of the range
block it is referred to .
(an example is the .Values.customizations
reference).
Inside the greet
function we deconstruct the list using index . N
, again referencing the .
context. At this level it is not the original template context anymore but the context passed in to the include (the list
object). The deconstruction in our case is simply naming the positional arguments of the list to something more explicit.
The greet
function returns a concatenated string of $prefix
and $root.Values.name
. The $prefix
is looked up inside the customization block first and, if that does not evaluate to anything, it falls back to the default prefix.