Use filters to reformat the values of variables for use in subsequent tasks.
Outcomes
Use filters to process and manipulate variables in a playbook or role.
As the student user on the workstation machine, use the lab command to prepare your system for this exercise.
This command creates a Git repository at https://git.lab.example.com/student/data-filters.git, which contains a partially complete playbook project that you finish during this guided exercise.
[student@workstation ~]$ lab start data-filters
Procedure 7.1. Instructions
In this exercise, you deploy an HAProxy load balancer on servera to distribute the incoming web requests between serverb and serverc.
On those two back-end servers, you deploy Apache HTTP Server and some initial content.
The playbooks you use to do this take advantage of filters to set default values and to process and reuse the data stored in existing variables.
Change to the /home/student/git-repos directory, clone the project repository, and then create a branch for this exercise.
In this exercise, you do not change the deploy_haproxy.yml playbook or the haproxy role.
Run the deploy_haproxy.yml playbook to deploy the load balancer.
Create the /home/student/git-repos directory if it does not exist, and then change into it.
[student@workstation ~]$mkdir -p ~/git-repos[student@workstation ~]$cd ~/git-repos
Clone the repository, and change to the project root directory.
[student@workstation git-repos]$git clone \>https://git.lab.example.com/student/data-filters.gitCloning into 'data-filters'... ...output omitted... [student@workstation git-repos]$cd data-filters
Create the exercise branch and check it out.
[student@workstation data-filters]$ git checkout -b exercise
Switched to a new branch 'exercise'Run the deploy_haproxy.yml playbook to deploy the load balancer.
Because you have not deployed the web servers, requests to servera result in a 503 HTTP status code.
[student@workstation data-filters]$ansible-navigator run \>-m stdout deploy_haproxy.ymlPLAY [Ensure HAProxy is deployed] ******************************************** ...output omitted... PLAY RECAP ******************************************************************* loadbalancer_01 : ok=7 changed=6 unreachable=0 ... [student@workstation data-filters]$curl servera<html><body><h1>503 Service Unavailable</h1> ...output omitted...
Carefully review the task list and variables for the apache role.
Review the roles/apache/tasks/main.yml task list file for the apache role.
[student@workstation data-filters]$cat roles/apache/tasks/main.yml...output omitted... - name: Calculate the package list ansible.builtin.set_fact: # TODO: Combine the apache_base_packages and # apache_optional_packages variables into one list.apache_package_list: "{{ apache_base_packages }}" - name: Ensure httpd packages are installed ansible.builtin.yum: name: "{{apache_package_list}}" state: present # TODO: omit the 'enablerepo' directive # below if the apache_enablerepos_list is empty; # otherwise use the list as the value for the # 'enablerepo' directive.#enablerepo: "{{ apache_enablerepos_list }}"...output omitted...
The apache_package_list variable must be a combined list of both the base and optional packages required to install the httpd service.
You edit and correct the definition of this variable in a later step.
The variable of the apache_base_packages role is defined as a list that contains one package, httpd.
To prevent host group variables from overriding this value, the variable is defined in the roles/apache/vars/main.yml file:
apache_base_packages: - httpd
The apache_optional_packages variable is defined in the role as an empty list in roles/apache/defaults/main.yml file:
apache_optional_packages: []
The apache_enabledrepos_list variable contains a list of Yum repository IDs.
Any repository ID in this list is temporarily enabled to install any packages.
The default value is an empty list, as defined in the roles/apache/defaults/main.yml file:
apache_enablerepos_list: []
Correct the Jinja2 expression that defines the apache_package_list variable in the first task of the apache role.
Remove the TODO comment section and save the file.
Define the apache_optional_packages variable for the web_servers host group to contain the following values: git, php, and php-mysqlnd.
Edit the Jinja2 expression in the first task of the apache role that defines the apache_package_list variable.
Add the union filter to create a single list from the lists stored in the apache_base_packages and apache_optional_packages variables.
When you finish, remove the comments from this task.
The first task of the roles/apache/tasks/main.yml file should consist of the following content:
- name: Calculate the package list
ansible.builtin.set_fact:
apache_package_list: "{{ apache_base_packages | union(apache_optional_packages) }}"The apache_package_list variable definition is a single line of text.
Do not split Jinja2 filter expressions over multiple lines, or your playbook will fail.
Save the file.
Define the apache_optional_packages variable in a new file called group_vars/web_servers/apache.yml.
The variable defines a list of three packages: git, php, and php-mysqlnd.
This overrides the role's default value for this variable.
The group_vars/web_servers/apache.yml file should consist of the following content:
apache_optional_packages: - git - php - php-mysqlnd
Save the file.
Remove the comments from the enablerepo directive in the second task from the apache role.
Edit the directive's Jinja2 expression to use the default filter to omit this directive if the variable evaluates to a Boolean value of false.
Remove the TODO comment section for the second task, and save the task file.
The second task should consist of the following content:
- name: Ensure httpd packages are installed
ansible.builtin.yum:
name: "{{ apache_package_list }}"
state: present
enablerepo: "{{ apache_enablerepos_list | default(omit, true) }}"The completed roles/apache/tasks/main.yml file should consist of the following content:
---
# tasks file for apache
- name: Calculate the package list
ansible.builtin.set_fact:
apache_package_list: "{{ apache_base_packages | union(apache_optional_packages) }}"
- name: Ensure httpd packages are installed
ansible.builtin.yum:
name: "{{ apache_package_list }}"
state: present
enablerepo: "{{ apache_enablerepos_list | default(omit, true) }}"
- name: Ensure SELinux allows httpd connections to a remote database
seboolean:
name: httpd_can_network_connect_db
state: true
persistent: true
- name: Ensure httpd service is started and enabled
ansible.builtin.service:
name: httpd
state: started
enabled: trueRun the deploy_apache.yml playbook with the -v option.
Verify that optional packages are included in the apache_package_list fact.
[student@workstation data-filters]$ansible-navigator run \>-m stdout deploy_apache.yml -v...output omitted... TASK [apache : Calculate the package list] *********************************** ok: [webserver_01] => {"ansible_facts": {"apache_package_list":["httpd", "git", "php", "php-mysqlnd"]}, "changed": false} ...output omitted...
Review the task list and variable definitions for the webapp role.
The webapp role ensures that the correct web application content exists on each host.
When correctly implemented, the role removes any content from the root web directory that does not belong to the web application.
Edit the three tasks in the webapp role that have a TODO comment.
Use filters to implement the functionality indicated in each comment.
Review the tasks in the roles/webapp/tasks/main.yml file:
---
# tasks file for webapp
- name: Ensure stub web content is deployed
ansible.builtin.copy:
content: "{{ webapp_message }} (version {{ webapp_version }})\n"
dest: "{{ webapp_content_root_dir }}/index.html"
- name: Find deployed webapp files
ansible.builtin.find:
paths: "{{ webapp_content_root_dir }}"
recurse: true
register: webapp_find_files
- name: Compute webapp file list
ansible.builtin.set_fact:
# TODO: Use the map filter to extract
# the 'path' attribute of each entry
# in the 'webapp_find_files'
# variable 'files' list.
webapp_deployed_files: []
- name: Compute relative webapp file list
ansible.builtin.set_fact:
# TODO: Use the 'map' filter, along with
# the 'relpath' filter, to create the
# 'webapp_rel_deployed_files' variable
# from the 'webapp_deployed_files' variable.
#
# Files in the 'webapp_rel_deployed_files'
# variable should have a path relative to
# the 'webapp_content_root_dir' variable.
webapp_rel_deployed_files: []
- name: Remove Extraneous Files
ansible.builtin.file:
path: "{{ webapp_content_root_dir }}/{{ item }}"
state: absent
# TODO: Loop over a list of files
# that are in the 'webapp_rel_deployed_files'
# list, but not in the 'webapp_file_list' list.
# Use the difference filter.
loop: []The webapp_file_list variable is defined in the roles/webapp/vars/main.yml file.
This variable defines a file manifest for the web application.
For this version of the web application, the only file in the application is index.html.
The webapp_content_root_dir variable defines the directory location of web application content on each web server.
The default value is /var/www/html.
Replace the empty list for the webapp_deployed_files variable in the third task of the webapp role with a Jinja2 expression.
Start with the webapp_find_files['files'] variable and apply the map filter, followed by the list filter.
Provide the map filter with an argument of attribute='path' to retrieve the path attribute from each entry in the list.
After you remove the TODO comments, the third task should consist of the following content:
- name: Compute the webapp file list
ansible.builtin.set_fact:
webapp_deployed_files: "{{ webapp_find_files['files'] | map(attribute='path') | list }}"The webapp_deployed_files variable definition is a single line of text.
Do not split Jinja2 filter expressions over multiple lines, or your playbook will fail.
Replace the empty list for the webapp_rel_deployed_files variable in the fourth task of webapp role with a Jinja2 expression.
Start with the webapp_deployed_files variable and apply the map filter, followed by the list filter.
The first argument to the map function is the relpath string, which executes the relpath function on each item of the webapp_deployed_files list.
The second argument to the map function is the webapp_content_root_dir variable.
This variable is passed as an argument to the relpath function.
After you remove the TODO comments, the fourth task should consist of the following content:
- name: Compute the relative webapp file list
ansible.builtin.set_fact:
webapp_rel_deployed_files: "{{ webapp_deployed_files | map('relpath', webapp_content_root_dir) | list }}"The webapp_rel_deployed_files variable definition is a single line of text.
Do not split Jinja2 filter expressions over multiple lines, or your playbook will fail.
Replace the empty loop list in the fifth task in the webapp role with a Jinja2 expression.
Start with the webapp_rel_deployed_files variable and apply the difference filter.
Provide the webapp_file_list variable as an argument to the difference filter.
After you remove the TODO comments, the fifth task should consist of the following content:
- name: Remove Extraneous Files
ansible.builtin.file:
path: "{{ webapp_content_root_dir }}/{{ item }}"
state: absent
loop: "{{ webapp_rel_deployed_files | difference(webapp_file_list) }}"Save the changes to the roles/webapp/tasks/main.yml file.
The completed roles/webapp/tasks/main.yml file should consist of the following content:
---
# tasks file for webapp
- name: Ensure stub web content is deployed
ansible.builtin.copy:
content: "{{ webapp_message }} (version {{ webapp_version }})\n"
dest: "{{ webapp_content_root_dir }}/index.html"
- name: Find deployed webapp files
ansible.builtin.find:
paths: "{{ webapp_content_root_dir }}"
recurse: true
register: webapp_find_files
- name: Compute the webapp file list
ansible.builtin.set_fact:
webapp_deployed_files: "{{ webapp_find_files['files'] | map(attribute='path') | list }}"
- name: Compute the relative webapp file list
ansible.builtin.set_fact:
webapp_rel_deployed_files: "{{ webapp_deployed_files | map('relpath', webapp_content_root_dir) | list }}"
- name: Remove Extraneous Files
ansible.builtin.file:
path: "{{ webapp_content_root_dir }}/{{ item }}"
state: absent
loop: "{{ webapp_rel_deployed_files | difference(webapp_file_list) }}"Run the deploy_webapp.yml playbook with the -v option.
Verify that the playbook identifies a non-application file on webserver_01 and removes it.
[student@workstation data-filters]$ansible-navigator run \>-m stdout deploy_webapp.yml -v...output omitted... TASK [webapp : Compute the webapp file list] ********************************* ok: [webserver_01] => {"ansible_facts": {"webapp_deployed_files": ["/var/www/html/test.html", "/var/www/html/index.html"]}, "changed": false} ok: [webserver_02] => {"ansible_facts": {"webapp_deployed_files": ["/var/www/html/index.html"]}, "changed": false} TASK [webapp : Compute the relative webapp file list] ************************ ok: [webserver_01] => {"ansible_facts": {"webapp_rel_deployed_files": ["test.html", "index.html"]}, "changed": false} ok: [webserver_02] => {"ansible_facts": {"webapp_rel_deployed_files": ["index.html"]}, "changed": false} TASK [webapp : Remove Extraneous Files] **************************************changed: [webserver_01] => (item=test.html) => {"ansible_loop_var": "item", "changed": true, "item": "test.html", "path": "/var/www/html/test.html", "state": "absent"}PLAY RECAP ******************************************************************* webserver_01 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 ... webserver_02 : ok=4 changed=1 unreachable=0 failed=0 skipped=1 ...
The webserver_01 host initially has two files deployed in the /var/www/html directory: test.html and index.html.
Someone with access to the host might have installed a temporary test page on the web server.
The playbook removes any web server files from the web content root directory that are not part of the actual web application.