Templates give the ability to provide a skeletal file that can be dynamically completed using variables
- The most common template use case is configuration file management
- Templates are generally used by providing a template file on the ansible control node, and then using the template module within your playbook to deploy the file to a target server or group
- Templates are processed using the Jinja2 template language
- The template module is used to deploy template files
- There are two required parameters:
src
– the template to use (on the ansible control host)dest
– where the resulting file should be located (on the target host)
- A useful optional parameter is
validate
which requires a successful validation command to run against the result file prior to deployment - It is also possible to set basic file properties using the template module
- Template file are essentially little more than text files
- Template files are designated with a file extension of J2
- Template files have access to the same variables that the play that calls them does
Delimiters
Variables or logic expressions are placed between tags, or delimiters. For example, Jinja2 templates use {% EXPR %}
for expressions or logic (for example, loops), while {{ EXPR }}
are used for outputting the results of an expression or a variable to the end user. The latter tag, when rendered, is replaced with a value or values, and are seen by the end user. Use {# COMMENT #}
syntax to enclose comments.
In the following example the first line includes a comment that will not be included in the final file. The variable references in the second line are replaced with the values of the system facts being referenced.
1 2 |
{# /etc/hosts line #} {{ ansible_default_ipv4.address }} {{ ansible_hostname }} |
Example of j2 template:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#Apache HTTP server -- main configuration # # {{ ansible_managed }} ## General configuration ServerRoot {{ httpd_ServerRoot }} Listen {{ httpd_Listen }} Include conf.modules.d/*.conf User apache Group apache ## 'Main' server configuration ServerAdmin {{ httpd_ServerAdmin }} {% if httpd_ServerName is defined %) ServerName {{ httpd_ServerName }} {% endif %} DocumentRoot {{ httpd_DocumentRoot }} |
Issues to be Aware of with YAML vs. Jinja2 in Ansible
The use of some Jinja2 expressions inside of a YAML playbook may change the meaning for those expressions, so they require some adjustments in the syntax used.
1. YAML syntax requires quotes when a value starts with a variable reference ({{ }}). The quotes prevent the parser from treating the expression as the start of a YAML dictionary. For example, the following playbook snippet will fail:
1 2 3 |
- hosts: app_servers vars: app_path: {{ base_path }}/bin |
Instead, use the following syntax:
1 2 3 |
- hosts: app_servers vars: app_path: "{{ base_path }}/bin" |
2. When there is a need to include nested {{…}} elements, the braces around the inner ones must be removed. Consider the following playbook snippet:
1 2 3 |
- name: display the host value debug: msg: hostname = {{ params[ {{ host_ip }} ] }}, IPaddr = {{ host_ip }} |
Ansible raises the following error when it tries to run it:
1 2 3 4 5 6 |
... Output omitted ... TASK [display the host value] ************************************************** fatal: [localhost]: FAILED! => {"failed": true, "msg": "template error while templating string: expected token ':', got '}'. String: hostname = {{ params[ {{ host_ip }} ] }}, IPaddr = {{ host_ip }}"} ... Output omitted ... |
Use the following syntax instead. It will run without error.
1 2 3 |
- name: display the host value debug: msg: hostname = {{ params[ host_ip ] }}, IPaddr = {{ host_ip }} |
Now the playbook will run without error.
Example 1.
Let’s create jinja2 template:
1 2 |
[miro@controlnode playbooks]$ cat ../templates/network.j2 {{ ansible_default_ipv4.address }} |
And now we can create a playbook which will use this template:
1 2 3 4 5 6 7 8 |
[miro@controlnode playbooks]$ cat playbook8.yml --- - hosts: localhost tasks: - name: deploy local net file template: src: /home/miro/ansible/templates/network.j2 dest: /home/miro/ansible/templates/network.txt |
We can run to see what this playbook do:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[miro@controlnode playbooks]$ ansible-playbook playbook8.yml PLAY [localhost] ***************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************** ok: [localhost] TASK [deploy local net file] ***************************************************************************************************** changed: [localhost] PLAY RECAP *********************************************************************************************************************** localhost : ok=2 changed=1 unreachable=0 failed=0 [miro@controlnode playbooks]$ cat ../templates/network.txt 172.30.9.50 |
We can modifiy the j2 template and playbook8 will return other txt file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[miro@controlnode playbooks]$ cat ../templates/network.j2 My IP address is {{ ansible_default_ipv4.address }} {{ ansible_distribution }} is my OS version [miro@controlnode playbooks]$ ansible-playbook playbook8.yml PLAY [localhost] ***************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************** ok: [localhost] TASK [deploy local net file] ***************************************************************************************************** changed: [localhost] PLAY RECAP *********************************************************************************************************************** localhost : ok=2 changed=1 unreachable=0 failed=0 [miro@controlnode playbooks]$ cat ../templates/network.txt My IP address is 172.30.9.50 CentOS is my OS version |
The playbook below has two plays. First play if for group webservers. This play has two tasks. Task related with yum installs the latest httpd package. Task second is template whis puts the text to do httpd.conf fiile. The second play also have two tasks. It installs the postgres and ensure that service is started.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
--- - hosts: webservers remote user: root tasks: - name: ensure apache is at the latest version yum: name=httpd state=latest - name: write the apache config file template: src=/srv/httpd.j2 dest=/etc/httpd.conf - hosts: databases remote user: root tasks: - name: ensure postgresql is at the latest version yum: name=postgresql state=latest - name: ensure that postgresql is started service: name=postgresql state=started |
Example 2.
1. Create the inventory file in the current directory. This file configures two groups: webservers and workstations. Include the system servera.lab.example.com in the webservers group, and the system workstation.lab.example.com in the workstations group.
1 2 3 4 |
[webservers] servera.lab.example.com [workstations] workstation.lab.example.com |
2. Create a template for the Message of the Day. Include it in the motd.j2
file in the current directory. Include the following variables in the template:
-
ansible_hostname
to retrieve the managed host hostname. ansible_date_time.date
for the managed host date.system_owner
for the email of the owner of the system. This variable requires to be defined, with an appropriate value, in the vars section of the playbook template.
1 2 3 4 |
This is the system {{ ansible_hostname }}. Today's date is: {{ ansible_date_time.date }}. Only use this system with permission. You can ask {{ system_owner }} for access. |
3. Create a playbook in a new file in the current directory, named motd.yml
. Define the system_owner variable in the vars section, and include a task for the template module, which maps the motd.j2 Jinja2 template to the remote file /etc/motd
in the managed hosts. Set the owner and group to root, and the mode to 0644
.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
--- - hosts: all user: devops become: true vars: system_owner: clyde@example.com tasks: - template: src: motd.j2 dest: /etc/motd owner: root group: root mode: 0644 |
4. Run the playbook included in the motd.yml
file.
1 2 3 4 5 6 7 8 9 10 11 |
[student@workstation jinja2]$ ansible-playbook motd.yml PLAY *************************************************************************** TASK [setup] ******************************************************************* ok: [servera.lab.example.com] ok: [workstation.lab.example.com] TASK [template] **************************************************************** changed: [servera.lab.example.com] changed: [workstation.lab.example.com] PLAY RECAP ********************************************************************* servera.lab.example.com : ok=2 changed=1 unreachable=0 failed=0 workstation.lab.example.com : ok=2 changed=1 unreachable=0 failed=0 |
5. Log in to servera.lab.example.com using the devops user, to verify the motd is displayed when logging in. Log out when you have finished.
1 2 3 4 5 6 7 |
[student@workstation jinja2]$ ssh devops@servera.lab.example.com This is the system servera. Today's date is: 2016-04-11. Only use this system with permission. You can ask clyde@example.com for access. [devops@servera ~]# exit Connection to servera.lab.example.com closed. |
Example 3.
1. Create the inventory file. This file configures one group: servers. Include the system serverb.lab.example.com in the servers group.
1 2 |
[servers] serverb.lab.example.com |
2. Identify the facts in serverb.lab.example.com
which show what is the status of the system memory. Use the setup module to get a list of all the facts for the serverb.lab.example.com managed host. Both the ansible_memfree_mb
and ansible_memtotal_mb facts
provide information about the free memory and the total memory of the managed host.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[student@workstation jinja2-lab]$ ansible serverb.lab.example.com -m setup serverb.lab.example.com | SUCCESS => { "ansible_facts": { ... Output omitted ... Solution DO407-A2.0-en-1-20160804 237 "ansible_memfree_mb": 741, ... Output omitted ... "ansible_memtotal_mb": 992, ... Output omitted ... }, "changed": false } |
3. Create a template for the Message of the Day, named motd.j2
, in the current directory. Use the facts previously identified. Create a new file, named motd.j2
, in the current directory. Use both the ansible_memfree_mb
and ansible_memtotal_mb facts
variables to create a Message of the Day.
1 2 3 |
[student@workstation jinja2-lab]$ cat motd.j2 This system's total memory is: {{ ansible_memtotal_mb }} MBs. The current free memory is: {{ ansible_memfree_mb }} MBs. |
4. Create a new playbook file, named motd.yml
. Using the template module, configure the motd.j2
Jinja2 template file previously created, to be mapped to the file /etc/motd
in the managed hosts. This file has the root user as owner and group, and its permissions are 0644
. Configure the playbook so it uses the devops
user, and setup the become parameter to be true.
1 2 3 4 5 6 7 8 9 10 11 12 |
[student@workstation jinja2-lab]$ cat motd.yml --- - hosts: all user: devops become: true tasks: - template: src: motd.j2 dest: /etc/motd owner: root group: root mode: 0644 |
5. Run the playbook included in the motd.yml file.
1 2 3 4 5 6 7 8 9 10 |
[student@workstation jinja2-lab]$ ansible-playbook motd.yml PLAY *************************************************************************** TASK [setup] ******************************************************************* ok: [serverb.lab.example.com] TASK [template] **************************************************************** changed: [serverb.lab.example.com] Chapter 6. Implementing Jinja2 Templates 238 DO407-A2.0-en-1-20160804 PLAY RECAP ********************************************************************* serverb.lab.example.com : ok=2 changed=1 unreachable=0 failed=0 |
6. Check that the playbook included in the motd.yml
file has been executed correctly. Log in to serverb.lab.example.com
using the devops user, to verify the motd is displayed when logging in. Log out when you have finished.
1 2 3 4 |
[student@workstation jinja2-lab]$ ssh devops@serverb.lab.example.com This system's total memory is: 992 MBs. The current free memory is: 741 MBs. [devops@serverb ~]$ logout |