Search code examples
kubernetes-helm

How to re-use blocks of helm code across manifests?


I have a block of code I'm trying to re-use across several yaml manifest files.

How can I build a function from this that I can re-use across files? I have tried adding a template inside _helpers.tpl like below, but no luck!

Does anyone have any tips on how to template this code for re-use?

The template:

{{- define "xx" -}}
  {{ range $model := .Values.appConfig.models }}
  {{ range $modelMode := $.Values.appConfig.models }}
{{- end -}}

The code to wrap:

{{ range $model := .Values.appConfig.models }}
  {{ range $modelMode := $.Values.appConfig.models }}     
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: {{ $modelMode }}
  labels:

  {{- end -}}
{{- end -}}

My attempt:

{{- template "xx" }}
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: {{ $modelMode }}
  labels:

  {{- end -}}
{{- end -}}

Solution

  • There's a couple of rules around template definitions.

    First off: within a define...end block, all of the inner blocks need to match up; there need to be as many range directives as matching ends. In your case, I'd put the range...end block in the top-level templates/*.yaml file and invoke the template inside that.

    {{- range $model := .Values.appConfig.models -}}
    {{- range $modelMode := $.Values.appConfig.models -}}
    {{- template "xx" . -}}
    {{- end -}}
    {{- end -}}
    
    {{- define "xx" -}}
    ---
    apiVersion: policy/v1
    ...
    {{ end -}}
    

    The Go text/template language doesn't have global variables, and a template can't access variables in its caller. Anything you use in a defined template needs to be passed in the template parameter. Templates only take a single parameter; in Helm, you can pack multiple parameters into a dict or a list.

    The other important detail here is that, normally in Helm, . refers to a specific object with Values, Release, and other properties, but in the Go template language . has a couple of other uses as well. In a defined template, . is the parameter. If you need access to the Helm ., maybe to invoke a subtemplate that defines labels, you need to explicitly pass it as a parameter.

    This all combines into:

    {{- range $model := .Values.appConfig.models -}}
    {{- range $modelMode := $.Values.appConfig.models -}}
    {{- template "xx" (dict "top" $ "modelMode" $modelMode) -}}
    {{- end -}}
    {{- end -}}
    
    {{- define "xx" -}}
    ---
    apiVersion: policy/v1
    kind: PodDisruptionBudget
    metadata:
      name: {{ .modelMode }}
      labels:
    {{ include "mychart.labels" .top | indent 4 }}
    spec:
      minAvailable: {{ .top.Values.minAvailable }}
    {{ end -}}
    

    Take note of a couple of things in the last example. Where we invoke the template, we now pass a dict as the single parameter. We can then get the parameters out inside the invoked template with .modelMode and .top. If we need the top-level values, those are inside the .top object; if we need to invoke another template that needs the top-level object, we need to pass that object, and not the template-specific ..

    I've also called the label template with include rather than template. include is a Helm extension that returns the template result as a string, so in this case it can be re-indented. The two invocation paths are very similar and I might default to using include in most cases.