Working With Templates¶
When deploying a new application or scheduled task, shpkpr renders the application or task definition (typically a JSON document) using Jinja2. This allows the user to interpolate values into the template prior to deployment if required. This is particularly useful for keeping configuration or secret data out of your codebase and injecting it just in time prior to the deployment.
Passing values to a template¶
For the following sections we’ll use the shpkpr apps deploy
command as an example (the documentation that follows applies equally to other commands that render templates too), assuming the following environment variables have been set (so we don’t have to repeat them in the examples below):
$ export SHPKPR_MARATHON_URL=http://my.marathon.install.mydomain.com
And that we’re using the Standard default template. The only required values for this template are MARATHON_APP_ID
(the id of the application to be deployed) and DOCKER_REPOTAG
(the image and tag for the docker container to be deployed).
shpkpr offers two methods of passing values to a template at deploy time, both are described below:
On the command line¶
The most straightforward way to provide values for a template in shpkpr is on the command line. Each one of the commands which renders a template (currently shpkpr apps deploy
and shpkpr cron set
) accepts an arbitrary list of key=value
pairs which are passed directly to the template being rendered:
$ shpkpr apps deploy MARATHON_APP_ID=my-app DOCKER_REPOTAG=my.docker.registry/my-image:latest
Note
When passing template values on the command line, they must always be passed after any options (those parameters starting with -
or --
).
In the environment¶
It may be more convenient to use environment variables to pass values to a template, so shpkpr supports this method too. When reading values from the environment, shpkpr respects the value of the -e/--env-prefix
option (defaults to SHPKPR_
). This means that if you wanted to set the values for the required variables in the standard template you would set them like so:
$ export SHPKPR_MARATHON_APP_ID=my-app
$ export SHPKPR_DOCKER_REPOTAG=my.docker.registry/my-image:latest
You can then deploy with a simple:
$ shpkpr apps deploy
If you prefer not to prefix your environment variables and just slurp up all of them, you can do:
$ export MARATHON_APP_ID=my-app
$ export DOCKER_REPOTAG=my.docker.registry/my-image:latest
$ shpkpr apps deploy -e ""
Handling Missing Values¶
When rendering a template, if a value does not have a default and is not provided by the user, then shpkpr will exit with a non-zero exit code and display a helpful error message.
$ shpkpr apps deploy --marathon-url http://marathon MARATHON_APP_ID=my-app
Error: Unable to render template: 'DOCKER_REPOTAG' is undefined
Writing Templates¶
The Jinja2 documentation is the best place to go to find general advice on syntax, features and how the language works, so we’ll only cover shpkpr specific bits in this section.
Formatting¶
Currently, shpkpr only supports JSON templates. That is, after rendering the template must be valid JSON or shpkpr will exit with a non-zero exit code.
Given the following template:
{
"id": "my-app",
"cpus": 0.1,
"mem": 512,
"instances": 1,
"cmd": {{CMD}}
}
shpkpr will raise an error here due to the lack of quote-marks around {{CMD}}
which would result in invalid JSON (as shpkpr escapes quote-marks in interpolated values).
Currently shpkpr doesn’t produce particularly helpful error messages here, but that will be fixed in a future release.
$ shpkpr apps deploy --marathon-url http://marathon MARATHON_APP_ID=my-app -t resources/usage/templates/broken.json.tmpl CMD="echo Hello!"
Error: Unable to parse rendered template as JSON, check variables
Using Filters¶
From the Jinja docs:
Variables can be modified by filters. Filters are separated from the variable by a pipe symbol (
|
) and may have optional arguments in parentheses. Multiple filters can be chained. The output of one filter is applied to the next.For example,
{{ name|striptags|title }}
will remove all HTML Tags from variable`name
and title-case the output (title(striptags(name))
).Filters that accept arguments have parentheses around the arguments, just like a function call. For example:
{{ listx|join(', ') }}
will join a list with commas (str.join(', ', listx)
).
In addition to the built-in filters shipped by Jinja, shpkpr provides a number of additional filters to assist with writing your templates, from validation/typing to text transformation.
-
shpkpr.template_filters.
filter_items
(value, startswith=None, strip_prefix=False)¶ Jinja2 filter used to filter a dictionary’s keys by specifying a required prefix.
Returns a list of key/value tuples.
{{ my_dict|filter_items }} {{ my_dict|filter_items("MY_PREFIX_") }} {{ my_dict|filter_items("MY_PREFIX_", True) }}
This is most useful in combination with the special _all_env variable that shpkpr injects into every template. For example, to iterate over only the template variables that start with
LABEL_
you could do:{% for k, v in _all_env|filter_items("LABEL_", strip_prefix=True) %} "{{k}}": "{{v}}", {% endfor %}
-
shpkpr.template_filters.
require_int
(*args, **kwargs)¶ Jinja2 filter used to enforce an integer type in a template.
Accepts optional min/max constraints.
{{ my_integer_value|require_int }} {{ my_integer_value|require_int(min=0) }} {{ my_integer_value|require_int(min=0, max=20) }} {{ my_integer_value|require_int(max=20) }}
-
shpkpr.template_filters.
require_float
(*args, **kwargs)¶ Jinja2 filter used to enforce an float type in a template.
Accepts optional min/max constraints.
{{ my_float_value|require_float }} {{ my_float_value|require_float(min=0) }} {{ my_float_value|require_float(min=0, max=20) }} {{ my_float_value|require_float(max=20) }}
-
shpkpr.template_filters.
slugify
(value, *args, **kwargs)¶ Jinja2 filter used to slugify a string.
All arguments are passed directly to python-slugify’s
slugify
function, see the library’s documentation for further information.{{ my_string|slugify }} {{ my_string|slugify(max_length=20) }}
Dictionary access to all variables¶
Sometimes it’s useful to be able to access all template variables in a single dictionary object. This allows for iteration over the variables, or indirect access (using the value of another variable as the key).
shpkpr provides the special _all_env
variable for this purpose. It can be used as follows:
{# Iterate over all template variables beginning with "LABEL_" #}
{% for k, v in _all_env|filter_items("LABEL_") %}
"{{k}}": "{{v}}",
{% endfor %}
{# Regular dictionary-style access is available if necessary #}
{{ _all_env[SOME_VARIABLE] }}