Deploying Custom Files with Ansible JINJA2 Templates

grayscale photo of computer laptop near white notebook and ceramic mug on table
Reading Time: 5 minutes

Introduction

The most powerful way to manage files in ansible is to template them. with this method, you can write a template configuration file that is automatically customized for the managed host when the file is deployed, using ansible variable and facts. This can be easier to control and less error-prone. Ansible uses the jinja2 templates for template files. Jinja2 is a modern and designer-friendly templating language for Python frameworks. It is widely used for dynamic file generation based on its parameter.

Jinja2

Jinja2 is a very powerful and advanced templating language from python. It is very fast, reliable and widely used to generate dynamic data. It is a text-based template language and thus can be used to generate any markup as well as source code.

In ansible, most of the time we would use templates to replace configuration files or place some other files such as scripting, documents and other text files on the remote server. Let’s see more about how it fit in ansible.

Features of Jinja2

  1. Control structures (loops and conditional statements)
  2. Template inheritance
  3. Support for custom filters
  4. Rich set of built-in filters
  5. configurable syntax

Delimiters Used in Jinja 2

1.{% %} : used for control statements such as loops and if-else statements.

2.{{ }} :These double curly braces are the widely used tags in a template file and they are used for embedding variables and ultimately printing their value during code execution.

3.{# #} used for comments which are not included in the template output.
The file extension of a jinja2 template is .j2.

Jinja2 Templates

Jinja2 templates are simple template files that store variables that can change from time to time. When Playbooks are executed, these variables get replaced by actual values defined in Ansible Playbooks. This way, templating offers an efficient and flexible solution to create or alter configuration file with ease.

How to use Jinja2 template in Ansible?

Now that we know something about Jinja2 and syntax, we will use a Jinja2 template to configure customized files on our managed nodes.

Lets create templates project directory which we will use for this example. Here project means nothing but a new directory which contains everything your playbook needs such as ansible.cfg, inventory etc.

[ansadmin@ansible-master ~]$ ls
templates
[ansadmin@ansible-master ~]$ cd templates/
[ansadmin@ansible-master templates]$ ls
ansible.cfg inventory
[ansadmin@ansible-master templates]$

Lets create a simple inventory file with two managed nodes as server-a and server-b as shown bellow.

[ansadmin@ansible-master templates]$ cat inventory
[nodes]
server-a.example.com
server-b.example.com

Accessing Variables in JinJa2

Lets create a sample Jinja2 template with the name index.j2

[ansadmin@ansible-master templates]$ cat index.j2 
 A message from {{ inventory_hostname }}
 {{ webserver_message }}

The inventory_hostname is an ansible built-in magic variable that references the current host being iterated over in the play. The webserver_message is a variable that you will define in your playbook.

Lets create an playbook apache.yaml which will install and start httpd package and also will copy template to remote machine.

[ansadmin@ansible-master templates]$ cat apache.yaml
---
- name: playbook for installing and starting apache package
  hosts: all
  vars: 
    webserver_message: "I am running to the finish line."
  tasks:
    - name: Install httpd package
      yum:
        name: httpd
        state: present      
    - name: Start httpd service
      service:
        name: httpd
        state: started

    - name: Create index.html using Jinja2
      template:
        src: index.j2
        dest: /var/www/html/index.html  

In this playbook, we will install and start apache. Then use the template module to create index.html file using Jinja2 to process and transfer the index.j2  Jinja2 template file we created to the destination /var/www/html/index.html.

Lets run the playbook

[ansadmin@ansible-master templates]$ ansible-playbook apache.yaml 

PLAY [playbook for installing and starting apache package] ************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************************************
ok: [server-a.example.com]
ok: [server-b.example.com]

TASK [Install httpd package] ******************************************************************************************************************************************************************************
changed: [server-b.example.com]
changed: [server-a.example.com]

TASK [Start httpd service] ********************************************************************************************************************************************************************************
changed: [server-b.example.com]
changed: [server-a.example.com]

TASK [Create index.html using Jinja2] *********************************************************************************************************************************************************************
changed: [server-b.example.com]
changed: [server-a.example.com]

PLAY RECAP ************************************************************************************************************************************************************************************************
server-a.example.com                   : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
server-b.example.com                   : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Lets run a ad-hoc ansible command to check the contents of index.html on all the nodes

[ansadmin@ansible-master templates]$ ansible all -m command -a "cat /var/www/html/index.html"
server-b.example.com | CHANGED | rc=0 >>
A message from server-b.example.com
I am running to the finish line.
server-a.example.com | CHANGED | rc=0 >>
A message from server-a.example.com
I am running to the finish line.

we have seen that how Jinja2 was able to pick up the values of the inventory_hostname built-in variable and the webserver_message variable in your playbook.

Accessing Facts in Jinja2

We can also access facts in Jinja2 templates in the same way how we access facts and user defined variables from playbook.

Lets create another Jinja2 template for accessing facts as accessfacts.j2

[ansadmin@ansible-master templates]$ cat accessfacts.j2 
Server Information Summary
--------------------------

hostname={{ ansible_facts['hostname'] }}
fqdn={{ ansible_facts['fqdn'] }}
ipaddr={{ ansible_facts['default_ipv4']['address'] }}
distro={{ ansible_facts['distribution'] }}
distro_version={{ ansible_facts['distribution_version'] }}
totalmem={{ ansible_facts['memtotal_mb'] }}
freemem={{ ansible_facts['memfree_mb'] }}

accessfacts.j2 accesses seven different facts.

Lets create a playbook  serverinfo.yaml as shown below

[ansadmin@ansible-master templates]$ cat serverinfo.yaml
---
- name: Server Information Summary
  hosts: all
  tasks:
   - name: Create serverinfo.txt using Jinja2
     template:
       src: accessfacts.j2
       dest: /tmp/serverinfo.txt

we are creating /tmp/serverinfo.txt on all hosts based on the accessfacts.j2 template file.

Lets run the playbook

[ansadmin@ansible-master templates]$ ansible-playbook serverinfo.yaml 

PLAY [Server Information Summary] *************************************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************************************
ok: [server-a.example.com]
ok: [server-b.example.com]

TASK [Create serverinfo.txt using Jinja2] *****************************************************************************************************************************************************************
changed: [server-a.example.com]
changed: [server-b.example.com]

PLAY RECAP ************************************************************************************************************************************************************************************************
server-a.example.com                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
server-b.example.com                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0   
ignored=0  

Now let’s run a ad-hoc command to inspect the contents of the /tmp/serverinfo.txt file in all the nodes.

[ansadmin@ansible-master templates]$ ansible all -m command -a "cat /tmp/serverinfo.txt"
server-a.example.com | CHANGED | rc=0 >>
Server Information Summary
--------------------------

hostname=server-a
fqdn=server-a.example.com
ipaddr=172.31.34.104
distro=Amazon
distro_version=2
totalmem=983
freemem=406
server-b.example.com | CHANGED | rc=0 >>
Server Information Summary
--------------------------

hostname=server-b
fqdn=server-b.example.com
ipaddr=172.31.39.155
distro=Amazon
distro_version=2
totalmem=983
freemem=413

As we can see, Jinja2 was able to access and process all the facts.

Looping in Jinja2

You can use the for statement in Jinja2 to loop over items in a list,range, etc. For example, the following for loop will iterate over the numbers in the range(1,11) and will hence display the numbers from 1->10

{% for i in range(1,11) %}
	Number {{ i }}
{% endfor %}

Lets create template file hosts.j2 that can dynamically update all the nodes information to /etc/hosts file in all the nodes.

[ansadmin@ansible-master templates]$ cat hosts.j2 
{% for host in groups['all'] %}
{{ hostvars[host].ansible_facts.default_ipv4.address }}  {{ hostvars[host].ansible_facts.fqdn }}  {{ hostvars[host].ansible_facts.hostname }}
{% endfor %}

here we used another built-in magic variable hostvars which is basically a dictionary that contains all the hosts in inventory and variables assigned to them.

Lets create a playbook  dynamichosts.yaml which updates the /etc/hosts file dynamically in all the nodes.

[ansadmin@ansible-master templates]$ cat dynamichosts.yaml 
---
- name: Updating /etc/hosts file dynamically 
  hosts: all
  tasks:
    - name: Update /etc/hosts using Jinja2
      template:
        src: hosts.j2
        dest: /etc/hosts

Lets run the playbook

[ansadmin@ansible-master templates]$ ansible-playbook dynamichosts.yaml 

PLAY [Updating /etc/hosts file dynamically] ***************************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************************************
ok: [server-a.example.com]
ok: [server-b.example.com]

TASK [Update /etc/hosts using Jinja2] *********************************************************************************************************************************************************************
changed: [server-a.example.com]
changed: [server-b.example.com]

PLAY RECAP ************************************************************************************************************************************************************************************************
server-a.example.com                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
server-b.example.com                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Now let’s run a ad-hoc command to inspect the contents of the /etc/hosts file in all the nodes.

[ansadmin@ansible-master templates]$ ansible all -m command -a "cat /etc/hosts"
server-a.example.com | CHANGED | rc=0 >>
172.31.34.104  server-a.example.com  server-a
172.31.39.155  server-b.example.com  server-b
server-b.example.com | CHANGED | rc=0 >>
172.31.34.104  server-a.example.com  server-a
172.31.39.155  server-b.example.com  server-b

Conclusion

In Ansible, we can use Jinja2 templating to work efficiently and produce quantifiable results. There are other modules as well like copy module which provide some features of template module if not all when you do not want to work with Jinja2 templating. But using more feature equipped modules makes the tasks execution smooth and leverage the functionality to the full extent enables you to do more tasks with less code.

Reference

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading