Named Templates

It is time to move beyond one template, and begin to create others. In thissection, we will see how to define named templates in one file, and then usethem elsewhere. A named template (sometimes called a partial or asubtemplate) is simply a template defined inside of a file, and given a name.We’ll see two ways to create them, and a few different ways to use them.

In the Flow Control section we introduced three actionsfor declaring and managing templates: define, template, and block. In thissection, we’ll cover those three actions, and also introduce a special-purposeinclude function that works similarly to the template action.

An important detail to keep in mind when naming templates: template names areglobal. If you declare two templates with the same name, whichever one isloaded last will be the one used. Because templates in subcharts are compiledtogether with top-level templates, you should be careful to name your templateswith chart-specific names.

One popular naming convention is to prefix each defined template with the nameof the chart: {{ define "mychart.labels" }}. By using the specific chart nameas a prefix we can avoid any conflicts that may arise due to two differentcharts that implement templates of the same name.

Partials and _ files

So far, we’ve used one file, and that one file has contained a single template.But Helm’s template language allows you to create named embedded templates, thatcan be accessed by name elsewhere.

Before we get to the nuts-and-bolts of writing those templates, there is filenaming convention that deserves mention:

  • Most files in templates/ are treated as if they contain Kubernetes manifests
  • The NOTES.txt is one exception
  • But files whose name begins with an underscore () are assumed to _not havea manifest inside. These files are not rendered to Kubernetes objectdefinitions, but are available everywhere within other chart templates foruse.These files are used to store partials and helpers. In fact, when we firstcreated mychart, we saw a file called _helpers.tpl. That file is the defaultlocation for template partials.

Declaring and using templates with define and template

The define action allows us to create a named template inside of a templatefile. Its syntax goes like this:

  1. {{ define "MY.NAME" }}
  2. # body of template here
  3. {{ end }}

For example, we can define a template to encapsulate a Kubernetes block oflabels:

  1. {{- define "mychart.labels" }}
  2. labels:
  3. generator: helm
  4. date: {{ now | htmlDate }}
  5. {{- end }}

Now we can embed this template inside of our existing ConfigMap, and theninclude it with the template action:

  1. {{- define "mychart.labels" }}
  2. labels:
  3. generator: helm
  4. date: {{ now | htmlDate }}
  5. {{- end }}
  6. apiVersion: v1
  7. kind: ConfigMap
  8. metadata:
  9. name: {{ .Release.Name }}-configmap
  10. {{- template "mychart.labels" }}
  11. data:
  12. myvalue: "Hello World"
  13. {{- range $key, $val := .Values.favorite }}
  14. {{ $key }}: {{ $val | quote }}
  15. {{- end }}

When the template engine reads this file, it will store away the reference tomychart.labels until template "mychart.labels" is called. Then it willrender that template inline. So the result will look like this:

  1. # Source: mychart/templates/configmap.yaml
  2. apiVersion: v1
  3. kind: ConfigMap
  4. metadata:
  5. name: running-panda-configmap
  6. labels:
  7. generator: helm
  8. date: 2016-11-02
  9. data:
  10. myvalue: "Hello World"
  11. drink: "coffee"
  12. food: "pizza"

Conventionally, Helm charts put these templates inside of a partials file,usually _helpers.tpl. Let’s move this function there:

  1. {{/* Generate basic labels */}}
  2. {{- define "mychart.labels" }}
  3. labels:
  4. generator: helm
  5. date: {{ now | htmlDate }}
  6. {{- end }}

By convention, define functions should have a simple documentation block({{//}}) describing what they do.

Even though this definition is in _helpers.tpl, it can still be accessed inconfigmap.yaml:

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: {{ .Release.Name }}-configmap
  5. {{- template "mychart.labels" }}
  6. data:
  7. myvalue: "Hello World"
  8. {{- range $key, $val := .Values.favorite }}
  9. {{ $key }}: {{ $val | quote }}
  10. {{- end }}

As mentioned above, template names are global. As a result of this, if twotemplates are declared with the same name the last occurrence will be the onethat is used. Since templates in subcharts are compiled together with top-leveltemplates, it is best to name your templates with chart specific names. Apopular naming convention is to prefix each defined template with the name ofthe chart: {{ define "mychart.labels" }}.

Setting the scope of a template

In the template we defined above, we did not use any objects. We just usedfunctions. Let’s modify our defined template to include the chart name and chartversion:

  1. {{/* Generate basic labels */}}
  2. {{- define "mychart.labels" }}
  3. labels:
  4. generator: helm
  5. date: {{ now | htmlDate }}
  6. chart: {{ .Chart.Name }}
  7. version: {{ .Chart.Version }}
  8. {{- end }}

If we render this, the result will not be what we expect:

  1. # Source: mychart/templates/configmap.yaml
  2. apiVersion: v1
  3. kind: ConfigMap
  4. metadata:
  5. name: moldy-jaguar-configmap
  6. labels:
  7. generator: helm
  8. date: 2016-11-02
  9. chart:
  10. version:

What happened to the name and version? They weren’t in the scope for our definedtemplate. When a named template (created with define) is rendered, it willreceive the scope passed in by the template call. In our example, we includedthe template like this:

  1. {{- template "mychart.labels" }}

No scope was passed in, so within the template we cannot access anything in ..This is easy enough to fix, though. We simply pass a scope to the template:

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: {{ .Release.Name }}-configmap
  5. {{- template "mychart.labels" . }}

Note that we pass . at the end of the template call. We could just as easilypass .Values or .Values.favorite or whatever scope we want. But what we wantis the top-level scope.

Now when we execute this template with helm install —dry-run —debug./mychart, we get this:

  1. # Source: mychart/templates/configmap.yaml
  2. apiVersion: v1
  3. kind: ConfigMap
  4. metadata:
  5. name: plinking-anaco-configmap
  6. labels:
  7. generator: helm
  8. date: 2016-11-02
  9. chart: mychart
  10. version: 0.1.0

Now {{ .Chart.Name }} resolves to mychart, and {{ .Chart.Version }}resolves to 0.1.0.

The include function

Say we’ve defined a simple template that looks like this:

  1. {{- define "mychart.app" -}}
  2. app_name: {{ .Chart.Name }}
  3. app_version: "{{ .Chart.Version }}"
  4. {{- end -}}

Now say I want to insert this both into the labels: section of my template,and also the data: section:

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: {{ .Release.Name }}-configmap
  5. labels:
  6. {{ template "mychart.app" . }}
  7. data:
  8. myvalue: "Hello World"
  9. {{- range $key, $val := .Values.favorite }}
  10. {{ $key }}: {{ $val | quote }}
  11. {{- end }}
  12. {{ template "mychart.app" . }}

The output will not be what we expect:

  1. # Source: mychart/templates/configmap.yaml
  2. apiVersion: v1
  3. kind: ConfigMap
  4. metadata:
  5. name: measly-whippet-configmap
  6. labels:
  7. app_name: mychart
  8. app_version: "0.1.0+1478129847"
  9. data:
  10. myvalue: "Hello World"
  11. drink: "coffee"
  12. food: "pizza"
  13. app_name: mychart
  14. app_version: "0.1.0+1478129847"

Note that the indentation on app_version is wrong in both places. Why? Becausethe template that is substituted in has the text aligned to the right. Becausetemplate is an action, and not a function, there is no way to pass the outputof a template call to other functions; the data is simply inserted inline.

To work around this case, Helm provides an alternative to template that willimport the contents of a template into the present pipeline where it can bepassed along to other functions in the pipeline.

Here’s the example above, corrected to use indent to indent the mychart_apptemplate correctly:

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: {{ .Release.Name }}-configmap
  5. labels:
  6. {{ include "mychart.app" . | indent 4 }}
  7. data:
  8. myvalue: "Hello World"
  9. {{- range $key, $val := .Values.favorite }}
  10. {{ $key }}: {{ $val | quote }}
  11. {{- end }}
  12. {{ include "mychart.app" . | indent 2 }}

Now the produced YAML is correctly indented for each section:

  1. # Source: mychart/templates/configmap.yaml
  2. apiVersion: v1
  3. kind: ConfigMap
  4. metadata:
  5. name: edgy-mole-configmap
  6. labels:
  7. app_name: mychart
  8. app_version: "0.1.0+1478129987"
  9. data:
  10. myvalue: "Hello World"
  11. drink: "coffee"
  12. food: "pizza"
  13. app_name: mychart
  14. app_version: "0.1.0+1478129987"

It is considered preferable to use include over template in Helm templatessimply so that the output formatting can be handled better for YAML documents.

Sometimes we want to import content, but not as templates. That is, we want toimport files verbatim. We can achieve this by accessing files through the.Files object described in the next section.