helm notes

worse is better?

SEAN K.H. LIAO

helm notes

worse is better?

helm notes

Helm is somehow the most popular method of distributing customizable kubernetes manifests right now. If like $work you use it in a gitops environment, it's "just" a fancy templating system. Anyway, here are some notes on authoring helm chart (dependencies).

nil checks

Helm passed data to its templates through its values system: arbitrary nested key/values in a tree. To access the data, use {{ $.Values.key }} in templates, where nested objects have their keys separated by dots like {{ $.Values.some.sub.sytem.key }}. A problem arises when authoring templates: in the previous example, if either of the some, sub, system keys wasn't present, the call would be a nil dereference error. This leads to either very verbose templates like the next example, or Helm's best pracitices recommendation to use a flat namespace like {{ $.Values.someSubSystemKey }}

1{{ if $.Values.some }}
2{{ if $.Values.some.sub }}
3{{ if $.Values.some.sub.system }}
4{{ $.Values.some.sub.system.key }}
5{{ end }}
6{{ end }}
7{{ end }}

Instead, we can work with a quirk of Go's templating engine and instead use parenthesis to guard each level of access, allowing us to keep a nicely structured value tree:

1{{ ((($.Values.some).sub).system).key }}

kebab case access

If for some reason, you have keys in values in kebab-case, you'll realize accessing them is difficult since - is an invalud character for identifiers. get will get you one level of key access, while dig will go through multiple levels (but still won't handle type mismatches):

1foo: {{ get (get $.Values "my-key") "sub-key" }}
2bar: {{ dig "my-key" "sub-key" "default-value" $.Values.AsMap }}

template namespacing

If you've ever wondered why the default templates are all prefixed with $name., it's because all templates are shared everywhere when rendering. The main charts templates are available to its dependencies, the dependencies' are available to the main chart and each other. "Available" is one way to put it, the other is that it may inadvertently override a template another chart was expecting.

1{{ define "myapp.name" }}
2...
3{{ end }}

global values

In helm, if you add a dependency, then it gets passed all the values in the top level key matching its name (or alias if one is defined) hoisted in to its root. But there's a magical global namespace too, which remains constant through all levels of dependencies, which you can use to pass the same data to all your charts without duplicate specification. So given the following values:

1# values.yaml
2global:
3  key: a
4mydep:
5  hello: world
6  foo:
7    bar: fizz

The mydep chart will see it as:

1global:
2  key: a
3hello: world
4foo:
5  bar: fizz

Subcharts key

Suppose you provide a template in a subchart to your users to render something complicated. You need values from both global and those passed to the chart. From the previous example, we can see that depending on where the template is invoked, it'll have an inconsistent view of values. This is where the $.Subcharts built in object comes in: under the name/alias of each subchart is the scope of each subchart, so templates can be invoked with the view of values as if they were in their originating chart.

1# in parent chatt
2{{ include "mydep.template" $.Subcharts.mydep }}