Read this in other languages:
English, 日本語, Portugues do Brasil, Française, Español.
Expanding on Exercise 1.4, this exercise introduces the application of conditionals, handlers, and loops in Ansible playbooks. You’ll learn to control task execution with conditionals, manage service responses with handlers, and efficiently handle repetitive tasks using loops.
Conditionals, handlers, and loops are advanced features in Ansible that enhance control, efficiency, and flexibility in your automation playbooks.
notify
directive, typically used for restarting services after changes.Conditionals in Ansible control whether a task should run based on certain conditions.
Let’s add to the system_setup.yml playbook the ability to install the Apache HTTP Server (httpd
) only on hosts that belong to the web
group in our inventory.
NOTE: Previous examples had hosts set to node1 but now it is set to all. This means when you run this updated Ansible playbook you will notice updates for the new systems being automated against, the user Roger created on all new systems and the Apache web server package httpd installed on all the hosts within the web group.
---
- name: Basic System Setup
hosts: all
become: true
vars:
user_name: 'Roger'
package_name: httpd
tasks:
- name: Update all security-related packages
ansible.builtin.dnf:
name: '*'
state: latest
security: true
update_only: true
- name: Create a new user
ansible.builtin.user:
name: "{{ user_name }}"
state: present
create_home: true
- name: Install Apache on web servers
ansible.builtin.dnf:
name: "{{ package_name }}"
state: present
when: inventory_hostname in groups['web']
In this example, inventory_hostname in groups['web']
is the conditional statement. inventory_hostname
refers to the name of the current host that Ansible is working on in the playbook. The condition checks if this host is part of the web
group defined in your inventory file. If true, the task will execute and install Apache on that host.
Handlers are used for tasks that should only run when notified by another task. Typically, they are used to restart services after a configuration change.
Let’s say we want to ensure the firewall is configured correctly on all web servers and then reload the firewall service to apply any new settings. We’ll define a handler that reloads the firewall service and is notified by a task that ensures the desired firewall rules are in place:
---
- name: Basic System Setup
hosts: all
become: true
.
.
.
- name: Install firewalld
ansible.builtin.dnf:
name: firewalld
state: present
when: inventory_hostname in groups['web']
- name: Ensure firewalld is running
ansible.builtin.service:
name: firewalld
state: started
enabled: true
when: inventory_hostname in groups['web']
- name: Allow HTTP traffic on web servers
ansible.posix.firewalld:
service: http
permanent: true
state: enabled
when: inventory_hostname in groups['web']
notify: Reload Firewall
handlers:
- name: Reload Firewall
ansible.builtin.service:
name: firewalld
state: reloaded
The handler Restart Apache is triggered only if the task “Allow HTTP traffic on web servers” makes any changes.
NOTE: Notice how the name of the handler is used within the notify section of the “Reload Firewall” configuration task. This ensures that the proper handler is executed as there can be multiple handlers within an Ansible playbook.
PLAY [Basic System Setup] ******************************************************
TASK [Gathering Facts] *********************************************************
ok: [node1]
ok: [node2]
ok: [ansible-1]
ok: [node3]
TASK [Update all security-related packages] ************************************
ok: [node2]
ok: [node1]
ok: [ansible-1]
ok: [node3]
TASK [Create a new user] *******************************************************
ok: [node1]
ok: [node2]
ok: [ansible-1]
ok: [node3]
TASK [Install Apache on web servers] *******************************************
skipping: [ansible-1]
ok: [node2]
ok: [node1]
ok: [node3]
TASK [Install firewalld] *******************************************************
skipping: [ansible-1]
changed: [node2]
changed: [node1]
changed: [node3]
TASK [Ensure firewalld is running] *********************************************
skipping: [ansible-1]
changed: [node3]
changed: [node2]
changed: [node1]
TASK [Allow HTTPS traffic on web servers] **************************************
skipping: [ansible-1]
changed: [node2]
changed: [node1]
changed: [node3]
RUNNING HANDLER [Reload Firewall] **********************************************
changed: [node2]
changed: [node1]
changed: [node3]
PLAY RECAP *********************************************************************
ansible-1 : ok=5 changed=2 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
node1 : ok=8 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node2 : ok=8 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node3 : ok=8 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Loops in Ansible allow you to perform a task multiple times with different values. This feature is particularly useful for tasks like creating multiple user accounts in our given example. In the original system_setup.yml playbook from Exercise 1.4, we had a task for creating a single user:
- name: Create a new user
ansible.builtin.user:
name: "{{ user_name }}"
state: present
create_home: true
Now, let’s modify this task to create multiple users using a loop:
- name: Create a new user
ansible.builtin.user:
name: "{{ item }}"
state: present
create_home: true
loop:
- alice
- bob
- carol
What Changed?
Loop Directive: The loop keyword is used to iterate over a list of items. In this case, the list contains the names of users we want to create: alice, bob, and carol.
User Creation with Loop: Instead of creating a single user, the modified task
now iterates over each item in the loop list. The {{ item }}
placeholder is dynamically replaced with each username in the list, so the ansible.builtin.user module creates each user in turn.
When you run the updated playbook, this task is executed three times, once for each user specified in the loop. It’s an efficient way to handle repetitive tasks with varying input data.
Snippet of the output for creating a new user on all the nodes.
[student@ansible-1 ~lab_inventory]$ ansible-navigator run system_setup.yml -m stdout
PLAY [Basic System Setup] ******************************************************
.
.
.
TASK [Create a new user] *******************************************************
changed: [node2] => (item=alice)
changed: [ansible-1] => (item=alice)
changed: [node1] => (item=alice)
changed: [node3] => (item=alice)
changed: [node1] => (item=bob)
changed: [ansible-1] => (item=bob)
changed: [node3] => (item=bob)
changed: [node2] => (item=bob)
changed: [node1] => (item=carol)
changed: [node3] => (item=carol)
changed: [ansible-1] => (item=carol)
changed: [node2] => (item=carol)
.
.
.
PLAY RECAP *********************************************************************
ansible-1 : ok=5 changed=1 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
node1 : ok=7 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node2 : ok=7 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node3 : ok=7 changed=1 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