Bookmark this page

Guided Exercise: Processing Variables Using Filters

  • 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.

  1. 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.

    1. 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
    2. Clone the repository, and change to the project root directory.

      [student@workstation git-repos]$ git clone \
      > https://git.lab.example.com/student/data-filters.git
      Cloning into 'data-filters'...
      ...output omitted...
      [student@workstation git-repos]$ cd data-filters
    3. Create the exercise branch and check it out.

      [student@workstation data-filters]$ git checkout -b exercise
      Switched to a new branch 'exercise'
    4. 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.yml
      
      PLAY [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...
  2. Carefully review the task list and variables for the apache role.

    1. 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: []
  3. 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.

    1. 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) }}"

      Warning

      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.

    2. 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.

  4. 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: true
  5. Run 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...
  6. 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.

    1. 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.

    2. 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 }}"

      Warning

      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.

    3. 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 }}"

      Warning

      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.

    4. 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) }}"
  7. 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.

Finish

On the workstation machine, change to the student user home directory and use the lab command to complete this exercise. This step is important to ensure that resources from previous exercises do not impact upcoming exercises.

[student@workstation ~]$ lab finish data-filters

This concludes the section.

Revision: do374-2.2-82dc0d7