Helm Templates: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
 
(178 intermediate revisions by the same user not shown)
Line 10: Line 10:
* [[helm install]]
* [[helm install]]
* [[Helm Named Template Recipes|Named Template Recipes]]
* [[Helm Named Template Recipes|Named Template Recipes]]
* [[Helm Notable Values|Notable Values]]


=Overview=
=Overview=


Templates are files living under a chart's [[#The_templates.2F_Directory|templates/ directory]]. They are written in YAML with Helm templates extensions. Upon processing by Helm, they become [[Kubernetes Manifests|Kubernetes manifest files]]. Helm template extensions are written in the Help template language, which is based on Go templates.
Templates are a set of Kubernetes parameterized manifests that form an application. They live under a chart's <code>[[#The_templates.2F_Directory|templates/]]</code> directory. They are written in YAML with Helm templates extensions. Upon processing by Helm, they become [[Kubernetes_Manifests#Overview|Kubernetes manifest files]]. Helm template extensions are written in the Help template language, which is based on Go templates.


=The templates/ Directory=
=The <tt>templates/</tt> Directory=
The <code>templates/</code> directory contains templates that, after combination with [[Helm_Configuration#Runtime_Configuration_Tree|values]], in a process named "rendering", become Kubernetes manifests. Helm sends all files found in the directory through the template rendering engine, then collect the results for the files that contain manifests, and sends the rendered manifests to Kubernetes. The <span id='NOTES.txt'><code>[[Helm Chart NOTES.txt|NOTES.txt]]</code> and  <span id='_helpers.tpl'></span><code>[[Helm Chart _helpers.tpl|_helpers.tpl]]</code> files are also rendered, but they are not sent to Kubernetes as manifests.


The 'templates' directory contains templates that, after combination with [[Helm_Concepts#Values_and_Chart_Configuration|values]], will the Kubernetes manifests. When Tiller evaluates a chart, it will send all of the files in the directory - with a few exceptions - through the template rendering engine, then collect the results and send them to to Kubernetes. Note that the <span id='NOTES.txt'>[[Helm Chart NOTES.txt|NOTES.txt]] and <span id='_helpers.tpl'></span>[[Helm Chart _helpers.tpl|_helpers.tpl]] files are also subject to template rendering, but they are not sent to Kubernetes as manifests.
The files whose names begin with an underscore ('_') are assumed to not have a manifest inside, so they are not turned into Kubernetes API resource definitions. However, they are available everywhere within other chart templates for use. These files are conventionally used to store [[Helm_Named_Templates#Sub-Template_Files|sub-templates]] and helpers. <code>[[Helm Chart _helpers.tpl|_helpers.tpl]]</code> is the default location for small sub-templates. If a sub-template is large enough, it can be stored in its own '_'-prefixed file. For more details about sub-templates, see: {{Internal|Helm_Named_Templates#Sub-Template_Files|Helm Named Templates}}


Template names do not follow a rigid naming pattern. It is, however, recommended to use the suffix .yaml for YAML files and .tpl for helpers.
==Template Name==
 
Template names do not follow a rigid naming pattern. It is, however, recommended to use the suffix <code>.yaml</code> for YAML files and <code>.tpl</code> for helpers.
The files whose name begins with an underscore ('_') are assumed to not have a manifest inside, so they are not rendered into manifest definitions. However, they are available everywhere within other chart templates for use. These files are conventionally used to store [[#Partial|partials]] and helpers. [[Helm Chart _helpers.tpl|_helpers.tpl]] is the default location for template [[#Partial|partials]].


==<span id='Order'></span>Installation and De-Installation Order==
==<span id='Order'></span>Installation and De-Installation Order==
During installation, Helm collects all of the resources in a given chart and its dependences, groups them by resource type, and installs them in the order specified here https://github.com/helm/helm/blob/release-2.14/pkg/tiller/kind_sorter.go#L29-L57. Upon de-installation the order is reversed: https://github.com/helm/helm/blob/release-2.14/pkg/tiller/kind_sorter.go#L62-L90


During installation, Helm collects all of the resources in a given chart and its dependences, groups them by resource type, and installs them in the order specified here https://github.com/helm/helm/blob/release-2.14/pkg/tiller/kind_sorter.go#L29-L57. Upon de-installation the order is reversed: https://github.com/helm/helm/blob/release-2.14/pkg/tiller/kind_sorter.go#L62-L90
For more details on dependencies, see: {{Internal|Helm_Dependencies#Installation_and_De-Installation_Order|How Helm Dependencies Work}}


=Template Comments=
=Template Comments=
Line 49: Line 51:
=<span id='Template_Directive'></span>Template Directives=
=<span id='Template_Directive'></span>Template Directives=


A template directive, sometimes also referred as tag, is enclosed in &#123;{ and }} blocks, and it is recommended to pad the directive with space at its left and right.  
A template directive, sometimes also referred as tag, is enclosed in <code>&#123;{</code> and <code>}}</code> blocks. It is recommended to pad the directive with space at its left and right.  
<span id='Simple_Replacement'></span>The simplest directive renders a value. A value is a namespaced object, where each dot (.) separates each namespaced element. A leading dot indicates that we start with the top-most namespace for the [[#Scope|scope]].
<span id='Simple_Replacement'></span>The simplest directive renders a value. A value is a namespaced object, where each dot (.) separates each namespaced element. A leading dot indicates that we start with the top-most namespace for the [[#Scope|scope]].


kind: ConfigMap
<syntaxhighlight lang='yaml'>
metadata:
kind: ConfigMap
  name: &#123;{ .Release.Name }}-configmap
metadata:
  name: {{ .Release.Name }}-configmap
</syntaxhighlight>
 
Potentially any element of the manifest, including keys, can be represented as a template directive:
<syntaxhighlight lang='yaml'>
apiVersion: v1
kind: Secret
stringData:
  {{ .Values.secret.fileName }}: |
    something
    ...
</syntaxhighlight>


Directives may also include [[#Template_Function|functions]] and other constructs.
Directives may also include [[#Template_Function|functions]] and other constructs.
Line 66: Line 80:
</syntaxhighlight>
</syntaxhighlight>


The recommended format is to allow for a space after '&#123;{' and before '}}':
As a matter of style, it is recommended to pad the element declared inside <code>{{</code> ... <code>}}</code> block with a leading and a trailing space:
<syntaxhighlight lang='yaml'>
<syntaxhighlight lang='yaml'>
{{ .Values.color }}
{{ .Values.color }}
Line 72: Line 86:


==Directives and Whitespace Handling==
==Directives and Whitespace Handling==
{{External|[https://helm.sh/docs/chart_template_guide/control_structures/#controlling-whitespace Controlling Whitespace]}}


The spaces inside a directive is irrelevant (we cannot have newlines inside a directive, all directives must be specified on a single line).  
The spaces inside a directive is irrelevant, but there cannot be newlines inside a directive, all directives must be specified on a single line. The <code>&#123;{</code> and <code>}}</code> directive delimiters, without any other modifications, leave the template whitespace adjacent to them alone, and do not interfere with it in any way.  
The '&#123;{' and '}}' directive delimiters, without any other modifications, leave the template whitespace surrounding them alone, and do not interfere with it in any way. A hyphen '-' placed after the '&#123;{' delimiter or before the '}}' delimiter instructs the rendering engine to trim the whitespace preceding, respectively trailing the delimiter. Whitespace includes spaces, tabs, newline ('\n') and carriage return ('\r'). When encountering "-", the template engine will simply drop the corresponding whitespace until a non-whitespace character is found.


A hyphen '-' placed after the <code>&#123;{</code> delimiter or before the <code>}}</code> delimiter instructs the rendering engine to trim the whitespace preceding, respectively trailing the delimiter. Whitespace includes spaces, tabs, newline ('\n') and carriage return ('\r'). When encountering "-", the template engine will simply drop the corresponding whitespace in the respective direction, until a non-whitespace character is found.
<syntaxhighlight lang='yaml'>
<syntaxhighlight lang='yaml'>
metadata:
metadata:
Line 82: Line 97:
</syntaxhighlight>
</syntaxhighlight>


will produce (assuming that "color" is declared to be "blue" in values.yaml:
will produce, assuming that <code>color</code> is declared to be "blue" in <code>values.yaml</code>:
 
<syntaxhighlight lang='yaml'>
<syntaxhighlight lang='yaml'>
metadata:
metadata:
Line 90: Line 104:
</syntaxhighlight>
</syntaxhighlight>


To trim preceding whitespace, use '&#123;{-'.  
To trim preceding whitespace, use <code>&#123;{-</code>.  


<syntaxhighlight lang='yaml'>
<syntaxhighlight lang='yaml'>
Line 106: Line 120:
</syntaxhighlight>
</syntaxhighlight>


To trim trailing whitespace (whitespace includes newlines), use '-}}'.
To trim trailing whitespace (whitespace includes newlines), use <code>-}}</code>.


<syntaxhighlight lang='yaml'>
<syntaxhighlight lang='yaml'>
Line 120: Line 134:
   color: bluespec:
   color: bluespec:
</syntaxhighlight>
</syntaxhighlight>
(which is something you most like don't want).


For details related to whitespace handling when declaring named templates, see [[Helm_Templates#Named_Templates_and_Whitespace_Handling|Named Templates and Whitespace Handling]], below.
which is something you most like do not want.
 
For details related to whitespace handling when declaring named templates, see: {{Internal|Helm_Named_Templates#Named_Templates_and_Whitespace_Handling|Named Templates and Whitespace Handling}}


=<span id='Scope'></span>Scopes=
=<span id='Scopes'></span>Scope=


Scopes are declared with '[[#with|with]]'.  
A scope in the context of a Helm template is is a data structure, specifically a <code>[[sprig_dict#Overview|dict]]</code> instance.  


<font color=darkgray>TODO.</font>
The "." root scope is a dict instance that carries by default the following sub-scopes, which contain Helm [[Helm_Templates#Built-in_Objects|built-in objects]]:
<font size=-1>
.
├─ [[#Values|Values]]
├─ [[#Chart|Chart]]
├─ [[#Release|Release]]
├─ [[#Capabilities|Capabilities]]
├─ [[#Files|Files]]
└─ [[#Template|Template]]
</font>
A scope must be specified when a [[Helm_Named_Templates#Sub-Template_Scope|sub-template]] is rendered with <code>[[Helm_Named_Templates#include_Function|include]]</code> or <code>[[Helm_Named_Templates#template|template]]</code>, otherwise the built-in objects referred from the sub-template may not be resolvable.
 
The root scope can be manipulates as follows:
<syntaxhighlight lang='yaml'>
{{- $Args := dict "Name" "example" -}}
{{- $_0 :=  set . "Args" $Args -}}
</syntaxhighlight>
Scopes are declared with <code>[[#with|with]]</code>.


=<span id='Template_Object'></span>Template Objects=
=<span id='Template_Object'></span>Template Objects=


Objects are passed into a template from the template engine. The template directives can create new objects and pass them around. There are also [[#Built-in_Objects|built-in objects]], which are made available by default. Objects can be simple - have just one value -, or they can contain other objects or functions. For example the "Release" built-in object contains several other objects (like "Release.Name"). The "Files" object contains functions.
Objects are passed into a template from the template engine. The template directives can create new objects and pass them around. There are also [[#Built-in_Objects|built-in objects]], which are made available by default. Objects can be simple, by having just one value, or they can contain other objects or functions. For example the <code>Release</code> built-in object contains several other objects, like <code>Release.Name</code>. The <code>Files</code> object contains functions.


==Built-in Objects==
==Built-in Objects==


{{External|[https://helm.sh/docs/chart_template_guide/#built-in-objects Built-in Objects]}}
{{External|[https://helm.sh/docs/chart_template_guide/builtin_objects/#helm Built-in Objects]}}


The built-in values always begin with a capital letter, based on Go's naming convention. For a fully working examples of built-in objects replacement see: {{External|https://github.com/ovidiuf/playground/tree/master/helm/bactrosaurus}}
Built-in objects are a way to access several types of values, some of which are directly configured by operators in <code>values.yaml</code>, while others are generated dynamically by Helm or taken from other parts of the chart. The built-in values always begin with a capital letter, based on Go's naming convention. For a fully working examples of built-in objects replacement see: {{External|https://github.com/ovidiuf/playground/tree/master/helm/bactrosaurus}}


===Chart===
===<tt>Chart</tt>===


This object contains value passed into the template from the [[Helm Chart Chart.yaml|Chart.yaml]] file. An existing field is available as (note leading dot) <tt>.Chart.<''UpperCasedFirstLetterFieldName''></tt>. It is important to capitalize the first letter of the field name, otherwise the directive evaluation fails.
This object contains value passed into the template from the <code>[[Helm_Chart_Chart.yaml#Overviewl|Chart.yaml]]</code> file. An existing field is accessible as (note leading dot) <code>.Chart.<''UpperCasedFirstLetterFieldName''></code>. It is important to capitalize the first letter of the field name, otherwise the directive evaluation fails.


Example:
Example:
<font size=-1>
  &#123;{ .Chart.Name }}
  &#123;{ .Chart.Name }}
  &#123;{ .Chart.Version }}
  &#123;{ .Chart.Version }}
</font>


===Values===
====<tt>Chart.Version</tt>====


This object contains values passed into template from the [[Helm Chart values.yaml|values.yaml]] file and from [[Helm_Concepts#Values_and_Chart_Configuration|other user sources]]. An existing field is available as (note leading dot) <tt>.Values.<''fieldName''></tt>. Unlike in [[#Chart|Chart]]'s case, the fields are allowed to keep their original capitalization. For example, a value declared as such in <tt>values.yaml</tt>:
===<tt>Values</tt>===
This object provides access the effective values of all configuration elements present in the [[Helm_Configuration#Runtime_Configuration_Tree|runtime configuration tree]]. It exposes the chart configuration to templates. The value of an existing configuration element can be access using the following syntax: (note leading dot) <code>.Values.<fieldName></code>. Unlike in the <code>[[#Chart|Chart]]</code>'s case, the fields are allowed to keep their original capitalization. For example, a value declared as such in <code>values.yaml</code>:


size: 10
<syntaxhighlight lang='yaml'>
size: 10
</syntaxhighlight>


can be references in a template as:
can be references in a template as:


kind: ConfigMap
<syntaxhighlight lang='yaml'>
...
kind: ConfigMap
data:
...
  size: &#123;{ .Values.size }}
data:
  size: {{ .Values.size }}
</syntaxhighlight>


Values can contain structured content:
Values may contain structured content:


characteristics:
<syntaxhighlight lang='yaml'>
  size: 10
characteristics:
  shape: "large"
  size: 10
  shape: "large"
</syntaxhighlight>


can be referenced in template as:
can be referenced in template as:


kind: ConfigMap
<syntaxhighlight lang='yaml'>
kind: ConfigMap
  ...
  ...
data:
data:
  size: &#123;{ .Values.characteristics.size }}
  size: {{ .Values.characteristics.size }}
  shape: &#123;{ .Values.characteristics.shape }}
  shape: {{ .Values.characteristics.shape }}
</syntaxhighlight>
While structuring data this way is possible, the recommendation is to keep values trees shallow, favoring flatness.


While structuring data this way is possible, the recommendation is to keep values trees shallow, favoring flatness.
In case the structure contains an array, individual elements can be referred from the template with the <code>[[#Accessing_Array_Elements|index]]</code> function.


In case the structure contains an array, individual elements can be referred from the template as such:
====Field Names and Dashes====
Field names that contain dashes, while supported, are not rendered in a straightforward manner. For more details, see:
{{Internal|Helm_Chart_values.yaml#Field_Names_and_Dashes|values.yaml &#124; Field Names and Dashes}}


===Release===
===<tt>Release</tt>===
This object describes the [[Helm_Concepts#Release|release]] itself.
This object describes the [[Helm_Concepts#Release|release]] itself.
====Release.Name====
====<tt>Release.Name</tt>====
Exposes the [[Helm_Concepts#Release_Name|release name]]:
Exposes the [[Helm_Concepts#Release_Name|release name]]:
<font size=-1>
  &#123;{ .Release.Name }}
  &#123;{ .Release.Name }}
</font>
====<tt>Release.Namespace</tt>====
Exposes the namespace the release has been made into:
<font size=-1>
&#123;{ .Release.Namespace }}
</font>
This value is specified on command line with <code>[[Helm_install#-n.2C--namespace|-n|--namespace]]</code>. If not specified, it explicitly defaults to <code>"default"</code>.


====Release.Revision====
====<tt>Release.Revision</tt>====
Exposes the [[Helm_Concepts#Release_Revision|release revision]]:
Exposes the [[Helm_Concepts#Release_Revision|release revision]]:
<font size=-1>
  &#123;{ .Release.Revision }}
  &#123;{ .Release.Revision }}
====Release.Time====
</font>
====<tt>Release.Time</tt>====
Exposes the time of the release:
Exposes the time of the release:
<font size=-1>
  &#123;{ .Release.Time }}
  &#123;{ .Release.Time }}
====Release.Namespace====
</font>
Exposes the namespace to be released info, if the manifest does not override:
====<tt>Release.IsUpgrade</tt>====
&#123;{ .Release.Namespace }}
====Release.IsUpgrade====
This is set to true if the current operation is an upgrade or rollback.
This is set to true if the current operation is an upgrade or rollback.
<font size=-1>
  &#123;{ .Release.IsUpgrade }}
  &#123;{ .Release.IsUpgrade }}
====Release.IsInstall====
</font>
====<tt>Release.IsInstall</tt>====
This is set to true if the current operation is an install.
This is set to true if the current operation is an install.
<font size=-1>
  &#123;{ .Release.IsInstall }}
  &#123;{ .Release.IsInstall }}
====Release.Service====
</font>
====<tt>Release.Service</tt>====
Exposes the releasing service - always Tiller
Exposes the releasing service - always Tiller


===Files===
===<span id='Files.Get'></span><span id='Files.GetBytes'></span><span id='Accessing_Files_inside_Templates'></span><tt>Files</tt>===


The object provide access to all non-special files in the chart. It cannot be used to access templates. The access is provided via several functions:
The object provide access to all non-special files in the chart. It cannot be used to access templates. For more details see: {{Internal|Helm Accessing Arbitrary Files inside Templates#Overview|Accessing Arbitrary Files inside Templates}}
 
====Files.Get====
&#123;{ .Files.Get <''file-name''> }}
 
====Files.GetBytes====
 
====Accessing Files inside Templates====
{{External|[https://helm.sh/docs/chart_template_guide/#accessing-files-inside-templates Accessing Files inside Templates]}}
<font color=darkgray>TODO.</font>
 
===Capabilities===


===<tt>Capabilities</tt>===
Provides information about the capabilities of the Kubernetes cluster:
Provides information about the capabilities of the Kubernetes cluster:
<font size=-1>
  &#123;{ .Capabilities.APIVersions }}
  &#123;{ .Capabilities.APIVersions }}
  &#123;{ .Capabilities.APIVersions.Has }}
  &#123;{ .Capabilities.APIVersions.Has }}
Line 225: Line 275:
   Minor|GitVersion|GitCommit|GitTreeState|BuildDate|GoVersion|Compiler|Platform}}
   Minor|GitVersion|GitCommit|GitTreeState|BuildDate|GoVersion|Compiler|Platform}}
  &#123;{ .Capabilities.TillerVersion }}
  &#123;{ .Capabilities.TillerVersion }}
</font>


===Template===
===<tt>Template</tt>===
 
Contains information about the current template that is being executed:
Contains information about the current template that is being executed:
<font size=-1>
  &#123;{ .Template.Name }}
  &#123;{ .Template.Name }}
  &#123;{ .Template.BasePath }}
  &#123;{ .Template.BasePath }}
</font>
=Data Types=
{{External|https://helm.sh/docs/chart_template_guide/data_types/}}
* string: A string of text
* bool: a true or false
* int: an integer value
* float64: a 64-bit floating point value
* a byte slice (<code>[]byte</code>), used to hold potentially binary data
* struct: an object with properties and methods
* a slice (indexed list) of one of the previous types
* a string-keyed map (<code>map[string]interface{}</code>) where the value is one of the previous types.
The easiest way to debug an object's type is to pass it through <code>printf "%t"</code> in a template, which will print the type. Also see the <code>[[Helm Template Function typeOf|typeOf]]</code> and <code>[[Helm Template Function kindOf|kindOf]]</code> functions.
==Notable Values==
{{Internal|Helm Notable Values|Helm Notable Values}}
=Action vs. Functions=
Helm templates use both actions and [[#Template_Functions|functions]]. For an action, the data is simply inserted in-line. For a function, the output of a function can be passed to another function. [[#Template_Control_Structures|Template control structures]], such as <code>[[Helm_Templates#if.2Felse|if/else]]</code>, <code>[[#with|with]]</code>, <code>[[#range|range]]</code>, <code>[[Helm_Named_Templates#define|define]]</code> and <code>[[Helm_Named_Templates#template|template]]</code> are actions.


=<span id='Template_Function'></span>Template Functions=
=<span id='Template_Function'></span>Template Functions=


{{External|[https://helm.sh/docs/chart_template_guide/#template-functions-and-pipelines Template Functions and Pipelines]}}
{{External|[https://helm.sh/docs/chart_template_guide/functions_and_pipelines/#helm Template Functions and Pipelines]}}
{{External|[https://godoc.org/text/template Go Templates]}}
{{External|[https://godoc.org/text/template Go Templates]}}
{{External|[https://godoc.org/github.com/Masterminds/sprig sprig Template Functions]}}
{{External|[https://godoc.org/github.com/Masterminds/sprig sprig Template Functions]}}


A template function modifies data provided to the template via [[#Template_Object|template objects]], and it is declared inside the template, in a [[#Template_Directive|template directive]]. Template functions follow the syntax:
A template function modifies data provided to the template via [[#Template_Object|template objects]], and it is invoked inside the template, in a [[#Template_Directive|template directive]]. Template functions follow the syntax:
 
<font size=-1>
  ''functionName'' ''arg1'' ''arg2'' ...
  ''functionName'' ''arg1'' ''arg2'' ...
 
</font>
Example:
Example:
 
<font size=-1>
  &#123;{ quote .Values.color }}
  &#123;{ quote .Values.color }}
 
</font>
==Helm Template Function Reference==
==Helm Template Function Reference==
 
* <span id='default'></span><code>[[Helm Template Function Default|default]]</code>
* <span id='default'></span>[[Helm Template Function Default|default]]
* <span id='printf'></span><code>[[Helm Template Function Printf|printf]]</code>
* <span id='printf'></span>[[Helm Template Function Printf|printf]]
* <span id='quote'></span><code>[[Helm Template Function Quote|quote]]</code>
* <span id='quote'></span>[[Helm Template Function Quote|quote]]
* <span id='repeat'></span><code>[[Helm Template Function Repeat|repeat]]</code>
* <span id='repeat'></span>[[Helm Template Function Repeat|repeat]]
* <span id='upper'></span><code>[[Helm Template Function Upper|upper]]</code>
* <span id='upper'></span>[[Helm Template Function Upper|upper]]
* <code>now</code>
* now
* <code>htmlDate</code>
* htmlDate
* <code>replace</code> "+" "_"  
* replace "+" "_"  
* <code>trunc</code> 63  
* trunc 63  
* <code>trimSuffix</code> "-"  
* trimSuffix "-"  
* <code>[[Helm Template Function indent|indent]]</code>
* indent 4
* <code>[[Helm Template Function nindent|nindent]]</code>
* [[#Accessing_Array_Elements|index]]
* <code>[[#Accessing_Array_Elements|index]]</code>
* <span id='toYaml'></span><code>[[Helm Template Function toYaml|toYaml]]</code>
* <span id='list'></span><code>[[Helm Template Function List|list]]</code>
* <span id='title'></span><code>[[Helm Template Function Title|title]]</code>
* <span id='typeOf'></span><code>[[Helm Template Function typeOf|typeOf]]</code>
* <span id='typeIs'></span><code>[[Helm Template Function typeIs|typeIs]]</code>
* <span id='kindOf'></span><code>[[Helm Template Function kindOf|kindOf]]</code>
* <span id='kindIs'></span><code>[[Helm Template Function kindIs|kindIs]]</code>
* <code>[[Sprig_list#join|join]]</code>
* <span id='empty'></span><code>[[Helm Template Function empty|empty]]</code>
* <span id='int'></span><code>[[Helm Template Function int|int]]</code>
* <code>[[#Failing_in_a_Template|fail]]</code>


===Operators===
===Operators===
{{External|[https://helm.sh/docs/chart_template_guide/#operators-are-functions Operators are Functions]}}
{{External|[https://helm.sh/docs/chart_template_guide/functions_and_pipelines/#operators-are-functions Operators are Functions]}}


Operators are implemented as functions that return a boolean value:
Operators are implemented as functions that return a boolean value:
 
<font size=-1>
   &#123;{ eq Values.color "blue" }}
   &#123;{ eq Values.color "blue" }}
   &#123;{ ne }}
   &#123;{ ne }}
Line 273: Line 355:
   &#123;{ or }}
   &#123;{ or }}
   &#123;{ not }}
   &#123;{ not }}
</font>
===Storage===
* <code>[[sprig_dict#Overview|dict]]</code>
* <code>[[sprig_list#Overview|list]]</code>


===Accessing Array Elements===
===Accessing Array Elements===


To access a specific element of an array data structure, use the <tt>index</tt> function. The <tt>index</tt> function is 0-based.
To access a specific element of an array data structure, use the <code>index</code> function. The <code>index</code> function is 0-based.


Assuming we declare a simple array:
Assuming we declare a simple array:


colors:
<syntaxhighlight lang='yaml'>
  - 'blue'
colors:
  - 'red'
  - 'blue'
  - 'green'
  - 'red'
  - 'green'
</syntaxhighlight>


then the first element of the array can be accessed with:
then the first element of the array can be accessed with:


&#123;{ index .Values.colors 0 }}
<syntaxhighlight lang='yaml'>
{{ index .Values.colors 0 }}
</syntaxhighlight>


The directive is rendered to "blue".
The directive is rendered as "blue".


When the array contains maps:
When the array contains maps:


colors:
<syntaxhighlight lang='yaml'>
  - name: blue
colors:
    shade: dark
  - name: blue
  - name: red
    shade: dark
    shade: light
  - name: red
    shade: light
</syntaxhighlight>
the fields of the maps can be accessed with - note <code>(</code>...<code>)</code>:
 
<syntaxhighlight lang='yaml'>
{{ (index .Values.colors 0).name }}
</syntaxhighlight>


the fields of the maps can be accessed with - note (...):
This pattern can be extrapolated to more complex data structures.


&#123;{ (index .Values.colors 0).name }}
====Using <tt>index</tt> to Read Values whose Field Names Contain Dashes====
index can be used to access the values for those fields whose names contain dashes. For more details, see:
{{Internal|Helm_Chart_values.yaml#Field_Names_and_Dashes|values.yaml &#124; Field Names and Dashes}}


This pattern can be extrapolated to more complex data structures.
==<tt>include</tt> Function==


=Template Pipelines=
=Template Pipelines=
{{External|[https://helm.sh/docs/chart_template_guide/#pipelines Pipelines]}}
{{External|[https://helm.sh/docs/chart_template_guide/functions_and_pipelines/#pipelines Pipelines]}}
 
<font size=-1>
  &#123;{ <''object''> | <''function1''> | <''function2''>  }}
  &#123;{ <''object''> | <''function1''> | <''function2''>  }}
 
</font>
<font size=-1>
  &#123;{ .Values.color | upper | repeat 5 }}
  &#123;{ .Values.color | upper | repeat 5 }}
</font>


=Template Control Structures=
=Template Control Structures=
Line 316: Line 418:
Flow control structures are called "actions".  
Flow control structures are called "actions".  


==if/else==
==<tt>if</tt>/<tt>else</tt>==
{{Internal|Helm Template If/Else|<tt>if/else</tt>}}


{{External|[https://helm.sh/docs/chart_template_guide/#if-else if/else]}}
==<tt>with</tt>==
{{External|[https://helm.sh/docs/chart_template_guide/control_structures/#modifying-scope-using-with Modifying scope using <tt>with</tt>]}}


<tt>if</tt>/<tt>else</tt> can be used to create conditional blocks.
<code>with</code> narrows the scope of the context for better readability and more expressive template blocks.  <code>with</code> specifies a [[#Scope|scope]]:


<font color=darkgray>TODO.</font>
<syntaxhighlight lang='yaml'>
{{- with .Values.deployment }}
strategy:
  rollungUpdate:
    maxUnavailable: {{ .maxUnavailable }}
    maxSurge: {{ .maxSurge }}
revisionHistoryLimit: {{ .revisionHistoryLimit }}
minReadySeconds: {{ .minReadySeconds }}
{{- end }}
</syntaxhighlight>


==with==
<font color=darkkhaki>TODO</font>
{{External|[https://helm.sh/docs/chart_template_guide/#modifying-scope-using-with Modifying scope using <tt>with</tt>]}}


<tt>with</tt> specifies a [[#Scope|scope]].
==<tt>range</tt>==
{{Internal|Helm Template range|<tt>range</tt>}}


<font color=darkgray>TODO.</font>
==<span id='Named_Template_Actions'></span><span id='define'></span><span id='Named_Templates_and_Whitespace_Handling'></span><span id='Named_Template_Recipes'></span><span id='template_2'></span><span id='block'></span><span id='The_include_Function'></span>Named Templates (Sub-templates, Partials)==


==range==
{{Internal|Helm Named Templates|Helm Named Templates}}
{{External|[https://helm.sh/docs/chart_template_guide/#looping-with-the-range-action Looping with the <tt>range</tt> Action]}}


<tt>range</tt> provides a "for each" loop.
=<span id='Template_Variables'></span>Variables=
{{Internal|Helm Variables|Variables}}


<font color=darkgray>TODO.</font>
=<span id='Configmap_and_Secrets_Utility_Functions'></span>ConfigMap and Secrets Utility Functions=
{{Internal|Helm ConfigMap and Secrets#Overview|Helm ConfigMap and Secrets}}


==Named Template Actions==
=Debugging Templates=
 
{{External|[https://helm.sh/docs/chart_template_guide/debugging/#helm Debugging Templates]}}
{{External|[https://helm.sh/docs/chart_template_guide/#named-templates Named Templates]}}
{{External|https://github.com/databus23/schelm}}
 
<span id='Named_Template'></span><span id='Partial'></span>A '''named template''', sometimes called a '''partial''',  a '''subtemplate''' or an '''embedded template''', is a template define inside of a file, and given a names. There are two different ways to create named templates, with the [[#define|define]] action and with the [[#template|template]] action.
 
Template names are global. That means if two template are declared with the same name, whichever is loaded last will be the one that will be used. Moreover, the templates in [[Helm_Concepts#Subcharts|subcharts]], which we are not necessarily too familiar with, are compiled together with top-level templates, so it is possible to run into template name collisions we are not aware of. For this reason, it is a good practice to name your templates with unique, chart-specific names.
 
One popular naming convention is to prefix the name of each defined template with the name of the chart:
 
&#123;{ [[#define|define]] "mychart.labels" }}
 
If everyone follows the same convention, chart-specific named templates get their own namespace and that decreases the probability of conflict that may arise due to two different charts that implement templates with the same name.
 
===define===
{{External|[https://helm.sh/docs/chart_template_guide/#declaring-and-using-templates-with-define-and-template Declaring and Using Templates with <tt>define</tt>]}}
 
<tt>define</tt> declares a new named template inside of a template file, conventionally [[Helm Chart _helpers.tpl|_helpers.tpl]]:


<syntaxhighlight lang='yaml'>
<font color=darkkhaki>TODO</font>
{{/* Generate basic labels */}}
{{- define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
{{- end }}
</syntaxhighlight>


By convention, define functions should have a simple documentation block &#123;{/* ... */}} describing what they do.
=Failing in a Template=
 
The body of the partial may include directives.
 
Partials may be used instead of variables, by declaring a template that acts like a variable:


Some times, failing early and loudly with an explicit message is the best approach to invalid output.
<syntaxhighlight lang='yaml'>
<syntaxhighlight lang='yaml'>
{{/*
{{- fail "Invalid input" }}
Compute the service name that depends on the release name. This partial should be inlined every time the service name is needed.
*/}}
{{- define "myChart.postgresqlServiceName" -}}
{{- printf "%s-postgresql" .Release.Name -}}
{{- end -}}
</syntaxhighlight>
</syntaxhighlight>


When we need the service name in a template, we render it as such:
To print an argument:
 
<syntaxhighlight lang='yaml'>
<syntaxhighlight lang='yaml'>
... {{ template myChart.postgresqlServiceName . }} ...
{{- fail (printf "Invalid input: %s" .Values.someValue) }}
</syntaxhighlight>
</syntaxhighlight>


====Named Templates and Whitespace Handling====
This is an example of a conditional failure that ensures a related value is present if a primary configuration value is configured:
 
Unless controlled with '&#123;{-' and '-}}', the whitespace specified inside the partial are preserved and projected in the rendered result.
 
====Named Template Recipes====
 
{{Internal|Helm Named Template Recipes|Named Template Recipes}}
 
===template===
 
A partial defined as such can be embedded inside other template with the [[#template|template]] action:
 
<syntaxhighlight lang='yaml'>
<syntaxhighlight lang='yaml'>
kind: ConfigMap
{{- if .Values.ingress.host -}}
metadata:
{{- if not .Values.ingress.secretName }}{{ fail (printf "an ingress host is specified in %s, so a secretName must be specified as well" .Template.Name) }}{{- end }}
name: ...
{{- template "mychart.labels" }}
...
...
{{- end }}
</syntaxhighlight>
</syntaxhighlight>
<font color=darkkhaki>TO PROCESS: https://austindewey.com/2018/12/28/helm-tricks-input-validation-with-required-and-fail/</font>


When the template engine reads the file that contains the partial, it will store away the reference to "mychart.labels" until template "mychart.labels" is called. Then it will render that template inline. After rendering, the result will look like this:
=Template Recipes=
<syntaxhighlight lang='yaml'>
kind: ConfigMap
metadata:
name: ...
labels:
  generator: helm
  date: 2019-08-29
...
</syntaxhighlight>


<span id='Setting_the_Scope_of_a_Template'></span>'''Setting the Scope of a Template'''. The "template" action allows passing a [[#Scope|scope]]. By default, no scope is passed by default, so objects like .Chart.Name are not found and rendered to blank space.
==Transfer the Content of a Map from values.yaml to a Template==


To pass the scope of the calling template, use "template" as such (note the dot):
This patterns is useful to transform free-format annotations:
<syntaxhighlight lang='yaml'>
...
{{- template "mychart.labels" . }}
...
</syntaxhighlight>


In the above example, we passed the top-level (".") scope. We can pass whatever scope (template object) we want:
<code>values.yaml</code>:


<syntaxhighlight lang='yaml'>
<syntaxhighlight lang='yaml'>
...
data:
{{- template "mychart.labels" .Values }}
  color: 'red'
...
  shape: 'triangle'
</syntaxhighlight>
</syntaxhighlight>


or:
template:


<syntaxhighlight lang='yaml'>
<syntaxhighlight lang='yaml'>
...
metadata:
{{- template "mychart.labels" .Values.favorite }}
  {{- if .Values.data }}
...
  annotations:
    {{- range $key, $value := .Values.data }}
    {{ $key }}: {{ $value | quote -}}
    {{ end }}
  {{- end}}
</syntaxhighlight>
</syntaxhighlight>


===block===
For more details see: {{Internal|Helm_Template_range#Iterating_over_a_Map|<tt>range</tt>}}
{{External|[https://helm.sh/docs/chart_template_guide/#avoid-using-blocks Avoid using Blocks]}}
 
<tt>block</tt> declares a special kind of fillable template area.
 
<font color=darkgray>TODO.</font>
 
=The include Function=
{{External|[https://helm.sh/docs/chart_template_guide/#the-include-function The include Function]}}
<font color=darkgray>TODO.</font>
 
=Template Variables=
 
{{External|[https://helm.sh/docs/chart_template_guide/#variables Variables]}}
<font color=darkgray>TODO.</font>
 
=Configmap and Secrets Utility Functions=
{{External|[https://helm.sh/docs/chart_template_guide/#configmap-and-secrets-utility-functions Configmap and Secrets Utility Functions]}}
<font color=darkgray>TODO.</font>
 
=Debugging Templates=
{{External|[https://helm.sh/docs/chart_template_guide/#debugging-templates Debugging Templates]}}
{{External|https://github.com/databus23/schelm}}
 
<font color=darkgray>TODO.</font>
 
=TODO=
 


* https://helm.sh/docs/chart_best_practices/#templates
==Transfer the Content of a List from values.yaml to a Template==
* https://helm.sh/docs/developing_charts/#templates-and-values
{{Internal|Helm_Template_range#Iterating_over_a_.Values_List|Iterating over a .Values List}}
* https://helm.sh/docs/developing_charts/#know-your-template-functions
* https://helm.sh/docs/developing_charts/#quote-strings-don-t-quote-integers
* https://helm.sh/docs/developing_charts/#using-the-include-function
* https://helm.sh/docs/developing_charts/#using-the-required-function
* https://helm.sh/docs/developing_charts/#using-the-tpl-function
* https://helm.sh/docs/developing_charts/#creating-image-pull-secrets
* https://helm.sh/docs/developing_charts/#automatically-roll-deployments-when-configmaps-or-secrets-change
* https://helm.sh/docs/developing_charts/#tell-tiller-not-to-delete-a-resource
* https://helm.sh/docs/developing_charts/#using-partials-and-template-includes
* https://helm.sh/docs/chart_best_practices/#pods-and-podtemplates
* https://helm.sh/docs/chart_best_practices/#images
* https://helm.sh/docs/chart_best_practices/#imagepullpolicy
* https://helm.sh/docs/chart_best_practices/#podtemplates-should-declare-selectors

Latest revision as of 18:59, 2 March 2022

External

Internal

Overview

Templates are a set of Kubernetes parameterized manifests that form an application. They live under a chart's templates/ directory. They are written in YAML with Helm templates extensions. Upon processing by Helm, they become Kubernetes manifest files. Helm template extensions are written in the Help template language, which is based on Go templates.

The templates/ Directory

The templates/ directory contains templates that, after combination with values, in a process named "rendering", become Kubernetes manifests. Helm sends all files found in the directory through the template rendering engine, then collect the results for the files that contain manifests, and sends the rendered manifests to Kubernetes. The NOTES.txt and _helpers.tpl files are also rendered, but they are not sent to Kubernetes as manifests.

The files whose names begin with an underscore ('_') are assumed to not have a manifest inside, so they are not turned into Kubernetes API resource definitions. However, they are available everywhere within other chart templates for use. These files are conventionally used to store sub-templates and helpers. _helpers.tpl is the default location for small sub-templates. If a sub-template is large enough, it can be stored in its own '_'-prefixed file. For more details about sub-templates, see:

Helm Named Templates

Template Name

Template names do not follow a rigid naming pattern. It is, however, recommended to use the suffix .yaml for YAML files and .tpl for helpers.

Installation and De-Installation Order

During installation, Helm collects all of the resources in a given chart and its dependences, groups them by resource type, and installs them in the order specified here https://github.com/helm/helm/blob/release-2.14/pkg/tiller/kind_sorter.go#L29-L57. Upon de-installation the order is reversed: https://github.com/helm/helm/blob/release-2.14/pkg/tiller/kind_sorter.go#L62-L90

For more details on dependencies, see:

How Helm Dependencies Work

Template Comments

 # This is a comment
{{/* Generate basic labels */}}

Multi-Line Comments

 {{- /*
 This is another 
 multi-line
 comment
 */ -}}

Template Directives

A template directive, sometimes also referred as tag, is enclosed in {{ and }} blocks. It is recommended to pad the directive with space at its left and right. The simplest directive renders a value. A value is a namespaced object, where each dot (.) separates each namespaced element. A leading dot indicates that we start with the top-most namespace for the scope.

kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap

Potentially any element of the manifest, including keys, can be represented as a template directive:

apiVersion: v1
kind: Secret
stringData:
  {{ .Values.secret.fileName }}: |
    something
    ...

Directives may also include functions and other constructs.

With the exception of the multi-line comments, all directives must be specified on one line only. Space present inside a directive is irrelevant, the following formats are equivalent:

{{.Values.color}}
{{ .Values.color }}
{{            .Values.color               }}

As a matter of style, it is recommended to pad the element declared inside {{ ... }} block with a leading and a trailing space:

{{ .Values.color }}

Directives and Whitespace Handling

Controlling Whitespace

The spaces inside a directive is irrelevant, but there cannot be newlines inside a directive, all directives must be specified on a single line. The {{ and }} directive delimiters, without any other modifications, leave the template whitespace adjacent to them alone, and do not interfere with it in any way.

A hyphen '-' placed after the {{ delimiter or before the }} delimiter instructs the rendering engine to trim the whitespace preceding, respectively trailing the delimiter. Whitespace includes spaces, tabs, newline ('\n') and carriage return ('\r'). When encountering "-", the template engine will simply drop the corresponding whitespace in the respective direction, until a non-whitespace character is found.

metadata:
  color: {{ .Values.color }}
spec:

will produce, assuming that color is declared to be "blue" in values.yaml:

metadata:
  color: blue
spec:

To trim preceding whitespace, use {{-.

metadata:
  color: {{- .Values.color }}
spec:

will produce:

metadata:
  color:blue
spec:

To trim trailing whitespace (whitespace includes newlines), use -}}.

metadata:
  color: {{ .Values.color -}}
spec:

will produce:

metadata:
  color: bluespec:

which is something you most like do not want.

For details related to whitespace handling when declaring named templates, see:

Named Templates and Whitespace Handling

Scope

A scope in the context of a Helm template is is a data structure, specifically a dict instance.

The "." root scope is a dict instance that carries by default the following sub-scopes, which contain Helm built-in objects:

.
├─ Values
├─ Chart
├─ Release
├─ Capabilities
├─ Files
└─ Template

A scope must be specified when a sub-template is rendered with include or template, otherwise the built-in objects referred from the sub-template may not be resolvable.

The root scope can be manipulates as follows:

{{- $Args := dict "Name" "example" -}}
{{- $_0 :=  set . "Args" $Args -}}

Scopes are declared with with.

Template Objects

Objects are passed into a template from the template engine. The template directives can create new objects and pass them around. There are also built-in objects, which are made available by default. Objects can be simple, by having just one value, or they can contain other objects or functions. For example the Release built-in object contains several other objects, like Release.Name. The Files object contains functions.

Built-in Objects

Built-in Objects

Built-in objects are a way to access several types of values, some of which are directly configured by operators in values.yaml, while others are generated dynamically by Helm or taken from other parts of the chart. The built-in values always begin with a capital letter, based on Go's naming convention. For a fully working examples of built-in objects replacement see:

https://github.com/ovidiuf/playground/tree/master/helm/bactrosaurus

Chart

This object contains value passed into the template from the Chart.yaml file. An existing field is accessible as (note leading dot) .Chart.<UpperCasedFirstLetterFieldName>. It is important to capitalize the first letter of the field name, otherwise the directive evaluation fails.

Example:

{{ .Chart.Name }}
{{ .Chart.Version }}

Chart.Version

Values

This object provides access the effective values of all configuration elements present in the runtime configuration tree. It exposes the chart configuration to templates. The value of an existing configuration element can be access using the following syntax: (note leading dot) .Values.<fieldName>. Unlike in the Chart's case, the fields are allowed to keep their original capitalization. For example, a value declared as such in values.yaml:

size: 10

can be references in a template as:

kind: ConfigMap
...
data:
  size: {{ .Values.size }}

Values may contain structured content:

characteristics:
  size: 10
  shape: "large"

can be referenced in template as:

kind: ConfigMap
 ...
data:
  size: {{ .Values.characteristics.size }}
  shape: {{ .Values.characteristics.shape }}

While structuring data this way is possible, the recommendation is to keep values trees shallow, favoring flatness.

In case the structure contains an array, individual elements can be referred from the template with the index function.

Field Names and Dashes

Field names that contain dashes, while supported, are not rendered in a straightforward manner. For more details, see:

values.yaml | Field Names and Dashes

Release

This object describes the release itself.

Release.Name

Exposes the release name:

{{ .Release.Name }}

Release.Namespace

Exposes the namespace the release has been made into:

{{ .Release.Namespace }}

This value is specified on command line with -n|--namespace. If not specified, it explicitly defaults to "default".

Release.Revision

Exposes the release revision:

{{ .Release.Revision }}

Release.Time

Exposes the time of the release:

{{ .Release.Time }}

Release.IsUpgrade

This is set to true if the current operation is an upgrade or rollback.

{{ .Release.IsUpgrade }}

Release.IsInstall

This is set to true if the current operation is an install.

{{ .Release.IsInstall }}

Release.Service

Exposes the releasing service - always Tiller

Files

The object provide access to all non-special files in the chart. It cannot be used to access templates. For more details see:

Accessing Arbitrary Files inside Templates

Capabilities

Provides information about the capabilities of the Kubernetes cluster:

{{ .Capabilities.APIVersions }}
{{ .Capabilities.APIVersions.Has }}
{{ .Capabilities.KubeVersion }}
{{ .Capabilities.KubeVersion.Major|
 Minor|GitVersion|GitCommit|GitTreeState|BuildDate|GoVersion|Compiler|Platform}}
{{ .Capabilities.TillerVersion }}

Template

Contains information about the current template that is being executed:

{{ .Template.Name }}
{{ .Template.BasePath }}

Data Types

https://helm.sh/docs/chart_template_guide/data_types/
  • string: A string of text
  • bool: a true or false
  • int: an integer value
  • float64: a 64-bit floating point value
  • a byte slice ([]byte), used to hold potentially binary data
  • struct: an object with properties and methods
  • a slice (indexed list) of one of the previous types
  • a string-keyed map (map[string]interface{}) where the value is one of the previous types.

The easiest way to debug an object's type is to pass it through printf "%t" in a template, which will print the type. Also see the typeOf and kindOf functions.

Notable Values

Helm Notable Values

Action vs. Functions

Helm templates use both actions and functions. For an action, the data is simply inserted in-line. For a function, the output of a function can be passed to another function. Template control structures, such as if/else, with, range, define and template are actions.

Template Functions

Template Functions and Pipelines
Go Templates
sprig Template Functions

A template function modifies data provided to the template via template objects, and it is invoked inside the template, in a template directive. Template functions follow the syntax:

functionName arg1 arg2 ...

Example:

{{ quote .Values.color }}

Helm Template Function Reference

Operators

Operators are Functions

Operators are implemented as functions that return a boolean value:

 {{ eq Values.color "blue" }}
 {{ ne }}
 {{ lt }}
 {{ gt }}
 {{ and }}
 {{ or }}
 {{ not }}

Storage

Accessing Array Elements

To access a specific element of an array data structure, use the index function. The index function is 0-based.

Assuming we declare a simple array:

colors:
  - 'blue'
  - 'red'
  - 'green'

then the first element of the array can be accessed with:

{{ index .Values.colors 0 }}

The directive is rendered as "blue".

When the array contains maps:

colors:
  - name: blue
    shade: dark
  - name: red
    shade: light

the fields of the maps can be accessed with - note (...):

{{ (index .Values.colors 0).name }}

This pattern can be extrapolated to more complex data structures.

Using index to Read Values whose Field Names Contain Dashes

index can be used to access the values for those fields whose names contain dashes. For more details, see:

values.yaml | Field Names and Dashes

include Function

Template Pipelines

Pipelines

{{ <object> | <function1> | <function2>  }}

{{ .Values.color | upper | repeat 5 }}

Template Control Structures

Flow control structures are called "actions".

if/else

if/else

with

Modifying scope using with

with narrows the scope of the context for better readability and more expressive template blocks. with specifies a scope:

{{- with .Values.deployment }}
strategy:
  rollungUpdate:
    maxUnavailable: {{ .maxUnavailable }}
    maxSurge: {{ .maxSurge }}
revisionHistoryLimit: {{ .revisionHistoryLimit }}
minReadySeconds: {{ .minReadySeconds }}
{{- end }}

TODO

range

range

Named Templates (Sub-templates, Partials)

Helm Named Templates

Variables

Variables

ConfigMap and Secrets Utility Functions

Helm ConfigMap and Secrets

Debugging Templates

Debugging Templates
https://github.com/databus23/schelm

TODO

Failing in a Template

Some times, failing early and loudly with an explicit message is the best approach to invalid output.

{{- fail "Invalid input" }}

To print an argument:

{{- fail (printf "Invalid input: %s" .Values.someValue) }}

This is an example of a conditional failure that ensures a related value is present if a primary configuration value is configured:

{{- if .Values.ingress.host -}}
{{- if not .Values.ingress.secretName }}{{ fail (printf "an ingress host is specified in %s, so a secretName must be specified as well" .Template.Name) }}{{- end }}
...
{{- end }}

TO PROCESS: https://austindewey.com/2018/12/28/helm-tricks-input-validation-with-required-and-fail/

Template Recipes

Transfer the Content of a Map from values.yaml to a Template

This patterns is useful to transform free-format annotations:

values.yaml:

data:
  color: 'red'
  shape: 'triangle'

template:

metadata:
  {{- if .Values.data }}
  annotations:
    {{- range $key, $value := .Values.data }}
    {{ $key }}: {{ $value | quote -}}
    {{ end }}
  {{- end}}

For more details see:

range

Transfer the Content of a List from values.yaml to a Template

Iterating over a .Values List