Skip to content

Decoupling Ansible Handlers

Sysadmin Ansible

Since version Ansible 2.2, so quite a while, there is another way to notify a handler or a couple of handlers or no handlers. No handlers? Right, this sounds a bit funny, doesn’t it? Well, it is a bit comparable with talking to kids: if no one is listening, no one will react :).

Notifying Handlers

Before 2.2, there was only one way to notify a handler, by notifying the name of the handler:

 1...
 2tasks:
 3  - copy:
 4      src: main.cf
 5      dest: /etc/postfix/main.cf
 6    notify: restart postfix
 7
 8handlers:
 9  - name: restart postfix
10    service: name=postfix state=restarted

This is simple and works great, especially if your handler is logically related to the task, like in a playbook or within a role. Because with using the name, the handler and the task are directly coupled.

When you run a playbook, Ansible makes all handlers available to the play (or tasks stage). So we are not limited in notifying only the handlers within the same role. If we are aware of the handler names of other roles, we can notify those as well.

There is nothing against this practice in general, except that the two roles are now coupled and not generic anymore.

Subscribe to Changes

We extend our example and assume we want to get notified for a particular change of the postfix config. The usual way to handle this would be to add another handler name to the notify clause of the task:

 1...
 2tasks:
 3- name: configure postfix
 4  template:
 5    src: main.cf.j2
 6    dest: /etc/postfix/main.cf
 7  notify:
 8    - restart postfix
 9    - post on slack
10
11handlers:
12  - name: restart postfix
13    service:
14      name: postfix
15      state: restarted
16
17  - name: post on slack
18    slack:
19      token: ...
20      msg: ...
21    delegate_to: localhost

Done!

Our postfix playbook has all we want, and we think about turning it into a generic postfix role and sharing it on Galaxy.

The slack handler is nothing we want to put in a generic postfix role, right? Further, having a notify for a handler located outside a role is not practical. Let’s remove the slack notification from the role and change the playbook to use the postfix role.

1# file: role/postfix/tasks/main.yml
2- name: configure postfix
3  template:
4    src: main.cf.j2
5    dest: /etc/postfix/main.cf
6  notify:
7    - restart postfix
1# file: role/postfix/handlers/main.yml
2- name: restart postfix
3  service:
4    name: postfix
5    state: restarted

We remove the postfix task in our playbook and use the newly created postfix role:

 1...
 2roles:
 3  - role: postfix
 4
 5handlers:
 6  - name: post on slack
 7    slack:
 8      token: ...
 9      msg: ...
10    delegate_to: localhost

We are back to the problem that the slack handler won’t get notified anymore. How can we solve that? How can we subscribe to a change of a task? Handler listen!

Handler Listen

We change the handler to listen on a key word or call it event, a handlers name. No modifications in the role are required, we can keep it generic. The only thing to do is to add listen: <handler name> to the slack task.

 1...
 2roles:
 3  - role: postfix
 4
 5handlers:
 6  - name: post on slack
 7    slack:
 8      token: ...
 9      msg: ...
10    delegate_to: localhost
11    listen: restart postfix

The configure task in the postfix role is not coupled to the slack handler anymore. The role has no relation to it at all. Handler listen even allows us to add more handlers listening to the event without touching the task clause.

 1...
 2roles:
 3  - role: postfix
 4
 5handlers:
 6  - name: post on slack
 7    slack:
 8      token: ...
 9      msg: ...
10    delegate_to: localhost
11    listen: restart postfix
12
13  - name: reaction on postfix config change
14    command: ...
15    listen: restart postfix

This decoupling is especially helpful in roles: make your roles notify an event if you think this might be helpful to trigger tasks outside the role. But note, make sure to have at least one handler listening to it, or one with the identical name, or Ansible will complain about notifying a non-existent handler.

Hint: it can be a handler with a debug task.