handlers are used to do something at the end of a playbook, after all prior tasks have been run. Often, handlers are used to perform cleanup tasks at the very end of a playbook.
As an example, let's say a cleanup script must always be run as the last task in the playbook. Here is how you could run cleanup.sh as the last task in the playbook.
---
- hosts: all
tasks:
- name: first shell command
shell: <some command>
notify: cleanup <- must be an exact match of the handler name
- name: second shell command
shell: <some command>
notify: cleanup <- must be an exact match of the handler name
handlers:
- name: cleanup
script: cleanup.sh
...
Something like this should be returned which shows that the cleanup handler was run as the last task in the playbook.
TASK [first shell command]
changed: [server1.example.com]
TASK [second shell command]
changed: [server1.example.com]
RUNNING HANDLER [cleanup]
changed: [server1.example.com]
However, consider the scenario where the first shell command returns fatal. In this scenario, the handler would NOT be run.
TASK [first shell command]
fatal: [server1.example.com]: FAILED! => {"changed": true, "cmd": "ls /tmp/files", "delta": "0:00:00.006940", "end": "2023-02-28 03:07:55.479674", "msg": "non-zero return code", "rc": 2, "start": "2023-02-28 03:07:55.472734", "stderr": "ls: cannot access /tmp/files: No such file or directory", "stderr_lines": ["ls: cannot access /tmp/files: No such file or directory"], "stdout": "", "stdout_lines": []}
server1.example.com : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
I think the most stable solution here is to use block and rescue to ensure that the cleanup tasks are run even when something fatal occurs. You may not even want or need to use handlers and notify in a scenario like this.
---
- hosts: all
- block:
- shell: ls /bogus
rescue:
- script: cleanup.sh
...
Handlers are often created in a role.
roles/foo/handlers/main.yml
In this scenario, you could include the handler in the role like this. You may want to also include changed_when: true and failed_when: false to ensure the handler is run, even when one or more task return fatal.
- Using the --force-handlers command line option
- Using force_handlers: true in ansible.cfg or in your playbook
---
- hosts: all
tasks:
- name: first shell command
shell: <some command>
changed_when: true <- include this if the handler must always be run
failed_when: false <- include this if the handler must always be run
notify: cleanup <- must be an exact match of the handler name
- name: second shell command
shell: <some command>
changed_when: true <- include this if the handler must always be run
failed_when: false <- include this if the handler must always be run
notify: cleanup <- must be an exact match of the handler name
handlers:
- include: roles/foo/handlers/main.yml
...
Tasks are performed in this order.
- pre_tasks
- tasks
- post_tasks
Did you find this article helpful?
If so, consider buying me a coffee over at