docs(chart_tempalte_primer): add template primer

This adds a detailed document on writing templates in Helm. This is NOT
a best-practices guide, but a thorough introduction to templates in
Helm.
parent 92be174a
......@@ -57,6 +57,20 @@ including installing pre-releases.
- [Chart Repository Guide](docs/chart_repository.md)
- [Syncing your Chart Repository](docs/chart_repository_sync_example.md)
- [Signing Charts](docs/provenance.md)
- [Chart Template Developer's Guide](docs/chart_template_guide/index.md)
- [Getting Started with Templates](docs/chart_template_guide/getting_started.md)
- [Built-in Objects](docs/chart_template_guide/builtin_objects.md)
- [Values Files](docs/chart_template_guide/values_files.md)
- [Functions and Pipelines](docs/chart_template_guide/functions_and_pipelines.md)
- [Flow Control (if/else, with, range, whitespace management)](docs/chart_template_guide/control_structures.md)
- [Variables](docs/chart_template_guide/variables.md)
- [Named Templates (Partials)](docs/chart_template_guide/named_templates.md)
- [Accessing Files Inside Templates](docs/chart_template_guide/accessing_files.md)
- [Creating a NOTES.txt File](docs/chart_template_guide/notes_files.md)
- [Subcharts and Global Values](docs/chart_template_guide/subcharts_and_globals.md)
- [Debugging Templates](docs/chart_template_guide/debugging.md)
- [Wrapping Up](docs/chart_template_guide/wrapping_up.md)
- [Appendix: Go Data Types](docs/chart_template_guide/data_types.md)
- [Architecture](docs/architecture.md)
- [Developers](docs/developers.md)
- [History](docs/history.md)
......
# Accessing Files Inside Templates
In the previous section we looked at several ways to create and access named templates. This makes it easy to import one template from within another template. But sometimes it is desirable to import a _file that is not a template_ and inject its contents without sending the contents through the template renderer.
Helm provides access to files through the `.Files` object. Before we get going with the template examples, though, there are a few things to note about how this works:
- It is okay to add extra files to your Helm chart. These files will be bundled and set to Tiller. Be careful, though. Charts must be smaller than 1M because of the storage limitations of Kubernetes objects.
- Some files cannot be accessed through the `.Files` object, usually for security reasons.
- Files in `templates/` cannot be accessed.
- Charts to not preserve UNIX mode information, so file-level permissions will have no impact on the availability of a file when it comes to the `.Files` object.
With those caveats behind, let's write a template that reads three files into our ConfigMap. To get started, we will add three files to the chart, putting all three directly inside of the `mychart/` directory.
`config1.toml`:
```toml
message = Hello from config 1
```
`config2.toml`:
```toml
message = This is config 2
```
`config3.toml`:
```toml
message = Goodbye from config 3
```
Each of these is a simple TOML file (think old-school Windows INI files). We know the names of these files, so we can use a `range` function to loop through them and inject their contents into our ConfigMap.
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Release.Name}}-configmap
data:
{{- $files := .Files}}
{{- range tuple "config1.toml" "config2.toml" "config3.toml" }}
{{.}}: |-
{{ $files.Get . }}
{{- end }}
```
This config map uses several of the techniques discussed in previous sections. For example, we create a `$files` variable to hold a reference to the `.Files` object. We also use the `tuple` function to create a list of files that we loop through. Then we print each file name (`{{.}}: |-`) followed by the contents of the file `{{ $files.Get . }}`.
Running this template will produce a single ConfigMap with the contents of all three files:
```yaml
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: quieting-giraf-configmap
data:
config1.toml: |-
message = Hello from config 1
config2.toml: |-
message = This is config 2
config3.toml: |-
message = Goodbye from config 3
```
When working with a Secret resource, you can import a file and have the template base-64 encode it for you:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: {{.Release.Name}}-secret
type: Opaque
data:
token: |-
{{.Files.Get "config1.toml" | b64enc}}
```
The above will take the same `config1.toml` file we used before and encode it:
```yaml
# Source: mychart/templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: lucky-turkey-secret
type: Opaque
data:
token: |-
bWVzc2FnZSA9IEhlbGxvIGZyb20gY29uZmlnIDEK
```
Currently, there is no way to pass files external to the chart during `helm install`. So if you are asking users to supply data, it must be loaded using `helm install -f` or `helm install --set`.
This discussion wraps up our dive into the tools and techniques for writing Helm templates. In the next section we will see how you can use one special file, `templates/NOTES.txt`, to send post-installation instructions to the users of your chart.
# Built-in Objects
Objects are passed into a template from the template engine. And your code can pass objects around (we'll see examples when we look at the `with` and `range` statements). There are even a few ways to create new objects within your templates, like with the `tuple` function we'll see later.
Objects can be simple, and have just one value. Or they can contain other objects or functions. For example. the `Release` object contains several objects (like `Release.Name`) and the `Files` object has a few functions.
In the previous section, we use `{{.Release.Name}}` to insert the name of a release into a template. `Release` is one of four top-level objects that you can access in your templates.
- `Release`: This object describes the release itself. It has several objects inside of it:
- `Release.Name`: The release name
- `Release.Time`: The time of the release
- `Release.Namespace`: The namespace to be released into (if the manifest doesn't override)
- `Release.Service`: The name of the releasing service (always `Tiller`).
- `Values`: Values passed into the template from the `values.yaml` file and from user-supplied files. By default, `Values` is empty.
- `Chart`: The contents of the `Chart.yaml` file. Any data in `Chart.yaml` will be accessible here. For example `{{.Chart.Name}}-{{.Chart.Version}}` will print out the `mychart-0.1.0`.
- The available fields are listed in the [Charts Guide](charts.md)
- `Files`: This provides access to all non-special files in a chart. While you cannot use it to access templates, you can use it to access other files in the chart. See the section _Accessing Files_ for more.
- `Files.Get` is a function for getting a file by name (`.Files.Get config.ini`)
- `Files.GetBytes` is a function for getting the contents of a file as an array of bytes instead of as a string. This is useful for things like images.
The values are available to any top-level template. As we will see later, this does not necessarily mean that they will be available _everywhere_.
The built-in values always begin with a capital letter. This is in keeping with Go's naming convention. When you create your own names, you are free to use a convention that suits your team. Some teams, like the [Kubernetes Charts](https://github.com/kubernetes/charts) team, choose to use only initial lower case letters in order to distinguish local names from those built-in. In this guide, we follow that convention.
This diff is collapsed.
# Appendix: Go Data Types and Templates
The Helm template language is implemented in the strongly typed Go programming language. For that reason, variables in templates are _typed_. For the most part, variables will be exposed as one of the following types:
- string: A string of text
- bool: a `true` or `false`
- int: An integer value (there are also 8, 16, 32, and 64 bit signed and unsigned variants of this)
- float64: a 64-bit floating point value (there are also 8, 16, and 32 bit varieties of this)
- a byte slice (`[]byte`), often 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
There are many other types in Go, and sometimes you will have to convert between them in your templates. 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.
\ No newline at end of file
# Debugging Templates
Debugging templates can be tricky simply because the templates are rendered on the Tiller server, not the Helm client. And then the rendered templates are sent to the Kubernetes API server, which may reject the YAML files for reasons other than formatting.
There are a few commands that can help you debug.
- `helm lint` is your go-to tool for verifying that your chart follows best practices
- `helm install --dry-run --debug`: We've seen this trick already. It's a great way to have the server render your templates, then return the resulting manifest file.
- `helm get manifest`: This is a good way to see what templates are installed on the server.
When your YAML is failing to parse, but you want to see what is generated, one
easy way to retrieve the YAML is to commet out the problem section in the template,
and then re-run `helm install --dry-run --debug`:
```YAML
apiVersion: v1
# some: problem section
# {{ .Values.foo | quote }}
```
The above will be rendered and returned with the comments intact:
```yaml
```YAML
apiVersion: v1
# some: problem section
# "bar"
```
This provides a quick way of viewing the generated content without YAML parse
errors blocking.
# Template Functions and Pipelines
So far, we've seen how to place information into a template. But that information is placed into the template unmodified. Sometimes we want to transform the supplied data in a way that makes it more useable to us.
Let's start with a best practice: When injecting strings from the `.Values` object into the template, we ought to quote these strings. We can do that by calling the `quote` function in the template directive:
```
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Release.Name}}-configmap
data:
myvalue: "Hello World"
drink: {{quote .Values.favorite.drink}}
food: {{quote .Values.favorite.food}}
```
Template functions follow the syntax `functionName arg1 arg2...`. In the snippet above, `quote .Values.favorite.drink` calls the `quote` function and passes it a single argument.
Helm has over 60 available functions. Some of them are defined by the [Go template language](https://godoc.org/text/template) itself. Most of the others are part of the [Spring template library](https://godoc.org/github.com/Masterminds/sprig). We'll see many of them as we progress through the examples.
> While we talk about the "Helm template language" as if it is Helm-specific, it is actually a combination of the Go template language, some extra functions, and a variety of wrappers to expose certain objects to the templates. Many resources on Go templates may be helpful as you learn about templating.
## Pipelines
One of the powerful features of the template language is its concept of _pipelines_. Drawing on a concept from UNIX, pipelines are a tool for chaining together a series of template commands to compactly express a series of transformations. In other words, pipelines are an efficient way of getting several things done in sequence. Let's rewrite the above example using a pipeline.
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Release.Name}}-configmap
data:
myvalue: "Hello World"
drink: {{.Values.favorite.drink | quote}}
food: {{.Values.favorite.food | quote}}
```
In this example, instead of calling `quote ARGUMENT`, we inverted the order. We "sent" the argument to the function using a pipeline (`|`): `.Values.favorite.drink | quote`. Using pipelines, we can chain several functions together:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Release.Name}}-configmap
data:
myvalue: "Hello World"
drink: {{.Values.favorite.drink | quote}}
food: {{.Values.favorite.food | upper | quote}}
```
> Inverting the order is a common practice in templates. You will see `.val | quote` more often than `quote .val`. Either practice is fine.
When evaluated, that template will produce this:
```yaml
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: trendsetting-p-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
```
Note that our original `pizza` has now been transformed to `"PIZZA"`.
When pipelining arguments like this, the result of the first evaluation (`.Values.favorite.drink`) is sent as the _last argument to the function_. We can modify the drink example above to illustrate with a function that takes two arguments: `repeat COUNT STRING`:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Release.Name}}-configmap
data:
myvalue: "Hello World"
drink: {{.Values.favorite.drink | repeat 5 | quote}}
food: {{.Values.favorite.food | upper | quote}}
```
The `repeat` function will echo the given string the given number of times, so we will get this for output:
```yaml
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: melting-porcup-configmap
data:
myvalue: "Hello World"
drink: "coffeecoffeecoffeecoffeecoffee"
food: "PIZZA"
```
## Using the `default` function
One function frequently used in templates is the `default` function: `default DEFAULT_VALUE GIVEN_VALUE`. This function allows you to specify a default value inside of the template, in case the value is omitted. Let's use it to modify the drink example above:
```yaml
drink: {{.Values.favorite.drink | default "tea" | quote}}
```
If we run this as normal, we'll get our `coffee`:
```
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: virtuous-mink-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
```
Now, we will remove the favorite drink setting from `values.yaml`:
```yaml
favorite:
#drink: coffee
food: pizza
```
Now re-running `helm install --dry-run --debug ./mychart` will produce this YAML:
```yaml
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: fair-worm-configmap
data:
myvalue: "Hello World"
drink: "tea"
food: "PIZZA"
```
It is considered good (almost mandatory) practice to set defaults with `default` for any object that originates from `.Values`. (In some places, an `if` conditional guard may be better suited. We'll see those in the next section.)
Template functions and pipelines are a powerful way to transform information and then insert it into your YAML. But sometimes it's necessary to add some template logic that is a little more sophisticated than just inserting a string. In the next section we will look at the control structures provided by the template language.
## Operators are functions
For templates, the operators (`eq`, `ne`, `lt`, `gt`, `and`, `or` and so on) are all implemented as functions. In pipelines, operations can be grouped with parentheses (`(`, and `)`).
Now we can turn from functions and pipelines to flow control with conditions, loops, and scope modifiers.
# Getting Started with a Chart Template
In this section of the guide, we'll create a chart and then add a first template. The chart we created here will be used throughout the rest of the guide.
To get going, let's take a brief look at a Helm chart.
## Charts
As described in the [Charts Guide](../charts.md), Helm charts are structured like
this:
```
mychart/
Chart.yaml
values.yaml
charts/
templates/
...
```
The `templates/` directory is for template files. When Tiller evaluates a chart,
it will send all of the files in the `templates/` directory through the
template rendering engine. Tiller then collects the results of those templates
and sends them on to Kubernetes.
The `values.yaml` file is also important to templates. This file contains the
_default values_ for a chart. These values may be overridden by users during
`helm install` or `helm upgrade`.
The `Charts.yaml` file contains a description of the chart. You can access it
from within a template. The `charts/` directory _may_ contain other charts (which
we call _subcharts_). Later in this guide we will see how those work when it
comes to template rendering.
## A Starter Chart
For this guide, we'll create a simple chart called `mychart`, and then we'll
create some templates inside of the chart.
```console
$ helm create mychart
Creating mychart
```
From here on, we'll be working in the `mychart` directory.
### A Quick Glimps of `mychart/templates/`
If you take a look at the `mychart/templates/` directory, you'll notice a few files
already there.
- `NOTES.txt`: The "help text" for your chart. This will be displayed to your users
when they run `helm install`.
- `deployment.yaml`: A basic manifest for creating a Kubernetes [deployment](http://kubernetes.io/docs/user-guide/deployments/)
- `service.yaml`: A basic manifest for creating a [service endpoint](http://kubernetes.io/docs/user-guide/services/) for your deployment
- `_helpers.tpl`: A place to put template helpers that you can re-use throughout the chart
And what we're going to do is... _remove them all!_ That way we can work through our tutorial from scratch. We'll actually create our own `NOTES.txt` and `_helpers.tpl` as we go.
```console
$ rm -rf mychart/templates/*.*
```
When you're writing production grade charts, having basic versions of these charts can be really useful. So in your day-to-day chart authoring, you probably won't want to remove them.
## A First Template
The first template we are going to create will be a `ConfigMap`. In Kubernetes,
a ConfigMap is simply a container for storing configuration data. Other things,
like pods, can access the data in a ConfigMap.
Because ConfigMaps are basic resources, they make a great starting point for us.
Let's begin by creating a file called `mychart/templates/configmap.yaml`:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mychart-configmap
data:
myvalue: "Hello World"
```
**TIP:** Template names do not follow a rigid naming pattern. However, we recommend
using the suffix `.yaml` for YAML files and `.tpl` for helpers.
The YAML file above is a bare-bones ConfigMap, having the minimal necessary fields.
In virtue of the fact that this file is in the `templates/` directory, it will
be sent through the template engine.
It is just fine to put a plain YAML file like this in the `templates/` directory.
When Tiller reads this template, it will simply send it to Kubernetes as-is.
With this simple template, we now have an installable chart. And we can install
it like this:
```console
$ helm install ./mychart
NAME: full-coral
LAST DEPLOYED: Tue Nov 1 17:36:01 2016
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/ConfigMap
NAME DATA AGE
mychart-configmap 1 1m
```
In the output above, we can see that our ConfigMap was created. Using Helm, we
can retrieve the release and see the actual template that was loaded.
```console
$ helm get manifest full-coral
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mychart-configmap
data:
myvalue: "Hello World"
```
The `helm get manifest` command takes a release name (`full-coral`) and prints
out all of the Kubernetes resources that were uploaded to the server. Each file
begins with `---` to indicate the start of a YAML document, and then is followed
by an automatically generated comment line that tells us what template file
generated this YAML document.
From there on, we can see that the YAML data is exactly what we put in our
`configmap.yaml` file.
Now we can delete our release: `helm delete full-coral`.
### Adding a Simple Template Call
Hard-coding the `name:` into a resource is usually considered to be bad practice.
Names should be unique to a release. So we might want to generate a name field
by inserting the release name.
**TIP:** The `name:` field is limited to 24 characters because of limitations to
the DNS system (long story). For that reason, release names are limited to
14 characters.
Let's alter `configmap.yaml` accordingly.
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Release.Name}}-configmap
data:
myvalue: "Hello World"
```
The big change comes in the value of the `name:` field, which is now
`{{.Release.Name}}-configmap`.
> A template directive is enclosed in `{{` and `}}` blocks.
The template directive `{{.Release.Name}}` injects the release name into the template. The values that are passed into a template can be thought of as _namespaced objects_, where a dot (`.`) separates each namespaced element.
The leading dot before `Release` indicates that we start with the top-most namespace for this scope (we'll talk about scope in a bit). So we could read `.Release.Name` as "start at the top namespace, find the `Release` object, then look inside of it for an object called `Name`".
The `Release` object is one of the built-in objects for Helm, and we'll cover it in more depth later. But for now, it is sufficient to say that this will display the release name that Tiller assigns to our release.
Now when we install our resource, we'll immediately see the result of using this template directive:
```console
$ helm install ./mychart
NAME: clunky-serval
LAST DEPLOYED: Tue Nov 1 17:45:37 2016
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/ConfigMap
NAME DATA AGE
clunky-serval-configmap 1 1m
```
Note that in the `RESOURCES` section, the name we see there is `clunky-serval-configmap`
instead of `mychart-configmap`.
You can run `helm get manifest clunky-serval` to see the entire generated YAML.
At this point, we've seen templates are their most basic: YAML files that have template directives embedded in `{{` and `}}`. In the next part, we'll take a deeper look into templates. But before moving on, there's one quick trick that can make building templates faster: When you want to test the template rendering, but not actually install anything, you can use `helm install --debug --dry-run ./mychart`. This will send the chart to the Tiller server, which will render the templates. But instead of installing the chart, it will return the rendered template to you so you can see the output:
```console
$ helm install --debug --dry-run ./mychart
SERVER: "localhost:44134"
CHART PATH: /Users/mattbutcher/Code/Go/src/k8s.io/helm/_scratch/mychart
NAME: goodly-guppy
TARGET NAMESPACE: default
CHART: mychart 0.1.0
MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: goodly-guppy-configmap
data:
myvalue: "Hello World"
```
Using `--dry-run` will make it easier to test your code, but it won't ensure that Kubernetes itself will accept the templates you generate. It's best not to assume that your chart will install just because `--dry-run` works.
In the next few sections, we'll take the basic chart we defined here and explore the Helm template language in detail. And we'll get started with built-in objects.
\ No newline at end of file
# The Chart Template Developer's Guide
This guide provides an introduction to Helm's chart templates, with emphasis on
the template language.
Templates generate manifest files, which are YAML-formatted resource descriptions
that Kubernetes can understand. We'll look at how templates are structured,
how they can be used, how to write Go templates, and how to debug your work.
This guide focuses on the following concepts:
- The Helm template language
- Using values
- Techniques for working with templates
This guide is oriented toward learning the ins and outs of the Helm template language. Other guides provide introductory material, examples, and best practices.
\ No newline at end of file
This diff is collapsed.
# Creating a NOTES.txt File
In this section we are going to look at Helm's tool for providing instructions to your chart users. At the end of a `chart install` or `chart upgrade`, Helm can print out a block of helpful information for users. This information is highly customizable using templates.
To add installation notes to your chart, simply create a `templates/NOTES.txt` file. This file is plain text, but it is processed like as a template, and has all the normal template functions and objects available.
Let's create a simple `NOTES.txt` file:
```
Thank you for installing {{.Chart.Name}}.
Your release is named {{.Release.Name}}.
To learn more about the release, try:
$ helm status {{.Release.Name}}
$ helm get {{.Release.Name}}
```
Now if we run `helm install ./mychart` we will see this message at the bottom:
```
RESOURCES:
==> v1/Secret
NAME TYPE DATA AGE
rude-cardinal-secret Opaque 1 0s
==> v1/ConfigMap
NAME DATA AGE
rude-cardinal-configmap 3 0s
NOTES:
Thank you for installing mychart.
Your release is named rude-cardinal.
To learn more about the release, try:
$ helm status rude-cardinal
$ helm get rude-cardinal
```
Using `NOTES.txt` this way is a great way to give your users detailed information about how to use their newly installed chart. Creating a `NOTES.txt` file is strongly recommended, though it is not required.
\ No newline at end of file
# Subcharts and Global Values
To this point we have been working only with one chart. But charts can have dependencies, called _subcharts_, that also have their own values and templates. In this section we will create a subchart and see the different ways we can access values from within templates.
Before we dive into the code, there are a few important details to learn about subcharts.
1. A subchart is considered "stand-alone", which means a subchart can never explicitly depend on its parent chart.
2. For that reason, a subchart cannot access the values of its parent.
3. A parent chart can override values for subcharts.
4. Helm has a concept of _global values_ that can be accessed by all charts.
As we walk through the examples in this section, many of these concepts will become clearer.
## Creating a Subchart
For these exercises, we'll start with the `mychart/` chart we created at the beginning of this guide, and we'll add a new chart inside of it.
```console
$ cd mychart/charts
$ helm create mysubchart
Creating mysubchart
$ rm -rf mysubchart/templates/*.*
```
Notice that just as before, we deleted all of the base templates so that we can start from scratch. In this guide, we are focused on how templates work, not on managing dependencies. But the [Charts Guide](charts.md) has more information on how subcharts work.
## Adding Values and a Template to the Subchart
Next, let's create a simple template and values file for our `mysubchart` chart. There should already be a `values.yaml` in `mychart/charts/subchart`. We'll set it up like this:
```yaml
dessert: cake
```
Next, we'll create a new ConfigMap template in `mychart/charts/subchart/templates/configmap.yaml`:
```
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Release.Name}}-cfgmap2
data:
dessert: {{.Values.dessert}}
```
Because every subchart is a _stand-alone chart_, we can test `mysubchart` on its own:
```console
$ helm install --dry-run --debug mychart/charts/mysubchart
SERVER: "localhost:44134"
CHART PATH: /Users/mattbutcher/Code/Go/src/k8s.io/helm/_scratch/mychart/charts/mysubchart
NAME: newbie-elk
TARGET NAMESPACE: default
CHART: mysubchart 0.1.0
MANIFEST:
---
# Source: mysubchart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: newbie-elk-cfgmap2
data:
dessert: cake
```
## Overriding Values from a Parent Chart
Our original chart, `mychart` is now the _parent_ chart of `mysubchart`. This relationship is based entirely on the fact that `mysubchart` is within `mychart/charts`.
Because `mychart` is a parent, we can specify configuration in `mychart` and have that configuration pushed into `mysubchart`. For example, we can modify `mychart/values.yaml` like this:
```yaml
favorite:
drink: coffee
food: pizza
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions
mysubchart:
dessert: ice cream
```
Note the last two lines. Any directives inside of the `mysubchart` section will be sent to the `mysubchart` chart. So if we run `helm install --dry-run --debug mychart`, once of the things we will see is the `mysubchart` ConfigMap:
```yaml
# Source: mychart/charts/mysubchart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: unhinged-bee-cfgmap2
data:
dessert: ice cream
```
The value at the top level has now overridden the value of the subchart.
There's an important detail to notice here. We didn't change the template of `mychart/charts/mysubchart/templates/configmap.yaml` to point to `.Values.mysubchart.dessert`. From that template's perspective, the value is still located at `.Values.dessert`. As the template engine passes values along, it sets the scope. So for the `mysubchart` templates, only values specifically for `mysubchart` will be available in `.Values`.
Sometimes, though, you do want certain values to be available to all of the templates. This is accomplished using global chart values.
## Global Chart Values
Global values are values that can be accessed from any chart or subchart by exactly the same name. Globals require explicit declaration. You can't use an existing non-global as if it were a global.
The Values data type has a reserved section called `Values.global` where global values can be set. Let's set one in our `mychart/values.yaml` file.
```yaml
favorite:
drink: coffee
food: pizza
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions
mysubchart:
dessert: ice cream
global:
salad: caesar
```
Because of the way globals work, both `mychart/templates/configmap.yaml` and `mysubchart/templates/configmap.yaml` should be able to access that value as `{{.Values.global.salad}}`.
`mychart/templates/configmap.yaml`:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Release.Name}}-configmap
data:
salad: {{.Values.global.salad}}
```
`mysubchart/tempaltes/configmap.yaml`:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Release.Name}}-cfgmap2
data:
dessert: {{.Values.dessert}}
salad: {{.Values.global.salad}}
```
Now if we run a dry run install, we'll see the same value in both outputs:
```yaml
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: silly-snake-configmap
data:
salad: caesar
---
# Source: mychart/charts/mysubchart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: silly-snake-cfgmap2
data:
dessert: ice cream
salad: caesar
```
Globals are useful for passing information like this, though it does take some planning to make sure the right templates are configured to use globals.
## Sharing Templates with Subcharts
Parent charts and subcharts can share templates. This can become very powerful when coupled with `block`s. For example, we can define a `block` in the `subchart` ConfigMap like this:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Release.Name}}-cfgmap2
{{block "labels" .}}from: mysubchart{{end}}
data:
dessert: {{.Values.dessert}}
salad: {{.Values.global.salad}}
```
Running this would produce:
```yaml
# Source: mychart/charts/mysubchart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: gaudy-mastiff-cfgmap2
from: mysubchart
data:
dessert: ice cream
salad: caesar
```
Note that the `from:` line says `mysubchart`. In a previous section, we created `mychart/templates/_helpers.tpl`. Let's define a new named template there called `labels` to match the declaration on the block above.
```yaml
{{- define "labels" }}from: mychart{{end}}
```
Recall how the labels on templates are _globally shared_. That means that if we create a block named `labels` in one chart, and then define an override named `labels` in another chart, the override will be applied.
Now if we do a `helm install --dry-run --debug mychart`, it will override the block:
```yaml
# Source: mychart/charts/mysubchart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: nasal-cheetah-cfgmap2
from: mychart
data:
dessert: ice cream
salad: caesar
```
Now `from:` is set to `mychart` because the block was overridden.
Using this method, you can write flexible "base" charts that can be added as subcharts to many different charts, and which will support selective overriding using blocks.
This section of the guide has focused on subcharts. We've seen how to inherit values, how to use global values, and how to override templates with blocks. In the next section we will turn to debugging, and learn how to catch errors in templates.
# Values Files
In the previous section we looked at the built-n objects that Helm templates offer. One of the four built-in objects is `Values`. This object provides access to values passed into the chart. Its contents come from four sources:
- The `values.yaml` file in the chart
- If this is a subchart, the `values.yaml` file of a parent chart
- A values file if passed into `helm install` or `helm update` with the `-f` flag (`helm install -f myvals.yaml ./mychart`)
- Individual parameters passed with `--set` (such as `helm install --set foo=bar ./mychart`)
The list above is in order of specificity: `values.yaml` is the default, and can be overridden by a parent chart's `values.yaml`, which can in turn be overridden by a user-supplied values file or `--set` parameters.
Values files are plain YAML files. Let's edit `mychart/values.yaml` and then edit our ConfigMap template.
Removing the defaults in `values.yaml`, we'll set just one parameter:
```yaml
favoriteDrink: coffee
```
Now we can use this inside of a template:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Release.Name}}-configmap
data:
myvalue: "Hello World"
drink: {{.Values.favoriteDrink}}
```
Notice on the last line we access `favoriteDrink` as an attribute of `Values`: `{{.Values.favoriteDrink}}`.
Let's see how this renders.
```console
$ helm install --dry-run --debug ./mychart
SERVER: "localhost:44134"
CHART PATH: /Users/mattbutcher/Code/Go/src/k8s.io/helm/_scratch/mychart
NAME: geared-marsupi
TARGET NAMESPACE: default
CHART: mychart 0.1.0
MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: geared-marsupi-configmap
data:
myvalue: "Hello World"
drink: coffee
```
Because `favoriteDrink` is set in the default `values.yaml` file to `coffee`, that's the value displayed in the template. We can easily override that by adding a `--set` flag in our call to `helm install`:
```
helm install --dry-run --debug --set favoriteDrink=slurm ./mychart
SERVER: "localhost:44134"
CHART PATH: /Users/mattbutcher/Code/Go/src/k8s.io/helm/_scratch/mychart
NAME: solid-vulture
TARGET NAMESPACE: default
CHART: mychart 0.1.0
MANIFEST:
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: solid-vulture-configmap
data:
myvalue: "Hello World"
drink: slurm
```
Since `--set` has a higher precedence than the default `values.yaml` file, our template generates `drink: slurm`.
Values files can contain more structured content, too. For example, we could create a `favorite` section in our `values.yaml` file, and then add several keys there:
```yaml
favorite:
drink: coffee
food: pizza
```
Now we would have to modify the template slightly:
```
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Release.Name}}-configmap
data:
myvalue: "Hello World"
drink: {{.Values.favorite.drink}}
food: {{.Values.favorite.food}}
```
While structuring data this way is possible, the recommendation is that you keep your values trees shallow, favoring flatness. When we look at assigning values to subcharts, we'll see how values are named using a tree structure.
At this point, we've seen several built-in objects, and used them to inject information into a template. Now we will take a look at another aspect of the template engine: functions and pipelines.
# Variables
With functions, pipelines, objects, and control structures under our belts, we can turn to one of the more basic ideas in many programming languages: variables. In templates, they are less frequently used. But we will see how to use them to simply code, and to make better use of `with` and `range`.
In an earlier example, we saw that this code will fail:
```yaml
{{- with .Values.favorite }}
drink: {{.drink | default "tea" | quote}}
food: {{.food | upper | quote}}
release: {{.Release.Name}}
{{- end }}
```
`Release.Name` is not inside of the scope that's restricted in the `with` block. One way to work around scoping issues is to assign objects to variables that can be accessed without respect to the present scope.
In Helm templates, a variable is a named reference to another object. It follows the form `$name`. Variables are assigned with a special assignment operator: `:=`. We can rewrite the above to use a variable for `Release.Name`.
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Release.Name}}-configmap
data:
myvalue: "Hello World"
{{- $relname := .Release.Name -}}
{{- with .Values.favorite }}
drink: {{.drink | default "tea" | quote}}
food: {{.food | upper | quote}}
release: {{$relname}}
{{- end }}
```
Notice that before we start the `with` block, we assign `$relname := .Release.Name`. Now inside of the `with` block, the `$relname` variable still points to the release name.
Running that will produce this:
```yaml
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: viable-badger-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
release: viable-badger
```
Variables are particularly useful in `range` loops. They can be used on list-like objects to capture both the index and the value:
```yaml
toppings: |-
{{- range $index, $topping := .Values.pizzaToppings }}
{{$index}}: {{$topping}}
{{- end }}
```
Note that `range` comes first, then the variables, then the assignment operator, then the list. This will assign the integer index (starting from zero) to `$index` and the value to `$topping`. Running it will produce:
```yaml
toppings: |-
0: mushrooms
1: cheese
2: peppers
3: onions
```
For data structures that have both a key and a value, we can use `range` to get both. For example, we can loop through `.Values.favorite` like this:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{.Release.Name}}-configmap
data:
myvalue: "Hello World"
{{- range $key, $val := .Values.favorite }}
{{$key}}: {{$val | quote}}
{{- end}}
```
Now on the first iteration, `$key` will be `drink` and `$val` will be `coffee`, and on the second, `$key` will be `food` and `$val` will be `pizza`. Running the above will generate this:
```yaml
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: eager-rabbit-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "pizza"
```
Variables are not "global". They are scoped to the block in which they are declared. Earlier, we assigned `$relname` in the top level of the template. That variable will be in scope for the entire template. But in our last example, `$key` and `$val` will only be in scope inside of the `{{range...}}{{end}}` block.
So far we have looked at just one template declared in just one file. But one of the powerful features of the Helm template language is its ability to declare multiple templates and use them together. We'll turn to that in the next section.
\ No newline at end of file
# Wrapping Up
This guide is intended to give you, the chart developer, a strong understanding of how to use Helm's template language. The guide focuses on the technical aspects of template development.
But there are many things this guide has not covered when it comes to the practical day-to-day development of charts. Here are some useful pointers to other documentation that will help you as you create new charts:
- The [Kubernetes Charts project](https://github.com/kubernetes/charts) is an indispensable source of charts. That project is also sets the standard for best practices in chart development.
- The Kubernetes [User's Guide](http://kubernetes.io/docs/user-guide/) provides detailed examples of the various resource kinds that you can use, from ConfigMaps and Secrets to DaemonSets and Deployments.
- The Helm [Charts Guide](../charts.md) explains the workflow of using charts.
- The Helm [Chart Hooks Guide](../charts_hooks.md) explains how to create lifecycle hooks.
- The Helm [Charts Tips and Tricks](../charts_tips_and_tricks) article provides some useful tips for writing charts.
- The [Sprig documentation](https://github.com/Masterminds/sprig) documents more than sixty of the template functions.
- The [Go template docs](https://godoc.org/text/template) explain the template syntax in detail.
- The [Schelm tool](https://github.com/databus23/schelm) is a nice helper utility for debugging charts.
Sometimes it's easier to ask a few questions and get answers from experienced developers. The best place to do that is in the Kubernetes `#Helm` Slack channel:
- [Kubernetes Slack](https://slack.k8s.io/): `#helm`
Finally, if you find errors or omissions in this document, want to suggest some new content, or would like to contribute, visit [The Helm Project](https://github.com).
\ No newline at end of file
......@@ -3,6 +3,28 @@
This guide covers some of the tips and tricks Helm chart developers have
learned while building production-quality charts.
## Know Your Template Functions
Helm uses [Go templates](https://godoc.org/text/template) for templating
your resource files. While Go ships several built-in functions, we have
added many others.
First, we added almost all of the functions in the
[Sprig library](https://godoc.org/github.com/Masterminds/sprig). We removed two
for security reasons: `env` and `expandenv` (which would have given chart authors
access to Tiller's environment).
We also added one special template function: `include`. The `include` function
allows you to bring in another template, and then pass the results to other
template functions.
For example, this template snippet includes a template called `mytpl.tpl`, then
lowercases the result, then wraps that in double quotes.
```yaml
value: {{include "mytpl.tpl" . | lower | quote}}
```
## Quote Strings, Don't Quote Integers
When you are working with string data, you are always safer quoting the
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment