Another ways to have multiple actions in the single handler in Ansible

George Shuklin
OpsOps
Published in
2 min readDec 17, 2019

This is an endless story: your handler need two (or more) actions, and you are allowed to have only one. Because block is not a valid action for a handler.

I’ve wrote an article about the most powerful way to solve this some time ago. (Tl;dr; use include_tasks as action in the handler and put anything you want in the included tasklist).

But it’s too powerful and it involves a hex you don’t want to cast (include, include, I afraid of you…).

So, here are three (less powerful but more elegant) ways to have multiple actions inside of a single handler.

Unload/load pattern

This is a very special case when you want to do something and instantly do something opposite. modprobe state=absent and modprobe state=present is a good example for a handler for changes in modprobe config.

- name: reload kvm-intel module
modprobe: name=kvm-intel state={{ item }}
loop:
- absent
- present

As you can see, we just use ‘loop’ and changing state. It works as magic, moreover, it does not involve bleeding edge features of Ansible.

Abusing ‘action’

If you can change item, you can abuse the action argument for the task in Ansible:

- hosts: localhost
tasks:
- debug:
changed_when: true
notify: do
handlers:
- name: do
action:
module: '{{ item.module }}'
args: '{{ item.args }}'
loop:
- module: debug
args:
msg: 'hello here'
- module: setup
args: gather_subset=network
- module: file
args:
path: /tmp/foobar
state: touch

Technically you can do this, but I hardly can image a situation when such travesty would be justified. Moreover, you get warning from Ansible “Using a variable for a task’s ‘args’ is unsafe in some situations” and you can’t do anything about it.

Chaining notifications

You can add ‘notify’ to any handler. If that handler causes a changed state, it notifies the next handler in the chain.

This trick is absolutely legal, but it has a special property: a chain stops as soon as any action in that chain is ‘no changes’. This can be a desirable behavior, or it can cause you some headache at debug time. Use with caution:

- hosts: localhost
tasks:
- debug:
changed_when: true
notify: stage1
handlers:
- name: stage1
file: path=/tmp/foo state=touch
notify: stage2
- name: stage2
file: path=/tmp/foo state=file
notify: stage3
- name: stage3
fail:
msg: 'impossible!'

As you may guess, this example normally should not trigger ‘stage3’ (if there is no race condition with file removal).

Conclusion

Out of those examples, only the first is completely reasonable (it provides under-delivered functions of module), two others I consider as acts of despair. If you can, try to avoid them.

--

--

George Shuklin
OpsOps

I work at Servers.com, most of my stories are about Ansible, Ceph, Python, Openstack and Linux. My hobby is Rust.