salt.renderers.jinja

Jinja loading utils to enable a more powerful backend for jinja templates

Jinja in States

The most basic usage of Jinja in state files is using control structures to wrap conditional or redundant state elements:

{% if grains['os'] != 'FreeBSD' %}
tcsh:
    pkg:
        - installed
{% endif %}

motd:
  file.managed:
    {% if grains['os'] == 'FreeBSD' %}
    - name: /etc/motd
    {% elif grains['os'] == 'Debian' %}
    - name: /etc/motd.tail
    {% endif %}
    - source: salt://motd

In this example, the first if block will only be evaluated on minions that aren't running FreeBSD, and the second block changes the file name based on the os grain.

Writing if-else blocks can lead to very redundant state files however. In this case, using pillars, or using a previously defined variable might be easier:

{% set motd = ['/etc/motd'] %}
{% if grains['os'] == 'Debian' %}
  {% set motd = ['/etc/motd.tail', '/var/run/motd'] %}
{% endif %}

{% for motdfile in motd %}
{{ motdfile }}:
  file.managed:
    - source: salt://motd
{% endfor %}

Using a variable set by the template, the for loop will iterate over the list of MOTD files to update, adding a state block for each file.

Include and Import

Includes and imports can be used to share common, reusable state configuration between state files and between files.

{% from 'lib.sls' import test %}

This would import the test template variable or macro, not the test state element, from the file lib.sls. In the case that the included file performs checks again grains, or something else that requires context, passing the context into the included file is required:

{% from 'lib.sls' import test with context %}

Macros

Macros are helpful for eliminating redundant code, however stripping whitespace from the template block, as well as contained blocks, may be necessary to emulate a variable return from the macro.

# init.sls
{% from 'lib.sls' import pythonpkg with context %}

python-virtualenv:
  pkg.installed:
    - name: {{ pythonpkg('virtualenv') }}

python-fabric:
  pkg.installed:
    - name: {{ pythonpkg('fabric') }}
# lib.sls
{% macro pythonpkg(pkg) -%}
  {%- if grains['os'] == 'FreeBSD' -%}
    py27-{{ pkg }}
  {%- elif grains['os'] == 'Debian' -%}
    python-{{ pkg }}
  {%- endif -%}
{%- endmacro %}

This would define a macro that would return a string of the full package name, depending on the packaging system's naming convention. The whitespace of the macro was eliminated, so that the macro would return a string without line breaks, using whitespace control.

Template Inheritance

Template inheritance works fine from state files and files. The search path starts at the root of the state tree or pillar.

Filters

Saltstack extends builtin filters with these custom filters:

strftime

Converts any time related object into a time based string. It requires a valid strftime directives. An exhaustive list can be found in the official Python documentation.

{% set curtime = None | strftime() %}

Fuzzy dates require the timelib Python module is installed.

{{ "2002/12/25"|strftime("%y") }}
{{ "1040814000"|strftime("%Y-%m-%d") }}
{{ datetime|strftime("%u") }}
{{ "tomorrow"|strftime }}
sequence
Ensure that parsed data is a sequence.
yaml_encode

Serializes a single object into a YAML scalar with any necessary handling for escaping special characters. This will work for any scalar YAML data type: ints, floats, timestamps, booleans, strings, unicode. It will not work for multi-objects such as sequences or maps.

{%- set bar = 7 %}
{%- set baz = none %}
{%- set zip = true %}
{%- set zap = 'The word of the day is "salty"' %}

{%- load_yaml as foo %}
bar: {{ bar|yaml_encode }}
baz: {{ baz|yaml_encode }}
baz: {{ zip|yaml_encode }}
baz: {{ zap|yaml_encode }}
{%- endload %}

In the above case {{ bar }} and {{ foo.bar }} should be identical and {{ baz }} and {{ foo.baz }} should be identical.

yaml_dquote

Serializes a string into a properly-escaped YAML double-quoted string. This is useful when the contents of a string are unknown and may contain quotes or unicode that needs to be preserved. The resulting string will be emitted with opening and closing double quotes.

{%- set bar = '"The quick brown fox . . ."' %}
{%- set baz = 'The word of the day is "salty".' %}

{%- load_yaml as foo %}
bar: {{ bar|yaml_dquote }}
baz: {{ baz|yaml_dquote }}
{%- endload %}

In the above case {{ bar }} and {{ foo.bar }} should be identical and {{ baz }} and {{ foo.baz }} should be identical. If variable contents are not guaranteed to be a string then it is better to use yaml_encode which handles all YAML scalar types.

yaml_squote
Similar to the yaml_dquote filter but with single quotes. Note that YAML only allows special escapes inside double quotes so yaml_squote is not nearly as useful (viz. you likely want to use yaml_encode or yaml_dquote).

Jinja in Files

Jinja can be used in the same way in managed files:

# redis.sls
/etc/redis/redis.conf:
    file.managed:
        - source: salt://redis.conf
        - template: jinja
        - context:
            bind: 127.0.0.1
# lib.sls
{% set port = 6379 %}
# redis.conf
{% from 'lib.sls' import port with context %}
port {{ port }}
bind {{ bind }}

As an example, configuration was pulled from the file context and from an external template file.

Note

Macros and variables can be shared across templates. They should not be starting with one or more underscores, and should be managed by one of the following tags: macro, set, load_yaml, load_json, import_yaml and import_json.

Escaping Jinja

Occasionally, it may be necessary to escape Jinja syntax. There are two ways to to do this in Jinja. One is escaping individual variables or strings and the other is to escape entire blocks.

To escape a string commonly used in Jinja syntax such as {{, you can use the following syntax:

{{ '{{' }}

For larger blocks that contain Jinja syntax that needs to be escaped, you can use raw blocks:

{% raw %]
    some text that contains jinja characters that need to be escaped
{% endraw %}

See the Escaping section of Jinja's documentation to learn more.

A real-word example of needing to use raw tags to escape a larger block of code is when using file.managed with the contents_pillar option to manage files that contain something like consul-template, which shares a syntax subset with Jinja. Raw blocks are necessary here because the Jinja in the pillar would be rendered before the file.managed is ever called, so the Jinja syntax must be escaped:

{% raw %}
- contents_pillar: |
    job "example-job" {
      <snipped>
      task "example" {
          driver = "docker"

          config {
              image = "docker-registry.service.consul:5000/example-job:{{key "nomad/jobs/example-job/version"}}"
      <snipped>
{% endraw %}

Calling Salt Functions

The Jinja renderer provides a shorthand lookup syntax for the salt dictionary of execution function.

New in version 2014.7.0.

# The following two function calls are equivalent.
{{ salt['cmd.run']('whoami') }}
{{ salt.cmd.run('whoami') }}

Debugging

The show_full_context function can be used to output all variables present in the current Jinja context.

New in version 2014.7.0.

Context is: {{ show_full_context() }}
salt.renderers.jinja.render(template_file, saltenv='base', sls='', argline='', context=None, tmplpath=None, **kws)

Render the template_file, passing the functions and grains into the Jinja rendering system.

Return type:string
class salt.utils.jinja.SerializerExtension(environment)

Yaml and Json manipulation.

Format filters

Allows jsonifying or yamlifying any data structure. For example, this dataset:

data = {
    'foo': True,
    'bar': 42,
    'baz': [1, 2, 3],
    'qux': 2.0
}
yaml = {{ data|yaml }}
json = {{ data|json }}
python = {{ data|python }}

will be rendered as:

yaml = {bar: 42, baz: [1, 2, 3], foo: true, qux: 2.0}
json = {"baz": [1, 2, 3], "foo": true, "bar": 42, "qux": 2.0}
python = {'bar': 42, 'baz': [1, 2, 3], 'foo': True, 'qux': 2.0}

The yaml filter takes an optional flow_style parameter to control the default-flow-style parameter of the YAML dumper.

{{ data|yaml(False) }}

will be rendered as:

bar: 42
baz:
  - 1
  - 2
  - 3
foo: true
qux: 2.0

Load filters

Strings and variables can be deserialized with load_yaml and load_json tags and filters. It allows one to manipulate data directly in templates, easily:

{%- set yaml_src = "{foo: it works}"|load_yaml %}
{%- set json_src = "{'bar': 'for real'}"|load_json %}
Dude, {{ yaml_src.foo }} {{ json_src.bar }}!

will be rendered as:

Dude, it works for real!

Load tags

Salt implements import_yaml and import_json tags. They work like the import tag, except that the document is also deserialized.

Syntaxes are {% load_yaml as [VARIABLE] %}[YOUR DATA]{% endload %} and {% load_json as [VARIABLE] %}[YOUR DATA]{% endload %}

For example:

{% load_yaml as yaml_src %}
    foo: it works
{% endload %}
{% load_json as json_src %}
    {
        "bar": "for real"
    }
{% endload %}
Dude, {{ yaml_src.foo }} {{ json_src.bar }}!

will be rendered as:

Dude, it works for real!

Import tags

External files can be imported and made available as a Jinja variable.

{% import_yaml "myfile.yml" as myfile %}
{% import_json "defaults.json" as defaults %}
{% import_text "completeworksofshakespeare.txt" as poems %}

Catalog

import_* and load_* tags will automatically expose their target variable to import. This feature makes catalog of data to handle.

for example:

# doc1.sls
{% load_yaml as var1 %}
    foo: it works
{% endload %}
{% load_yaml as var2 %}
    bar: for real
{% endload %}
# doc2.sls
{% from "doc1.sls" import var1, var2 as local2 %}
{{ var1.foo }} {{ local2.bar }}