Configuration Management

Decoupling Ansible Handlers

Posted on September 2, 2017

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 sound a bit funny, doesn’t it? Well, it is a bit comparable with talking to kids, if no one is listen, 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 ...
 2 tasks:
 3   - copy:
 4       src: main.conf
 5       dest: /etc/postfix/main.cnf
 6     notify: restart postfix
 7 
 8 handlers:
 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 are directly coupled.

When you run a playbook, Ansible make 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 handlers name of other roles, we can also notify those as well.

There is nothing against this practice in general, except, 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 to add another handler name to the notify clause of the task:

 1 ...
 2 tasks:
 3 - name: configure postfix
 4   template:
 5     src: main.conf.j2
 6     dest: /etc/postfix/main.cnf
 7   notify:
 8     - restart postfix
 9     - post on slack
10 
11 handlers:
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 to turn 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 role change the playbook to use the postfix role.

1 # file: role/postfix/tasks/main.yml
2 - name: configure postfix
3   template:
4     src: main.conf.j2
5     dest: /etc/postfix/main.cnf
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 new created postfix role:

 1 ...
 2 roles:
 3   - role: postifx
 4 
 5 handlers:
 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 we can 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 is required, we can keep it generic. The only thing to do is to add listen: <handler name> to the slack task.

 1 ...
 2 roles:
 3   - role: postifx
 4 
 5 handlers:
 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 handler’s name anymore. The role has no relation to the slack handler. Handler listen allows us to even add more handlers listen to the event without touching the task clause.

 1 ...
 2 roles:
 3   - role: postifx
 4 
 5 handlers:
 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 to notify an event if you think this might be helpful to trigger tasks outside the role. But note, make sure to have a least one handler to listen to it or to have the identical name or Ansible will complain about notifying a inexistent handler.

Hint. It can be a handler with a debug task.