Skip to content

Latest commit

 

History

History
 
 

1.5-handlers

Workshop Exercise - Conditionals, Handlers and Loops

Read this in other languages:
uk English, japan日本語, brazil Portugues do Brasil, france Française,Español Español.

Table of Contents

Objective

Three foundational Ansible features are:

Guide

Step 1 - Conditionals

Ansible can use conditionals to execute tasks or plays when certain conditions are met.

To implement a conditional, the when statement must be used, followed by the condition to test. The condition is expressed using one of the available operators like e.g. for comparison:

== Compares two objects for equality.
!= Compares two objects for inequality.
> true if the left hand side is greater than the right hand side.
>= true if the left hand side is greater or equal to the right hand side.
< true if the left hand side is lower than the right hand side.
<= true if the left hand side is lower or equal to the right hand side.

For more on this, please refer to the documentation: http://jinja.pocoo.org/docs/2.10/templates/

As an example you would like to install an FTP server, but only on hosts that are in the "ftpserver" inventory group.

To do that, first edit the inventory to add another group, and place node2 in it. The section to add looks like this:

[ftpserver]
node2

Edit the inventory ~/lab_inventory/hosts to add those lines. When you are done, it will look similar to the following listing:

Tip

The ansible_host variable only needs to be specified once for a node. When adding a node to other groups, you do not need to specify the variable again.

Important Do not copy/paste the example below. Just edit the file to add the above lines.

[web]
node1 ansible_host=xx.xx.xx.xx
node2 ansible_host=xx.xx.xx.xx
node3 ansible_host=xx.xx.xx.xx

[ftpserver]
node2

[control]
ansible-1 ansible_host=xx.xx.xx.xx

Next create the file ftpserver.yml on your control host in the ~/ansible-files/ directory:

---
- name: Install vsftpd on ftpservers
  hosts: all
  become: true
  tasks:
    - name: Install FTP server when host in ftpserver group
      ansible.builtin.yum:
        name: vsftpd
        state: latest
      when: inventory_hostname in groups["ftpserver"]

Tip

By now you should know how to run Ansible Playbooks, we’ll start to be less verbose in this guide. Go create and run it. :-)

Run it and examine the output. The expected outcome: The task is skipped on node1, node3 and the ansible host (your control host) because they are not in the ftpserver group in your inventory file.

TASK [Install FTP server when host in ftpserver group] *******************************************
skipping: [ansible-1]
skipping: [node1]
skipping: [node3]
changed: [node2]

Step 2 - Handlers

Sometimes when a task does make a change to the system, an additional task or tasks may need to be run. For example, a change to a service’s configuration file may then require that the service be restarted so that the changed configuration takes effect.

Here Ansible’s handlers come into play. Handlers can be seen as inactive tasks that only get triggered when explicitly invoked using the "notify" statement. Read more about them in the Ansible Handlers documentation.

As a an example, let’s write a playbook that:

  • manages Apache’s configuration file /etc/httpd/conf/httpd.conf on all hosts in the web group
  • restarts Apache when the file has changed

First we need the file Ansible will deploy, let’s just take the one from node1. Remember to replace the IP address shown in the listing below with the IP address from your individual node1.

[student@ansible-1 ansible-files]$ scp node1:/etc/httpd/conf/httpd.conf ~/ansible-files/files/.
httpd.conf

Next, create the Playbook httpd_conf.yml. Make sure that you are in the directory ~/ansible-files.

---
- name: Manage httpd.conf
  hosts: web
  become: true
  tasks:

    - name: Copy Apache configuration file
      ansible.builtin.copy:
        src: httpd.conf
        dest: /etc/httpd/conf/
        mode: '644'
      notify:
        - Restart_apache

  handlers:
    - name: Restart_apache
      ansible.builtin.service:
        name: httpd
        state: restarted

So what’s new here?

  • The "notify" section calls the handler only when the copy task actually changes the file. That way the service is only restarted if needed - and not each time the playbook is run.
  • The "handlers" section defines a task that is only run on notification.

Run the playbook. We didn’t change anything in the file yet so there should not be any changed lines in the output and of course the handler shouldn’t have fired.

  • Now change the Listen 80 line in ~/ansible-files/files/httpd.conf to:
Listen 8080
  • Run the playbook again. Now the Ansible’s output should be a lot more interesting:

    • httpd.conf should have been copied over
    • The handler should have restarted Apache

Apache should now listen on port 8080. Easy enough to verify:

[student@ansible-1 ansible-files]$ curl http://node1
curl: (7) Failed to connect to node1 port 80: Connection refused
[student@ansible-1 ansible-files]$ curl http://node1:8080
<body>
<h1>Apache is running fine</h1>
</body>

Leave the setting for listen on port 8080. We'll use this in a later exercise.

Step 3 - Simple Loops

Loops enable us to repeat the same task over and over again. For example, lets say you want to create multiple users. By using an Ansible loop, you can do that in a single task. Loops can also iterate over more than just basic lists. For example, if you have a list of users with their coresponding group, loop can iterate over them as well. Find out more about loops in the Ansible Loops documentation.

To show the loops feature we will generate three new users on node1. For that, create the file loop_users.yml in ~/ansible-files on your control node as your student user. We will use the user module to generate the user accounts.

---
- name: Ensure users
  hosts: node1
  become: true

  tasks:
    - name: Ensure three users are present
      ansible.builtin.user:
        name: "{{ item }}"
        state: present
      loop:
         - dev_user
         - qa_user
         - prod_user

Understand the playbook and the output:

  • The names are not provided to the user module directly. Instead, there is only a variable called {{ item }} for the parameter name.
  • The loop keyword lists the actual user names. Those replace the {{ item }} during the actual execution of the playbook.
  • During execution the task is only listed once, but there are three changes listed underneath it.

Step 4 - Loops over hashes

As mentioned loops can also be over lists of hashes. Imagine that the users should be assigned to different additional groups:

- username: dev_user
  groups: ftp
- username: qa_user
  groups: ftp
- username: prod_user
  groups: apache

The user module has the optional parameter groups to list additional users. To reference items in a hash, the {{ item }} keyword needs to reference the subkey: {{ item.groups }} for example.

Let's rewrite the playbook to create the users with additional user rights:

---
- name: Ensure users
  hosts: node1
  become: true

  tasks:
    - name: Ensure three users are present
      ansible.builtin.user:
        name: "{{ item.username }}"
        state: present
        groups: "{{ item.groups }}"
      loop:
        - { username: 'dev_user', groups: 'ftp' }
        - { username: 'qa_user', groups: 'ftp' }
        - { username: 'prod_user', groups: 'apache' }

Check the output:

  • Again the task is listed once, but three changes are listed. Each loop with its content is shown.

Verify that the user dev_user was indeed created on node1 using the following playbook:

{% raw %}

---
- name: Get user ID
  hosts: node1
  vars:
    myuser: "dev_user"
  tasks:
    - name: Get {{ myuser }} info
      ansible.builtin.getent:
        database: passwd
        key: "{{ myuser }}"
    - ansible.builtin.debug:
        msg:
          - "{{ myuser }} uid: {{ getent_passwd[myuser].1 }}"

{% endraw %}

$ ansible-navigator run user_id.yml -m stdout

PLAY [Get user ID] *************************************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Get dev_user info] *******************************************************
ok: [node1]

TASK [debug] *******************************************************************
ok: [node1] => {
    "msg": [
        "dev_user uid: 1002"
    ]
}

PLAY RECAP *********************************************************************
node1                      : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Navigation
Previous Exercise - Next Exercise

Click here to return to the Ansible for Red Hat Enterprise Linux Workshop