Ansible Playbook Using Templates


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. This is where Ansible template modules come into play.

Templates are simple text files that we use in Ansible which contains all your configuration parameters. During the playbook execution, depending on the conditions like which cluster you are using, the variables will be replaced with the relevant values. Most of the time we use them to replace configuration files or place some documents on the server. We can do much more than replacing the variables with the help of Jinj2 templating engine. We can have conditional statements, loops, 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.

A Basic Example

In the following task, I am using the template module on the example1.j2 file which will replace the default variables with values given in the playbook.

File: Playbook.yml

---
- hosts: all
  vars:
    variable1: 'Hello...!!!'
    variable2: 'My first playbook using template'
  tasks:
    - name: Basic Template Example
      template:
        src: example1.j2
        dest: /home/knoldus/Documents/Ansible/output.txt

File: example1.j2

{{ variable1 }}
No effects on this line
{{ variable2 }}

File: output.txt

Hello...!!!
No effects on this line
My first playbook using template

As you can see, both variables in the example1.j2 are replaced by their values. 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 a relative or absolute path.
  • dest: The destination path on the remote server

Additional attributes of the template module

These are some of the other parameters which we can use to change some default behavior of template module :

  • force – If the destination file already exists, then this parameter decides whether it should be replaced or not. By default, the value is ‘yes’.

  • mode – If you want to set the permissions for the destination file explicitly, then you can use this parameter.

  • backup – If you want a backup file to be created in the destination directory, you should set the value of the backup parameter to ‘yes’. By default, the value is ‘no’. The backup file will be created every time there is a change in the destination directory.

  • group – Name of the group that should own the file/directory. It is similar to executing chown for a file in Linux systems.

Using lists in Ansible templates

In the next example, I’ll be using the template module to print all the items present in a list using the for the loop.

File: Playbook.yml

- hosts: all
  vars:
    list1: ['Apple','Banana','Cat', 'Dog']
  tasks:
    - name: Template Loop example.
    - template:
        src: example2.j2
        dest: /home/knoldus/Documents/Ansible/output.txt

File: example2.j2

Example of template module loop with a list.
{% for item in list1 %}
  {{ item }}
{% endfor %}

File: output.txt

Example of template module loop with a list.
Apple
Banana
Cat
Dog

We can see that after each iteration, a newline is added. The more practical approach of for loops is when we have to add a specific port to a large number of hosts and it is too tedious to manually update the inventory file. We can simply write the following code to update the same

host.list={% for node in groups['nodes'] %}{{ node }}:5672{% endfor %}

Working With Multiple Files in Ansible

We can use the with_items parameter on a dictionary to render multiple files. For example, if we want to render three templates each with different source and destination, with_items parameter can be put to use. An illustration is given below.

- hosts: all
  tasks:
    - name: Template with_items example.
      template:
        src: "{{ item.src }}"
        dest: "{{ item.dest }}"
      with_items:
        - {src: 'example.j2',dest: '/home/knoldus/Documents/Ansible/output.txt'}
        - {src: 'example1.j2',dest: '/home/knoldus/Documents/Ansible/output1.txt'}
        - {src: 'example2.j2',dest: '/home/knoldus/Documents/Ansible/output2.txt'}

Working With Filters

Filters are a way of transforming template expressions from one kind of data into another. They take some arguments, process them, and return the result. There are a lot of builtin filters which can be used in the Ansible playbook. For example, in the following code myvar is a variable; Ansible will pass myvar to the Jinja2 filter as an argument. The Jinja2 filter will then process it and return the resulting data.

		    {{ myvar | filter }}

Templating happens on the Ansible controller, not on the task’s target host, so filters also execute on the controller as they manipulate local data. Some common uses of filters are as follows :

  • For formatting data – These filters will take a data structure in a template and render it in a slightly different format. These are occasionally useful for debugging. It may be reading in some already formatted data. Example:
    {{ some_variable | to_json }}
    {{ some_variable | from_json }}
    .
  • IP address filter – Used to test if a string is a valid IP address. IP address filter can also be used to extract specific information from an IP address. Example:
    {{ myvar | ipaddr }}.
  • URL Split Filter – The urlsplit filter extracts the fragment, hostname, netloc, password, path, port, query, scheme, and username from an URL. With no arguments, returns a dictionary of all the fields. Example:
    {{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#frament" | urlsplit('hostname') }}
  • Regular Expression Filters – Used to search a string with a regex, use the “regex_search” filter. Example:
    {{ 'foobar' | regex_search('(foo)') }}
  • List Filters – These filters all operate on list variables. Example:
    {{ list1 | min }}
    {{ list1 | unique }}
    {{ list1 | union(list2) }}
    .
  • Defining default values for undefined variables: Jinja2 provides a useful ‘default’ filter, that is often a better approach to failing if a variable is not defined. Example:
    {{ some_variable | default(5) }}

Conclusion

The use of a well supported and powerful template library to supplement Ansible’s automation and remote control features provides a wide range of opportunities to output pretty complex files with a reasonable amount of effort. As is true with any tool, sometimes getting too clever with it can make it difficult to understand what is going on when looking at the code later on. So instead of trying to come up with the shortest way to write something in a template, you might want to consider a more verbose solution to a problem – like a simple for loop instead of a clever combination of filters and functions – if that turns out to be the more pragmatic, more readable approach. Co-workers (and your future self) might be grateful for it 6 months down the road.

References :

Ansible Docs

Jinja2 Docs

Ansible Examples Repository

knoldus-advt-sticker

This entry was posted in Devops and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s