Working with Jinja2 Templates
1. Creating Template Files
| Property | Description | Example |
|---|---|---|
| template_file | Path to the Jinja2 template file | templates/web_config.j2 |
| dest | Destination path where the rendered file will be written | /etc/nginx/conf.d/app.conf |
| vars | Variables to pass into the template context | {'app_port': 8080, 'ssl_enabled': true} |
Example: Basic Template File Creation
{% raw %}# templates/web_config.j2
upstream {{ app_name }} {
server 127.0.0.1:{{ app_port }};
}
server {
listen {{ server_port }};
server_name {{ domain_name }};
location / {
proxy_pass http://{{ app_name }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}{% endraw %}
Example: Using Variables in Templates
{% raw %}# playbook.yml
- name: Deploy web config from template
hosts: webservers
tasks:
- name: Render nginx config
template:
src: web_config.j2
dest: /etc/nginx/conf.d/{{ app_name }}.conf
vars:
app_port: 8080
server_port: 443
domain_name: example.com
app_name: myapp{% endraw %}
Note: Template files must end with
.j2 extension to be recognized by Ansible.
2. Using Template Module
| Parameter | Description | Default | Notes |
|---|---|---|---|
| src | Path to the template file | Required | Must be a valid Jinja2 template |
| dest | Path where the rendered file will be created | Required | Can be a directory or full path |
| backup | Creates a backup of the destination file before overwriting | false |
Useful for rollback in case of errors |
| mode | Permissions for the rendered file | 0644 |
Must be a valid octal permission string |
| owner | Owner of the rendered file | root |
Applies to Unix-like systems |
| group | Group of the rendered file | root |
Applies to Unix-like systems |
Example: Basic Template Module Usage
- name: Render a configuration file from template
ansible.builtin.template:
src: web_config.j2
dest: /etc/nginx/conf.d/web.conf
mode: '0644'
owner: root
group: root
Example: Template with Variables
- name: Render a config with variables
ansible.builtin.template:
src: app_config.j2
dest: /opt/app/config.json
mode: '0640'
vars:
app_port: 8080
app_host: "localhost"
Note: The
template module uses Jinja2 syntax for templating. Variables are referenced using {{ variable_name }} in the template file.
Warning: Ensure that the
dest path is writable and has appropriate permissions to avoid rendering failures.
3. Accessing Variables
| Variable Type | Access Syntax | Description |
|---|---|---|
| Host Variables | hostvars[inventory_hostname] |
Access variables defined for a specific host |
| Group Variables | group_vars[group_name] |
Access variables defined for a group |
| Host Facts | hostvars[inventory_hostname].ansible_facts |
Access system facts collected by setup module |
| Role Variables | role_var_name |
Access variables defined in a role's vars/ directory |
| Extra Vars | vars['var_name'] |
Access variables passed via command line or inventory |
Example: Accessing Host Variables
{% for host in groups['webservers'] %}
{{ hostvars[host].ansible_facts.cpu_model }}
{% endfor %}
4. Using Filters
| Filter | Description | Example |
|---|---|---|
| upper | Converts a string to uppercase | {{ "hello world"|upper }} |
| lower | Converts a string to lowercase | {{ "HELLO WORLD"|lower }} |
| capitalize | Capitalizes the first letter of a string | {{ "hello world"|capitalize }} |
| title | Converts a string to title case | {{ "hello world"|title }} |
| reverse | Reverses a string or list | {{ "hello"|reverse }} |
| join | Joins a list into a string with a delimiter | {{ ['a', 'b', 'c']|join(',') }} |
| split | Splits a string into a list using a delimiter | {{ "a,b,c"|split(',') }} |
| default | Provides a default value if the variable is undefined or empty | {{ undefined_var|default('default value') }} |
| replace | Replaces occurrences of a substring with another | {{ "hello world"|replace('world', 'Ansible') }} |
| truncate | Truncates a string to a specified length | {{ "This is a long string"|truncate(10) }} |
| first | Returns the first item in a list | {{ ['a', 'b', 'c']|first }} |
| last | Returns the last item in a list | {{ ['a', 'b', 'c']|last }} |
| length | Returns the length of a string or list | {{ "hello"|length }} |
| sort | Sorts a list | {{ [3, 1, 2]|sort }} |
| unique | Removes duplicate items from a list | {{ [1, 2, 2, 3]|unique }} |
| reverse | Reverses the order of a list | {{ [1, 2, 3]|reverse }} |
| batch | Splits a list into smaller lists of a given size | {{ [1, 2, 3, 4, 5]|batch(2) }} |
| select | Selects items from a list that match a condition | {{ [1, 2, 3, 4]|select('odd') }} |
| reject | Rejects items from a list that match a condition | {{ [1, 2, 3, 4]|reject('odd') }} |
| map | Applies a filter to each item in a list | {{ [1, 2, 3]|map('upper') }} |
| sum | Sums all items in a list (numeric values only) | {{ [1, 2, 3]|sum }} |
| min | Returns the smallest value in a list | {{ [1, 2, 3]|min }} |
| max | Returns the largest value in a list | {{ [1, 2, 3]|max }} |
| abs | Returns the absolute value of a number | {{ -5|abs }} |
| round | Rounds a number to the nearest integer | {{ 3.14|round }} |
| int | Converts a value to an integer | {{ "123"|int }} |
| float | Converts a value to a float | {{ "123.45"|float }} |
| bool | Converts a value to a boolean | {{ "true"|bool }} |
| list | Converts a value to a list | {{ "hello"|list }} |
| dict | Converts a value to a dictionary | {{ "key: value"|dict }} |
| items | Converts a dictionary into a list of key-value pairs | {{ {'a': 1, 'b': 2}|items }} |
| keys | Extracts keys from a dictionary | {{ {'a': 1, 'b': 2}|keys }} |
| values | Extracts values from a dictionary | {{ {'a': 1, 'b': 2}|values }} |
| groupby | Groups a list by a key function | {{ [{'name': 'Alice', 'age': 30}, {'name': 'Bob', 'age': 30}]|groupby('age') }} |
| selectattr | Selects items from a list based on attribute value | {{ [{'name': 'Alice', 'age': 30}, {'name': 'Bob', 'age': 25}]|selectattr('age', 'ge', 30) }} |
| rejectattr | Rejects items from a list based on attribute value | {{ [{'name': 'Alice', 'age': 30}, {'name': 'Bob', 'age': 25}]|rejectattr('age', 'ge', 30) }} |
| unique | Removes duplicate items from a list (case-insensitive) | {{ ['a', 'A', 'b']|unique }} |
| urlencode | Encodes a string for use in a URL | {{ "hello world"|urlencode }} |
| urlencode | Decodes a URL-encoded string | {{ "hello%20world"|urldecode }} |
| b64encode | Encodes a string in base64 | {{ "hello"|b64encode }} |
| b64decode | Decodes a base64-encoded string | {{ "aGVsbG8"|b64decode }} |
| to_json | Converts a value to JSON format | {{ {'a': 1}|to_json }} |
| from_json | Converts a JSON string to a Python object | {{ '{"a": 1}'|from_json }} |
| to_yaml | Converts a value to YAML format | {{ {'a': 1}|to_yaml }} |
| from_yaml | Converts a YAML string to a Python object | {{ "a: 1"|from_yaml }} |
| regex_replace | Replaces a regex pattern with a replacement string | {{ "hello world"|regex_replace('world', 'Ansible') }} |
| regex_findall | Finds all matches of a regex pattern in a string | {{ "hello world"|regex_findall('l+') }} |
| regex_search | Searches for a regex pattern in a string | {{ "hello world"|regex_search('l+') }} |
| regex_match | Matches a regex pattern against a string | {{ "hello world"|regex_match('h.*d') }} |
| batch | Groups items in a list into batches of a given size | {{ [1, 2, 3, 4, 5]|batch(2) }} |
| loop.index | Accesses the current index in a loop | {% for item in items %}{{ loop.index }}{% endfor %} |
| loop.first | Checks if the current item is the first in a loop | {% for item in items %}{% if loop.first %}First{% endif %}{% endfor %} |
| loop.last | Checks if the current item is the last in a loop | {% for item in items %}{% if loop.last %}Last{% endif %}{% endfor %} |
| loop.revindex | Accesses the reverse index in a loop | {% for item in items %}{{ loop.revindex }}{% endfor %} |
| loop.length | Accesses the length of a loop | {% for item in items %}{{ loop.length }}{% endfor %} |
| loop.cycle | Cycles through a list of values in a loop | {% for item in items %}{{ loop.cycle('odd', 'even') }}{% endfor %} |
| loop.depth | Accesses the depth of nested loops | {% for item in items %}{% for subitem in item %}{{ loop.depth }}{% endfor %}{% endfor %} |
| loop.depth0 | Accesses the zero-based depth of nested loops | {% for item in items %}{% for subitem in item %}{{ loop.depth0 }}{% endfor %}{% endfor %} |
| loop.previtem | Accesses the previous item in a loop | {% for item in items %}{{ loop.previtem }}{% endfor %} |
| loop.nextitem | Accesses the next item in a loop | {% for item in items %}{{ loop.nextitem }}{% endfor %} |
| loop.changed | Checks if a value has changed in a loop | {% for item in items %}{{ loop.changed(item) }}{% endfor %} |
5. Implementing Conditionals
| Directive | Description | Usage |
|---|---|---|
| when | Conditionally execute a task based on the evaluation of a boolean expression. | when: ansible_facts.os_family == "RedHat" |
| failed_when | Defines a condition under which a task is marked as failed. | failed_when: "result.rc != 0" |
| changed_when | Defines a condition under which a task is marked as changed. | changed_when: "result.stdout | regex_search('restarted')" |
Example: Using when with variables
- name: Install packages on Debian
apt:
name: nginx
state: present
when: ansible_os_family == "Debian"
- name: Install packages on RedHat
yum:
name: nginx
state: present
when: ansible_os_family == "RedHat"
Example: Using failed_when to check output
- name: Check if a file exists
command: ls /etc/nonexistent
register: result
failed_when: "result.rc != 0"
Example: Using changed_when to control task state
- name: Restart service if config changed
command: systemctl restart myservice
register: result
changed_when: "result.stdout | regex_search('restarted')"
Note: The
when directive is evaluated at runtime and supports Jinja2 expressions.
Warning: Be cautious with
failed_when and changed_when as they can mask actual errors or changes in your playbook.
6. Implementing Loops
| Loop Type | Description | Example |
|---|---|---|
| loop | Iterates over a list or array | |
| loop.index | Gives the current iteration number (1-based) | |
| loop.index0 | Gives the current iteration number (0-based) | |
| loop.revindex | Gives the number of iterations remaining (1-based) | |
| loop.revindex0 | Gives the number of iterations remaining (0-based) | |
| loop.first | True if this is the first iteration | |
| loop.last | True if this is the last iteration | |
| loop.cycle | Cycles through a list of values | |
Example: Looping Over a List
{% for host in groups['webservers'] %}
{{ host }} is part of the web server group
{% endfor %}
Example: Nested Loops with Index Tracking
{% for item in items %}
{% for subitem in item.subitems %}
{{ loop.parent.index }}.{{ loop.index }}: {{ subitem }}
{% endfor %}
{% endfor %}
Note: The
loop variable is only available inside a loop block and provides useful context about the current iteration.
7. Setting Template Variables
| Variable | Type | Description | Example |
|---|---|---|---|
| hostvars | Dictionary | Contains variables for all hosts in the inventory | {{ hostvars['web01']['ansible_fqdn'] }} |
| group_names | List | Names of groups the current host belongs to | {{ group_names | join(', ') }} |
| groups | Dictionary | All groups and their hosts | {{ groups['webservers'] | join(', ') }} |
| ansible_env | Dictionary | Environment variables on the target host | {{ ansible_env.HOME }} |
| vars | Dictionary | Variables defined in playbooks or roles | {{ vars['app_version'] }} |
Example: Using hostvars in a template
{% for host in groups['webservers'] %}
Server: {{ hostvars[host]['ansible_fqdn'] }}
Port: {{ hostvars[host]['http_port'] }}
{% endfor %}
Example: Conditional variable usage
{% if ansible_os_family == 'RedHat' %}
Firewall: iptables
{% else %}
Firewall: ufw
{% endif %}
8. Using Comments
| Comment Type | Syntax | Description |
|---|---|---|
| Inline Comment | {# This is an inline comment #} |
Comments that are ignored during template processing and do not appear in the output. |
| Multiline Comment | {#- This is a multiline comment -#} |
Comments that span multiple lines and are also ignored during processing. |
Example: Using Inline Comments in Jinja2
{# This comment will not appear in the output #}
<div>
<h1>{{ title }}</h1>
<p>{{ content }}</p>
</div>
9. Including Templates
| Directive | Description | Usage |
|---|---|---|
| include | Includes a template file and renders it in place | {% include 'template.j2' %} |
| import | Imports a template and makes its macros available | {% import 'macros.j2' as macros %} |
| from | Imports specific macros or variables from another template | {% from 'macros.j2' import macro1 %} |
Example: Including a Template
{% include 'header.j2' %}
<h1>{{ title }}</h1>
{% include 'footer.j2' %}
Example: Importing Macros
{% import 'utils.j2' as utils %}
<div class="card">
{{ utils.render_button('Click Me') }}
</div>
Note: The
include directive does not pass variables from the parent template by default. Use {% include 'template.j2' with context %} to pass the current context.
10. Using Macros
Jinja2 macros in Ansible templates function similarly to functions, allowing you to define reusable code blocks. They are particularly useful for generating repetitive HTML or configuration content.
| Macro | Description | Usage |
|---|---|---|
| macro_name | Defines a reusable block of code | {% macro macro_name(param1, param2) %}...{% endmacro %} |
Example: Defining and Using a Macro
{% macro render_button(text, url, type='primary') %}
<button class="btn btn-{{ type }}" href="{{ url }}">{{ text }}</button>
{% endmacro %}
{{ render_button('Click Me', '/home', 'secondary') }}
Note: Macros must be defined before they are used in the template.
Key Points About Macros
- Macros are defined using the
{% macro %}tag. - They can accept parameters just like functions.
- Macros are expanded inline at template rendering time.
11. Escaping Variables
| Variable | Escaped Value | Description |
|---|---|---|
| variable | {{ variable|escape }} |
Escapes a variable for safe HTML output |
| variable | {{ variable|e }} |
Short alias for the escape filter |
Example: Escaping HTML in Templates
{% set html_content = "<p>Hello & welcome</p>" %}
{{ html_content|escape }}
Note: The
escape filter is particularly useful when rendering user-generated content to prevent XSS attacks.
12. Controlling Whitespace
| Option | Values | Description |
|---|---|---|
| trim | true / false |
Removes leading and trailing whitespace from template content |
| lstrip_blocks | true / false |
Removes whitespace at the beginning of a line when it's immediately after a Jinja2 block |
| rstrip_blocks | true / false |
Removes whitespace at the end of a line when it's immediately before a Jinja2 block |
Example: Controlling Whitespace in Templates
{% filter trim %}
This template has extra whitespace.
{% endfilter %}
{%- if true -%}
This line will not have leading or trailing spaces.
{%- endif -%}
Note: Whitespace control is particularly useful when generating configuration files or code where formatting matters.