Jinja2 templates
Using Jinja2 Templates
- A template is a configuration file that contains variables and, based on the variables, is generated on the managed hosts according to host-specific requirements.
- Using templates allows for a structural way to generate configuration files, which is much more powerful than changing specific lines from specific files.
- Ansible uses Jinja2 to generate templates.
- Jinja2 is a generic templating language for Python developers.
- It is used in Ansible templates, but Jinja2-based approaches are also found in other parts of Ansible. For instance, the way variables are referred to is based on Jinja2.
In a Jinja2 template, three elements can be used. data
sample textcomment{# sample text #}variable{{ ansible_facts['default_ipv4']['address'] }}expression
{% for myhost in groups['web'] %}
{{ myhost }}
{% endfor %}- To work with a template, you must create a template file, written in Jinja2.
- Template file must be included in an Ansible playbook that uses the template module.
Sample Template:
# {{ ansible_managed }}
<VirtualHost *:80>
ServerAdmin webmaster@{{ ansible_facts['fqdn'] }}
ServerName {{ ansible_facts['fqdn'] }}
ErrorLog logs/{{ ansible_facts['hostname'] }}-error.log
CustomLog logs/{{ ansible_facts['hostname'] }}-common.log common
DocumentRoot /var/www/vhosts/{{ ansible_facts['hostname'] }}/
<Directory /var/www/vhosts/{{ ansible_facts['hostname'] }}>
Options +Indexes +FollowSymlinks +Includes
Require all granted
</Directory>
</VirtualHost>-
starts with # {{ ansible_managed }}.
-
This string is commonly used to identify that a file is managed by Ansible so that administrators are not going to change file contents by accident.
-
While processing the template, this string is replaced with the value of the ansible_managed variable.
-
This variable can be set in ansible.cfg.
-
For instance, you can use ansible_managed = This file is managed by Ansible to substitute the variable with its value while generating the template.
-
template file is just a text file that uses variables to substitute specific variables to their values.
Calling a template:
---
- name: installing a template file
hosts: ansible1
tasks:
- name: install httpd
yum:
name: httpd
state: latest
- name: start and enable httpd
service:
name: httpd
state: started
enabled: true
- name: install vhost config file
template:
src: listing813.j2
dest: /etc/httpd/conf.d/vhost.conf
owner: root
group: root
mode: 0644
- name: restart httpd
service:
name: httpd
state: restartedApplying Control Structures in Jinja2 Using for
- Control structures can be used to dynamically generate contents.
- A for statement can be used to iterate over all elements that exist as the value of a variable.
{% for node in groups['all'] %}
host_port={{ node }}:8080
{% endfor %}- variable with the name host_ports is defined on the second line (which is the line that will be written to the target file).
- To produce its value, the host group all is processed in the for statement on the first line.
- While processing the host group, a temporary variable with the name node is defined.
- This value of the node variable is replaced with the name of the host while it is processed, and after the host name, the string :8080 is copied, which will result in a separate line for each host that was found.
- As the last element, {% endfor %} is used to close the for loop.
LAB: Generating a Template with a Conditional Statement
---
- name: generate host list
hosts: ansible2
tasks:
- name: template loop
template:
src: listing815.j2
dest: /tmp/hostports.txtTo verify, you can use the ad hoc command ansible ansible2 -a "cat /tmp/hostports.txt"
Using Conditional Statements with if
- The for statement can be used in templates to iterate over a series of values.
- The if statement can be used to include text only if a variable contains a specific value or evaluates to a Boolean true.
Template Example with if if.j2
{% if apache_package == 'apache2' %}
Welcome to Apache2
{% else %}
Welcome to httpd
{% endif %}---
- name: work with template file
vars:
apache_package: 'httpd'
hosts: ansible2
tasks:
- template:
src: if.j2
dest: /tmp/httpd.conf[ansible@control ~]$ ansible ansible2 -a "cat /tmp/httpd.conf"
ansible2 | CHANGED | rc=0 >>
Welcome to httpdUsing Filters
- In Jinja2 templates, you can use filters.
- Filters are a way to perform an operation on the value of a template expression, such as a variable.
- The filter is included in the variable definition itself, and the result of the variable and its filter is used in the file that is generated.
Common filters {{ myvar | to_json }}
- writes the contents of myvar in JSON format {{ myvar || to_yaml }}
- writes the contents of myvar in YAML format {{ myvar | ipaddr }}
- tests whether myvar contains an IP address
From https://docs.ansible.com:
How do I loop over a list of hosts in a group, inside of a template?
A pretty common pattern is to iterate over a list of hosts inside of a host group, perhaps to populate a template configuration file with a list of servers. To do this, you can just access the “$groups” dictionary in your template, like this:
{% for host in groups['db_servers'] %}
{{ host }}
{% endfor %}If you need to access facts about these hosts, for example, the IP address of each hostname, you need to make sure that the facts have been populated. For example, make sure you have a play that talks to db_servers:
- hosts: db_servers
tasks:
- debug: msg="doesn't matter what you do, just that they were talked to previously."Then you can use the facts inside your template, like this:
{% for host in groups['db_servers'] %}
{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}Lab: Working with Conditional Statements in Templates
1. Use your editor to create the file exercise83.j2. Include the following line to open the Jinja2 conditional statement:
{% for host in groups['all'] %}2. This statement defines a variable with the name host. This variable iterates over the magic variable groups, which holds all Ansible host groups as defined in inventory. Of these groups, the all group (which holds all inventory host names) is processed.
3. Add the following line (write it as one line; it will wrap over two lines, but do not press Enter to insert a newline character):
{{ hostvars[host]['ansible_default_ipv4']['address'] }} {{ hostvars[host]['ansible_fqdn'] }} {{ hostvars[host]['ansible_hostname'] }}- This line writes a single line for each inventory host, containing three items.
- To do so, you use the magic variable hostvars, which can be used to identify Ansible facts that were discovered on the inventory host.
- The [host] part is replaced with the name of the current host, and after that, the specific facts are referred to. As a result, for each host a line is produced that holds the IP address, the FQDN, and next the host name.
4. Add the following line to close the for loop:
{% endfor %}5. Verify that the complete file contents look like the following and write and quit the file:
{% for host in groups['all'] %}
{{ hostvars[host]['ansible_default_ipv4']['address'] }} {{ hostvars[host]['ansible_fqdn'] }} {{ hostvars[host]['ansible_hostname'] }}
{% endfor %}6. Use your editor to create the file exercise83.yaml. It should contain the following lines:
---
- name: generate /etc/hosts file
hosts: all
tasks:
- name:
template:
src: exercise83.j2
dest: /tmp/hosts7. Run the playbook by using ansible-playbook exercise83.yaml
8. Verify the /tmp/hosts file was generated by using ansible all -a "cat /tmp/hosts"
This lab only worked if every host in the inventory file was reachable.
Lab: Generate an /etc/hosts File
Write a playbook that generates an /etc/hosts file on all managed hosts. Apply the following requirements:
• All hosts that are defined in inventory should be added to the /etc/hosts file.
[ansible@control ~]$ cat hostfile.yaml
---
- name: generate /etc/hosts
hosts: all
gather_facts: yes
tasks:
- name: Generate hosts file with template
template:
src: hosts.j2
dest: /etc/hosts
[ansible@control ~]$ cat hosts.j2
{% for host in groups['all'] %}
{{ hostvars[host]['ansible_default_ipv4']['address'] }} {{ hostvars[host]['ansible_fqdn'] }} {{ hostvars[host]['ansible_hostname'] }}
{% endfor %}}Lab: Manage a vsftpd Service
- Write a playbook that uses at least two plays to install a vsftpd service
- configure the vsftpd service using templates
- configure permissions as well as SELinux.
- Install, start, and enable the vsftpd service.
- open a port in the firewall to make it accessible.
- Use the /etc/vsftpd/vsftpd.conf file to generate a template.
- In this template, you should use the following variables to configure specific settings.
- Replace these settings with the variables and leave all else unmodified:
Anonymous_enable: yes
Local_enable: yes
Write_enable: yes
Anon_upload_enable: yes- Set permissions on the /var/ftp/pub directory to mode 0777.
- Configure the ftpd_anon_write Boolean to allow anonymous user writes.
- Set the public_content_rw_t SELinux context type to the /var/ftp/pub directory.
- If any additional tasks are required to get this done, take care of them.
vim vsftpd.yaml
---
- name: manage vsftpd
hosts: ansible1
vars:
anonymous_enable: yes
local_enable: yes
write_enable: yes
Anon_upload_enable: yes
tasks:
- name: install vsftpd
dnf:
name: vsftpd
state: latest
- name: configure vsftpd configuration file
template:
src: vsftpd.j2
dest: /etc/vsftpd/vsftpd.conf
- name: apply permissions
hosts: ansible1
tasks:
- name: set folder permissions to /var/ftp/pub
file:
path: /var/ftp/pub
mode: 0777
- name: set ftpd_anon_write boolean
seboolean:
name: ftpd_anon_write
state: yes
persistent: yes
- name: set public_content_rw_t SELinux context type to /var/ftp/pub directory
sefcontext:
target: '/var/ftp/pub(/.*)?'
setype: public_content_rw_t
state: present
notify: restore selinux contexts
- name: firewall stuff
firewalld:
service: ftp
state: enabled
permanent: true
immediate: true
- name: start and enable fsftpd
service:
name: vsftpd
state: started
enabled: yes
handlers:
- name: restore selinux contexts
command: restorecon -v /var/ftp/pubvsftpd.j2
{{ ansible_managed }}
anonymous_enable={{ anonymous_enable }}
local_enable={{ local_enable }}
write_enable={{ write_enable }}
Anon_upload_enable{{ Anon_upload_enable }}
local_umask=022
dirmessage_enable=YES
xferlog_enable=YES
connect_from_port_20=YES
xferlog_std_format=YES
listen=NO
listen_ipv6=YES
pam_service_name=vsftpd
userlist_enable=YES