Ansible Templating Lab

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 }}.

Goals

Lab Setup

  1. Ensure you are in the ciq-basics directory

  2. Create a folder for this lab, let’s call it lab02

You are now ready to start the lab.

Basic Templating with Ansible

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.

Creating our Jinja2 template and playbook

  1. Create a new file called my_template.j2:

  2. Enter the following lines into your editor:

{{ variable_to_be_replaced }}
This line won't be changed
Variable given as inline - {{ inline_variable }} - :)
  1. Save the template and create a playbook called 01_basic_template.yml:

  2. 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.txt
  1. Save the playbook and run it:

Sample 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.

  1. Add a new task to the above playbook and use the shell module to execute this command

     cat /tmp/my_file.txt

Contents 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.

Things to try

Using a loop structure inside Ansible template

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.

Creating our Jinja2 template and playbook

  1. Create a new file called my_template02.j2:

  2. Enter the following lines into your editor:

Ansible template for loop example
{% for i in range(3) %}
  This is the {{ i }}th variable
{% endfor %}
  1. Save the template and create a playbook called 02_template_loop.yml:

  2. 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.txt
  1. Save the playbook and run it:

Sample 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.

  1. Add a new task to the above playbook and use the shell module to execute this command
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.

Things to try

Using list variables in Ansible templates

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.

Creating our Jinja2 template and playbook

  1. Create a new file called my_template03.j2:

  2. Enter the following lines into your editor:

This is an example of template module loop with a list.
{% for item in list1 %}
  {{ item }}
{% endfor %}
  1. Save the template and create a playbook called 03_loop_variable.yml:

  2. 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.txt
  1. Save the playbook and run it: The playbook ran, but to validate the results, let’s take a look at the generated file that the template module created.
  2. Check the contents of your new file using a shell task again. Contents of my_file03.txt
This is an example of template module loop with a list.
  pen
  pineapple
  apple
  pen

Things to try

Ansible template comment example

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 %}

Things to try

Whitespace Control in Templates

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 and run your playbook

  1. Create a new file called my_template04.j2:

  2. 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.
  1. Create a playbook called 04_whitespace_control.yml:
- 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.txt
  1. Run the playbook and check the output:
cat /tmp/whitespace_demo.txt

Output without whitespace control:

This template demonstrates whitespace control.
List of items:

- apple

- banana

- cherry

End of list.
  1. Now create an improved template my_template04_clean.j2 using whitespace control:
This template demonstrates whitespace control.
List of items:
{%- for item in list1 %}
- {{ item }}
{%- endfor %}
End of list.
  1. Update your playbook to use the new template:
- 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.txt
  1. Run the playbook and check the cleaner output:

     cat /tmp/whitespace_clean.txt

Output 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:

Things to try

Using Jinja2 Directly in Playbooks

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 and run your playbook

  1. Create a new file called 05_jinja_in_playbook.yml:

  2. 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 }}"
  1. Save the playbook and run it:

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"
    }

Things to try

Return to Exercises