Definition
A handler
is a type of task in Ansible that is intended to be executed after being called by other tasks
The main differences between a task and a handler:
- Handlers are executed at the very end of a group of tasks and can be called up to 3 times (see the previous post about pre_tasks/post_tasks), while the order of execution will correspond to the order of recording
- Handlers are called by the
notify: <handler_name>
directive and are executed only if the task result has theCHANGED
status - The handler will be executed once, even if it was called several times
According to the official documentation, handlers should be used to restart or reboot services, but their scope of application does not end there; in general, the use of a handler looks like this:
tasks:
- name: Configure network
ansible.builtin.template:
dest: /etc/network/interfaces
src: networking.j2
owner: root
group: root
mode: "0644"
backup: true
notify: Restart networking
handlers:
- name: Restart networking
ansible.builtin.systemd:
name: networking
state: restarted
Practice
Logically, the task below is equivalent to the task above, but using when: <some_var>.changed
is very bad ansible practice:
tasks:
- name: Configure network
ansible.builtin.template:
dest: /etc/network/interfaces
src: networking.j2
owner: root
group: root
mode: "0644"
backup: true
register: network_config
- name: Restart networking
ansible.builtin.systemd:
name: networking
state: restarted
when: network_config.changed
But that’s not all, handlers can do much more, for example:
A handler can call other handlers or even a group of tasks via include_tasks
or import_tasks
:
tasks:
- name: Always changed
ansible.builtin.command: /bin/true
notify:
- First handler
handlers:
- name: First handler
ansible.builtin.command: /bin/true
notify: Inlcude tasks
- name: Inlcude tasks
ansible.builtin.include_tasks: tasks/main.
You can change the name to call the handler by using a Jinja2 pattern in the handler name, or by using the listen:
directive, but only static strings can be used for listen:
:
vars:
service: apache2
tasks:
- name: Always changed
ansible.builtin.command: /bin/true
notify:
- Restart {{ service }}
- Reboot
handlers:
- name: Restart {{ service }}
ansible.builtin.systemd:
name: "{{ service }}"
state: restarted
- name: Reboot task
ansible.builtin.reboot:
listen: Reboot
Using the ansible.builtin.meta:flush_handlers
module, you can execute all previously called handlers and reset their counter, allowing you to call them again:
tasks:
- name: Configure network
ansible.builtin.template:
dest: /etc/network/interfaces
src: networking.j2
owner: root
group: root
mode: "0644"
backup: true
notify: Restart networking
- name: Restart right away
ansible.builtin.meta: flush_handlers
- name: Notify again
ansible.builtin.command: /bin/true
notify: Restart networking
handlers:
- name: Restart networking
ansible.builtin.systemd:
name: networking
state: restarted
Since roles
is simply “included” in tasks
, it is possible to call handlers from a role (an example can also be found in the previous post), and for convenience, it is possible to use the role_name/FQCN_role : handler name
construction, for example:
- name: Always change
ansible.builtin.command: /bin/true
notify: 'k3s.orchestration.raspberrypi : Reboot Pi'
Based on point 3, the only condition for calling the handler is the CHANGED
status, and, as a consequence, you can define the change condition yourself by changed_when: <condition>
:
tasks:
- name: Run sample task
ansible.builtin.command: /bin/true
changed_when: false
notify: Say hello
# Will not be called due to 'changed_when: false'
handlers:
- name: Say hello
ansible.builtin.debug:
msg: Hello guys