Managing configurations of multiple servers and environments are one of the significant uses of Ansible. But these configuration files may vary for each remote servers or each cluster. But apart from some few parameters, all other settings will be same.
Creating static files for each of these configurations is not an efficient solution. And It will take a lot more time and every time a new cluster is added you will have to add more files. So if there is an efficient way to manage these dynamic values it would be beneficial. This is where Ansible template modules come into play.
A template in Ansible is a file which contains all your configuration parameters, but the dynamic values are given as variables. During the playbook execution, depending on the conditions like which cluster you are using, the variables will be replaced with the relevant values.
You can do much more than replacing the variables though, with the help of Jinj2 templating engine. You can have conditional statements, loops, write macros, filters for transforming the data, do arithmetic calculations, etc.
The template files will usually have the .j2 extension, which denotes the Jinja2 templating engine used.
The variables in a template file will be denoted by the double curly braces, {{ variables }}.
Ensure you are in the ciq-basics directory
Create a folder for this lab, let’s call it lab02
You are now ready to start the lab.
At the bare minimum, you need to have two parameters when using the Ansible template module.
src: the source of the template file. This can be relative or absolute path.
dest: the destination path on the remote server
In this first exercise, we will be using the template module to add values to a Jinja2 template. The template module will replace and defined variables with the appropriate values. Let’s see this in action.
Create a new file called my_template.j2:
Enter the following lines into your editor:
{{ variable_to_be_replaced }}
This line won't be changed
Variable given as inline - {{ inline_variable }} - :)
Save the template and create a playbook called 01_basic_template.yml:
Enter the following lines into your editor:
- name: This playbook creates a file from a template
hosts: all
gather_facts: false
vars:
variable_to_be_replaced: 'Hello world'
inline_variable: 'hello again'
tasks:
- name: Ansible Template Example
template:
src: ./my_template.j2
dest: /tmp/my_file.txtSample Output
PLAY [This playbook creates a file from a template] ****************************************
TASK [Ansible Template Example] ************************************************
changed: [node-1]
PLAY RECAP *********************************************************************
node-1 : ok=1 changed=1 unreachable=0 failed=0
The playbook ran, but to validate the results, let’s take a look at the generated file that the template module created.
Add a new task to the above playbook and use the shell module to execute this command
cat /tmp/my_file.txtContents of my_file.txt
Hello world
This line won't be changed
Variable given as inline - hello again - :)
Observe that the template module has replace the variables with the values we set in our playbook.
One of the main program expression we usually use is the ‘for’ loop. It can be used to iteratively go through the values of a list, dictionary etc.
It is possible to use this in ansible templates also using the Jinja2 format.
In the next exercise, we will build a template that will loop through the value 0 to 2 using the python range function. On each iteration, a line with the variable is printed.
Create a new file called my_template02.j2:
Enter the following lines into your editor:
Ansible template for loop example
{% for i in range(3) %}
This is the {{ i }}th variable
{% endfor %}
Save the template and create a playbook called 02_template_loop.yml:
Enter the following lines into your editor:
- name: this playbook creates a file from a template
hosts: all
gather_facts: false
tasks:
- name: Ansible Template Example
template:
src: ./my_template02.j2
dest: /tmp/my_file02.txtSample Output
PLAY [this playbook creates a template] ****************************************
TASK [Ansible Template Example] ************************************************
changed: [node-1]
PLAY RECAP *********************************************************************
node-1 : ok=1 changed=1 unreachable=0 failed=0
The playbook ran, but to validate the results, let’s take a look at the generated file that the template module created.
cat /tmp/my_file02.txt
Contents of my_file02.txt
Ansible template for loop example
This is the 0th variable
This is the 1th variable
This is the 2th variable
By tinkering around with the filters and features that are provided by Jinja2 we can begin to see that our templates can be much more than static documents that simply replace variables.
There are sometimes situations where you need to parse a list of information and do something with it in a template. In the exercise below we will build a playbook that looping through a variable that contains a list of data. We will provide that list in our playbook and then create a for loop structure. Note that, after each iteration, a new line is also added. So the three list items will be in three lines.
Create a new file called my_template03.j2:
Enter the following lines into your editor:
This is an example of template module loop with a list.
{% for item in list1 %}
{{ item }}
{% endfor %}
Save the template and create a playbook called 03_loop_variable.yml:
Enter the following lines into your editor:
- name: This playbook creates a file from a template
hosts: all
gather_facts: false
vars:
list1: ['pen','pineapple','apple','pen']
tasks:
- name: Ansible Template Loop Example
template:
src: ./my_template03.j2
dest: /tmp/my_file03.txtThis is an example of template module loop with a list.
pen
pineapple
apple
pen
It’s always good practice to use comments when building your playbooks, this comes in the form of the name parameter. Comments are good, and you should be as verbose as you can so that others understand the intent of the playbook. It’s worth noting that you can also add comments in your template file. However, sometimes you don’t want your comments to appear in the rendered file. You can prevent your comments from being included in the rendered file by wrapping your comments in Jinja2 style comments. To do this simply enclose the comments within {# … #}.
Example
{# this is a comment that will not get rendered #}
This is an example of template module loop with a list.
{% for item in list1 %}
{{ item }}
{% endfor %}
When working with Jinja2 templates, you may notice that loops and conditional statements can introduce unwanted whitespace and blank lines in your output. Jinja2 provides whitespace control syntax using dashes (-) to strip whitespace from the beginning or end of blocks. + Jinja2 Documentation
Create a new file called my_template04.j2:
Enter the following lines into your template to demonstrate the whitespace issue:
This template demonstrates whitespace control.
List of items:
{% for item in list1 %}
- {{ item }}
{% endfor %}
End of list.
- name: Demonstrate whitespace control in templates
hosts: all
gather_facts: false
vars:
list1: ['apple', 'banana', 'cherry']
tasks:
- name: Generate template without whitespace control
template:
src: ./my_template04.j2
dest: /tmp/whitespace_demo.txtcat /tmp/whitespace_demo.txt
Output without whitespace control:
This template demonstrates whitespace control.
List of items:
- apple
- banana
- cherry
End of list.
This template demonstrates whitespace control.
List of items:
{%- for item in list1 %}
- {{ item }}
{%- endfor %}
End of list.
- name: Demonstrate whitespace control in templates
hosts: all
gather_facts: false
vars:
list1: ['apple', 'banana', 'cherry']
tasks:
- name: Generate template with whitespace control
template:
src: ./my_template04_clean.j2
dest: /tmp/whitespace_clean.txtRun the playbook and check the cleaner output:
cat /tmp/whitespace_clean.txtOutput with whitespace control:
This template demonstrates whitespace control.
List of items:
- apple
- banana
- cherry
End of list.
The - character strips whitespace from that side of the block. You can use:
{%- to strip whitespace from the left side-%} to strip whitespace from the right side{%- -%} to strip whitespace from both sides-%} on the right side of blocks to see the effect{% if -%} and {%- endif %}So far we’ve been using Jinja2 templating in separate template files, but Jinja2 can also be used directly within Ansible playbooks themselves. This is particularly useful when you need to dynamically generate complex data structures, process lists, or create computed values using set_fact.
Create a new file called 05_jinja_in_playbook.yml:
Enter the following lines into your playbook:
- name: Demonstrate Jinja2 processing directly in playbooks
hosts: all
gather_facts: false
vars:
servers:
- name: web01
role: webserver
port: 80
status: active
- name: web02
role: webserver
port: 8080
status: inactive
- name: db01
role: database
port: 3306
status: active
- name: cache01
role: cache
port: 6379
status: active
tasks:
- name: Generate server summary using Jinja2
set_fact:
server_summary: |
Server Summary Report
=====================
{% for server in servers -%}
{% if server.status == 'active' -%}
✓ {{ server.name }} ({{ server.role }}) - Port {{ server.port }} - RUNNING
{% else -%}
✗ {{ server.name }} ({{ server.role }}) - Port {{ server.port }} - STOPPED
{% endif -%}
{% endfor -%}
Active Servers: {{ servers | selectattr('status', 'equalto', 'active') | list | length }}
Total Servers: {{ servers | length }}
- name: Display the generated summary
debug:
msg: "{{ server_summary }}"
- name: Create a list of active server names using Jinja2
set_fact:
active_servers: |
{%- set active_list = [] -%}
{%- for server in servers -%}
{%- if server.status == 'active' -%}
{%- set _ = active_list.append(server.name) -%}
{%- endif -%}
{%- endfor -%}
{{ active_list }}
- name: Display active servers list
debug:
msg: "Active servers: {{ active_servers | from_yaml }}"
- name: Generate port configuration using Jinja2
set_fact:
port_config: |
# Generated port configuration
{% for server in servers -%}
{% if server.status == 'active' -%}
{{ server.role }}_{{ server.name }}_port={{ server.port }}
{% endif -%}
{% endfor %}
- name: Display port configuration
debug:
msg: "{{ port_config }}"Sample Output
PLAY [Demonstrate Jinja2 processing directly in playbooks] *********************
TASK [Generate server summary using Jinja2] ************************************
ok: [node-1]
TASK [Display the generated summary] *******************************************
ok: [node-1] => {
"msg": "Server Summary Report\n=====================\n✓ web01 (webserver) - Port 80 - RUNNING\n✗ web02 (webserver) - Port 8080 - STOPPED\n✓ db01 (database) - Port 3306 - RUNNING\n✓ cache01 (cache) - Port 6379 - RUNNING\n\nActive Servers: 3\nTotal Servers: 4\n"
}
TASK [Create a list of active server names using Jinja2] **********************
ok: [node-1]
TASK [Display active servers list] *********************************************
ok: [node-1] => {
"msg": "Active servers: ['web01', 'db01', 'cache01']"
}
TASK [Generate port configuration using Jinja2] ********************************
ok: [node-1]
TASK [Display port configuration] **********************************************
ok: [node-1] => {
"msg": "# Generated port configuration\nwebserver_web01_port=80\ndatabase_db01_port=3306\ncache_cache01_port=6379\n"
}
map, join, or unique