This is the multi-page printable view of this section.
Click here to print.
Return to the regular view of this page.
Templating
Templating Engine.
kluctl uses a Jinja2 Templating engine to pre-process/render every involved configuration file and resource before
actually interpreting it. Only files that are explicitly excluded via
.templateignore files
are not rendered via Jinja2.
Generally, everything that is possible with Jinja2 is possible in kluctl configuration/resources. Please
read into the
Jinja2 documentation
to understand what exactly
is possible and how to use it.
.templateignore
In some cases it is required to exclude specific files from templating, for example when the contents conflict with
the used template engine (e.g. Go templates conflict with Jinja2 and cause errors). In such cases, you can place
a .templateignore
beside the excluded files or into a parent folder of it. The contents/format of the .templateignore
file is the same as you would use in a .gitignore
file.
Includes and imports
Standard Jinja2
includes
and
imports
can be used in all templates.
The path given to include/import is searched in the directory of the root template and all it’s parent directories up
until the project root. Please note that the search path is not altered in included templates, meaning that it will
always search in the same directories even if an include happens inside a file that was included as well.
To include/import a file relative to the currently rendered file (which is not necessarily the root template), prefix
the path with ./
, e.g. use {% include "./my-relative-file.j2" %}"
.
Macros
Jinja2 macros
are fully supported. When writing
macros that produce yaml resources, you must use the ---
yaml separator in case you want to produce multiple resources
in one go.
Why no Go Templating
kluctl started as a python project and was then migrated to be a Go project. In the python world, Jinja2 is the obvious
choice when it comes to templating. In the Go world, of course Go Templates would be the first choice.
When the migration to Go was performed, it was a conscious and opinionated decision to stick with Jinja2 templating.
The reason is that I (@codablock) believe that Go Templates are hard to read and write and at the same time quite limited
in their features (without extensive work). It never felt natural to write Go Templates.
This “feeling” was confirmed by multiple users of kluctl when it started and users described as “relieving” to not
be forced to use Go Templates.
The above is my personal experience and opinion. I’m still quite open for contributions in regard to Go Templating
support, as long as Jinja2 support is kept.
2 - Variable Sources
Available variable sources.
There are multiple places in deployment projects (deployment.yaml) where additional variables can be loaded into
future Jinja2 contexts.
The first place where vars can be specified is the deployment root, as documented
here
.
These vars are visible for all deployments inside the deployment project, including sub-deployments from includes.
The second place to specify variables is in the deployment items, as documented
here
.
The variables loaded for each entry in vars
are not available inside the deployment.yaml
file itself.
However, each entry in vars
can use all variables defined before that specific entry is processed. Consider the
following example.
vars:
- file: vars1.yaml
- file: vars2.yaml
- file: optional-vars.yaml
ignoreMissing: true
- file: default-vars.yaml
noOverride: true
- file: vars3.yaml
when: some.var == "value"
- file: vars3.yaml
sensitive: true
vars2.yaml
can now use variables that are defined in vars1.yaml
. A special case is the use of previously defined
variables inside
values
vars sources. Please see the documentation of
values
for details.
At all times, variables defined by
parents of the current sub-deployment project can be used in the current vars file.
Each variable source can have the optional field ignoreMissing
set to true
, causing Kluctl to ignore if the source
can not be found.
When specifying noOverride: true
, Kluctl will not override variables from the previously loaded variables. This is
useful if you want to load default values for variables.
Variables can also be loaded conditionally by specifying a condition via when: <condition>
. The condition must be in
the same format as described in
conditional deployment items
Specifying sensitive: true
causes the Webui to redact the underlying variables for non-admin users. This will be set
to true
by default for all variable sources that usually load sensitive data, including sops encrypted files and
Kubernetes secrets.
Different types of vars entries are possible:
file
This loads variables from a yaml file. Assume the following yaml file with the name vars1.yaml
:
my_vars:
a: 1
b: "b"
c:
- l1
- l2
This file can be loaded via:
After which all included deployments and sub-deployments can use the jinja2 variables from vars1.yaml
.
Kluctl also supports variable files encrypted with
SOPS
. See the
sops integration
integration for more details.
values
An inline definition of variables. Example:
vars:
- values:
a: 1
b: c
These variables can then be used in all deployments and sub-deployments.
In case you need to use variables defined in previous vars sources, the values
var source needs some special handling
in regard to templating. It’s important to understand that the deployment project is rendered BEFORE any vars source
processing is performed, which means that it will fail to render when you use previously defined variables in a values
vars source. To still use previously defined variables, surround the values
vars source with {% raw %}
and {% endraw %}
.
In addition, the template expressions must be wrapped with "
, as otherwise the loading of the deployment project
will fail shortly after rendering due to YAML parsing errors.
vars:
- values:
a: 1
b: c
{% raw %}
- values:
c: "{{ a }}"
{% endraw %}
An alternative syntax is to use a template expression that itself outputs a template expression:
vars:
- values:
a: 1
b: c
- values:
c: {{ '{{ a }}' }}
The advantage of the second method is that the type (number) of a
is preserved, while the first method would convert
it into a string.
git
This loads variables from a git repository. Example:
vars:
- git:
url: ssh://git@github.com/example/repo.git
ref:
branch: my-branch
path: path/to/vars.yaml
The ref field has the same format at found in
Git includes
Kluctl also supports variable files encrypted with
SOPS
. See the
sops integration
integration for more details.
clusterConfigMap
Loads a configmap from the target’s cluster and loads the specified key’s value into the templating context. The value
is treated and loaded as YAML and thus can either be a simple value or a complex nested structure. In case of a simple
value (e.g. a number), you must also specify targetPath
.
The referred ConfigMap must already exist while the Kluctl project is loaded, meaning that it is not possible to use
a ConfigMap that is deployed as part of the Kluctl project itself.
Assume the following ConfigMap to be already deployed to the target cluster:
apiVersion: v1
kind: ConfigMap
metadata:
name: my-vars
namespace: my-namespace
data:
vars: |
a: 1
b: "b"
c:
- l1
- l2
This ConfigMap can be loaded via:
vars:
- clusterConfigMap:
name: my-vars
namespace: my-namespace
key: vars
The following example uses a simple value:
apiVersion: v1
kind: ConfigMap
metadata:
name: my-vars
namespace: my-namespace
data:
value: 123
This ConfigMap can be loaded via:
vars:
- clusterConfigMap:
name: my-vars
namespace: my-namespace
key: value
targetPath: deep.nested.path
clusterSecret
Same as clusterConfigMap, but for secrets.
http
The http variables source allows to load variables from an arbitrary HTTP resource by performing a GET (or any other
configured HTTP method) on the URL. Example:
vars:
- http:
url: https://example.com/path/to/my/vars
The above source will load a variables file from the given URL. The file is expected to be in yaml or json format.
The following additional properties are supported for http sources:
method
Specifies the HTTP method to be used when requesting the given resource. Defaults to GET
.
body
The body to send along with the request. If not specified, nothing is sent.
A map of key/values pairs representing the header entries to be added to the request. If not specified, nothing is added.
jsonPath
Can be used to select a nested element from the yaml/json document returned by the HTTP request. This is useful in case
some REST api is used which does not directly return the variables file. Example:
vars:
- http:
url: https://example.com/path/to/my/vars
jsonPath: $[0].data
The above example would successfully use the following json document as variables source:
[{"data": {"vars": {"var1": "value1"}}}]
Authentication
Kluctl currently supports BASIC and NTLM authentication. It will prompt for credentials when needed.
awsSecretsManager
AWS Secrets Manager
integration. Loads a variables YAML from an AWS Secrets
Manager secret. The secret can either be specified via an ARN or via a secretName and region combination. An existing AWS
config profile can also be specified.
The secrets stored in AWS Secrets manager must contain a valid yaml or json file.
Example using an ARN:
vars:
- awsSecretsManager:
secretName: arn:aws:secretsmanager:eu-central-1:12345678:secret:secret-name-XYZ
profile: my-prod-profile
Example using a secret name and region:
vars:
- awsSecretsManager:
secretName: secret-name
region: eu-central-1
profile: my-prod-profile
The advantage of the latter is that the auto-generated suffix in the ARN (which might not be known at the time of
writing the configuration) doesn’t have to be specified.
vault
Vault by HashiCorp
with
Tokens
authentication integration. The address and the path to the secret can be configured.
The implementation was tested with KV Secrets Engine.
Example using vault:
vars:
- vault:
address: http://localhost:8200
path: secret/data/simple
Before deploying please make sure that you have access to vault. You can do this for example by setting
the environment variable VAULT_TOKEN
.
systemEnvVars
Load variables from environment variables. Children of systemEnvVars
can be arbitrary yaml, e.g. dictionaries or lists.
The leaf values are used to get a value from the system environment.
Example:
vars:
- systemEnvVars:
var1: ENV_VAR_NAME1
someDict:
var2: ENV_VAR_NAME2
someList:
- var3: ENV_VAR_NAME3
The above example will make 3 variables available: var1
, someDict.var2
and
someList[0].var3
, each having the values of the environment variables specified by the leaf values.
All specified environment variables must be set before calling kluctl unless a default value is set. Default values
can be set by using the ENV_VAR_NAME:default-value
form.
Example:
vars:
- systemEnvVars:
var1: ENV_VAR_NAME4:defaultValue
The above example will set the variable var1
to defaultValue
in case ENV_VAR_NAME4 is not set.
All values retrieved from environment variables (or specified as default values) will be treated as YAML, meaning that
integers and booleans will be treated as integers/booleans. If you want to enforce strings, encapsulate the values in
quotes.
Example:
vars:
- systemEnvVars:
var1: ENV_VAR_NAME5:'true'
The above example will treat true
as a string instead of a boolean. When the environment variable is set outside
kluctl, it should also contain the quotes. Please note that your shell might require escaping to properly pass quotes.
3 - Filters
Available filters.
In addition to the
builtin Jinja2 filters
,
kluctl provides a few additional filters:
b64encode
Encodes the input value as base64. Example: {{ "test" | b64encode }}
will result in dGVzdA==
.
b64decode
Decodes an input base64 encoded string. Example {{ my.source.var | b64decode }}
.
from_yaml
Parses a yaml string and returns an object. Please note that json is valid yaml, meaning that you can also use this
filter to parse json.
to_yaml
Converts a variable/object into its yaml representation. Please note that in most cases the resulting string will not
be properly indented, which will require you to also use the indent
filter. Example:
apiVersion: v1
kind: ConfigMap
metadata:
name: my-config
data:
config.yaml: |
{{ my_config | to_yaml | indent(4) }}
to_json
Same as to_yaml
, but with json as output. Please note that json is always valid yaml, meaning that you can also use
to_json
in yaml files. Consider the following example:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
spec:
template:
spec:
containers:
- name: c1
image: my-image
env: {{ my_list_of_env_entries | to_json }}
This would render json into a yaml file, which is still a valid yaml file. Compare this to how this would have to be
solved with to_yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
spec:
template:
spec:
containers:
- name: c1
image: my-image
env:
{{ my_list_of_env_entries | to_yaml | indent(10) }}
The required indention filter is the part that makes this error-prone and hard to maintain. Consider using to_json
whenever you can.
render
Renders the input string with the current Jinja2 context. Example:
{% set a="{{ my_var }}" %}
{{ a | render }}
sha256(digest_len)
Calculates the sha256 digest of the input string. Example:
{{ "some-string" | sha256 }}
digest_len
is an optional parameter that allows to limit the length of the returned hex digest. Example:
{{ "some-string" | sha256(6) }}
slugify
Slugify a string based on
python-slugify
.
4 - Functions
Available functions.
In addition to the provided
builtin global functions
,
kluctl also provides a few global functions:
load_template(file)
Loads the given file into memory, renders it with the current Jinja2 context and then returns it as a string. Example:
{% set a=load_template('file.yaml') %}
{{ a }}
load_template
uses the same path searching rules as described in
includes/imports
.
load_sha256(file, digest_len)
Loads the given file into memory, renders it and calculates the sha256 hash of the result.
The filename given to load_sha256
is treated the same as in load_template
. Recursive loading/calculating of hashes
is allowed and is solved by replacing load_sha256
invocations with currently loaded templates with dummy strings.
This also allows to calculate the hash of the currently rendered template, for example:
apiVersion: v1
kind: ConfigMap
metadata:
name: my-config-{{ load_sha256("configmap.yaml") }}
data:
digest_len
is an optional parameter that allows to limit the length of the returned hex digest.
get_var(field_path, default)
Convenience method to navigate through the current context variables via a
JSON Path
. Let’s assume you currently have these variables defined
(e.g. via
vars
):
Then {{ get_var('my.deep.var', 'my-default') }}
would return value
.
When any of the elements inside the field path are non-existent, the given default value is returned instead.
The field_path
parameter can also be a list of pathes, which are then tried one after the another, returning the first
result that gives a value that is not None. For example, {{ get_var(['non.existing.var', my.deep.var'], 'my-default') }}
would also return value
.
merge_dict(d1, d2)
Clones d1 and then recursively merges d2 into it and returns the result. Values inside d2 will override values in d1.
update_dict(d1, d2)
Same as merge_dict
, but merging is performed in-place into d1.
raise(msg)
Raises a python exception with the given message. This causes the current command to abort.
debug_print(msg)
Prints a line to stderr.
time.now()
Returns the current time. The returned object has the following members:
member |
description |
t.as_timezone(tz) |
Converts and returns the time t in the given timezone. Example:
{{ time.now().as_timezone("Europe/Berlin") }} |
t.weekday() |
Returns the time’s weekday. 0 means Monday and 6 means Sunday. |
t.hour() |
Returns the time’s hour from 0-23. |
t.minute() |
Returns the time’s minute from 0-59. |
t.second() |
Returns the time’s second from 0-59. |
t.nanosecond() |
Returns the time’s nanosecond from 0-999999999. |
t + delta |
Adds a delta to t . Example: {{ time.now() + time.second * 10 }} |
t - delta |
Subtracts a delta from t . Example: {{ time.now() - time.second * 10 }} |
t1 < t2 t1 >= t2 … |
Time objects can be compared to other time objects. Example:
{% if time.now() < time.parse_iso("2022-10-01T10:00") %}...{% endif %} All logical operators are supported. |
time.utcnow()
Returns the current time in UTC.
The object has the same members as described in
time.now()
.
time.parse_iso(iso_time_str)
Parse the given string and return a time object. The string must be in ISO time.
The object has the same members as described in
time.now()
.
time.second, time.minute, time.hour
Represents a time delta to be used with t + delta
and t - delta
. Example
{{ time.now() + time.minute * 10 }}