Handlers are just like regular tasks in ansible that only run when notified. Handlers are a very useful as well as important concept in Ansible.
There are two directives involved in running handler tasks.
- Notify directive which will send the trigger signal to the tasks to run.
- Handler directive under which tasks are grouped.
Let’s talk about a practical use case. Let’s say you want to write an SSH hardening playbook where you will be making some changes to the ssh config file.
As you know any configuration changes will need service restart for the changes to be effective. This is where handlers are very effective.
Once the task makes the changes in the sshd configuration file, we can send a signal to a task that will restart the sshd service.
Table of Contents
Ansible Handlers Key Points
Before diving into the hands-on part, let’s understand a few important points with respect to handlers.
- Handler tasks will only run when the parent task makes changes (changed=true).
- Handler tasks will only run at the end of each play, but there are options to run it wherever we want.
- Irrespective of how many times you call the same handler task in the play on a single host, the handler task will run only once.
- Task names should be unique. If two task has the same name, only the first task will run.
- Tasks will be executed in the order they are defined under the handler directive but not in the order called by notify directive.
Syntax
To define a handler task, notify and handler directive should be used. Notify directive will send the signal to the handler task to execute.
Below is the sample playbook. I have two tasks defined under the handler directive. To run these tasks, a notify directive is used and the task name is passed as the value.
- name: Handlers testing
hosts: ubuntu.anslab.com
gather_facts: false
tasks:
- name: Get the hostname
shell: hostname -s
register: hostname
notify: print hostname
handlers:
- name: print hostname
debug:
var: hostname.stdout
- name: print IP
debug:
var: IP.stdoutBelow is the output from the playbook. Even though I have two tasks in the handler, only one task runs which is called via notify directive.
Calling Multiple Tasks
Under a single notify directive you can call multiple handler tasks. Give the handler tasks name in the YAML list format.
- name: Get the hostname
shell: hostname -s
register: hostname
notify:
- print hostname
- print IPPython list notation can also be used to call multiple handler tasks.
- name: Get the hostname
shell: hostname -s
register: hostname
notify: ["print hostname", "print IP"]Order of Execution
Handler tasks will run only at the end of the play even when it is called before any tasks.
- name: Handlers testing
hosts: ubuntu.anslab.com
gather_facts: false
tasks:
- name: Get the hostname
shell: hostname -s
register: hostname
notify: print hostname
- name: Get IP address of the hostname
shell: hostname -I
register: IP
notify: print IP
handlers:
- name: print hostname
debug:
var: hostname.stdout
- name: print IP
debug:
var: IP.stdoutTake a look at the above playbook. The first task (Get the hostname) is calling the handler task(print hostname). The second task is calling the second handler task(print IP).
When I run the playbook, both tasks will execute first and send the signal to the handler and the handler task will run once all the tasks are completed.
Heads Up: irrespective of how the handler task is called, it will execute in the order it is defined in the handler directive.
Run Only Once
You may call the same handler task multiple times, but the handler task will run only one time.
I am running the same playbook which I used in the previous section but modified the notify directive to call the same handler task (print hostname).
- name: Handlers testing
hosts: ubuntu.anslab.com
gather_facts: false
tasks:
- name: Get the hostname
shell: hostname -s
register: hostname
notify: print hostname
- name: Get IP address of the hostname
shell: hostname -I
register: IP
notify: print hostname
handlers:
- name: print hostname
debug:
var: hostname.stdout
- name: print IP
debug:
var: IP.stdoutAs you can see from the output below, the print hostname handler task ran only once.
Duplicate Tasks
As I said in the introduction section, you should give descriptive and unique names to the tasks. If two or more tasks are defined with the same name only the first task read by ansible will run ignoring all other tasks with the same name.
If you take a look at the below tasks, both the task names are the same. Now when I run the playbook, ansible will run the first handler task and print the IP.
handlers:
- name: print hostname
debug:
var: IP.stdout
- name: print hostname
debug:
var: hostname.stdoutHandling Task Failures
In ansible, if a task fails the subsequent tasks will not run. How handlers handle the failures is, that if any task gets failed in the particular host, then the handler task will not run for that host even if the failed task is not the parent task (notify directive).
If you can take a look at the below playbook, there are two tasks that use the shell module. The first task uses /bin/true and it will always run fine. This task calls the handler task (run_now) which simply prints a message to stdout. The second task is set to fail by using /bin/false.
- name: Testing handler
hosts: ubuntu.anslab.com
gather_facts: false
tasks:
- name: set a task to success
shell: /bin/true
notify: run_now
- name: set a task to fail
shell: /bin/false
handlers:
- name: run_now
debug: msg="I am called from [ task 1 ]"If you can see the below image, the output of the first task runs fine and a signal is sent to the handler task to run but the next task in the same host gets failed so the handler task is not running.
Let’s see how to handle task failures with different options.
1. Force Handlers
You can set the property force_handlers: true in the playbook which will run the handler task even though you have task failures.
You can also set this parameter in different areas.
- Playbook ⇒
force_handlers: true - ansible.cfg file ⇒
force_handlers = true - Command line argument ⇒
--force-handlers
2. Ignore Errors
You can also set the property ignore_errors: true which will ignore the failed task and will execute the handler tasks.
Flushing Handler
By this time you should have understood that the handler task will run only at the end of the play. But there is a way to make it run at any point as we wish. This can be achieved through the meta module flush_handler command.
$ ansible-doc meta
Flush_handlers will run all the tasks which already sent the signal to the notify directive.
- name: Testing handler
hosts: ubuntu.anslab.com
gather_facts: false
tasks:
- name: set a task to success
shell: /bin/true
notify: run_now
- name: Run handler now
meta: flush_handlers
- name: set a task to fail
shell: /bin/false
handlers:
- name: run_now
debug: msg="I am called from [ task 1 ]"As you can see from the above output the handler task runs as the second task in the play.
Run Handler Task Using "Listen"
Till now we have triggered all the handler tasks using the task name. Using "listen", you can group multiple tasks and run all the tasks through the notify statement. This is a good alternate option for tags when using handlers.
If you see the below playbook, I have set listen to "all task" and calling it through the notify directive.
- name: Testing handler
hosts: ubuntu.anslab.com
gather_facts: false
tasks:
- name: set a task to success
shell: /bin/true
notify: "all task"
handlers:
- name: handler task 1
debug:
msg: This is handler task 1
listen: "all task"
- name: handler task 2
debug:
msg: This is handler task 2
listen: "all task"
- name: handler task 3
debug:
msg: This is handler task 3
listen: "all task"Conclusion
In this article we have seen what handlers are and how to use notify and handler directives in the ansible playbook to achieve different objectives.
We have also seen how to handle the task failures with two different options. Finally, we concluded the article with how to trigger multiple tasks using the listen directive.
Resource:










