93

In response to a change, I have multiple related tasks that should run. How do I write an Ansible handler with multiple tasks?

For example, I would like a handler that restarts a service only if already started:

- name: Restart conditionally
  shell: check_is_started.sh
  register: result

- name: Restart conditionally step 2
  service: name=service state=restarted
  when: result
Tim Diels
  • 3,246
  • 2
  • 19
  • 22

3 Answers3

116

There is proper solution to this problem as of Ansible 2.2.

handlers can also “listen” to generic topics, and tasks can notify those topics as follows:

handlers:
    - name: restart memcached
      service: name=memcached state=restarted
      listen: "restart web services"
    - name: restart apache
      service: name=apache state=restarted
      listen: "restart web services"

tasks:
    - name: restart everything
      command: echo "this task will restart the web services"
      notify: "restart web services"

This use makes it much easier to trigger multiple handlers. It also decouples handlers from their names, making it easier to share handlers among playbooks and roles

Specifically to the question, this should work:

    - name: Check if restarted
      shell: check_is_started.sh
      register: result
      listen: Restart processes
    
    - name: Restart conditionally step 2
      service: name=service state=restarted
      when: result
      listen: Restart processes

and in the task, notify handlers via 'Restart processes'

https://docs.ansible.com/ansible/latest/user_guide/playbooks_handlers.html#naming-handlers

4wk_
  • 2,458
  • 3
  • 34
  • 46
mkadan
  • 1,296
  • 1
  • 9
  • 6
  • 2
    Would this solution allow to use block: and rescue: to handle errors by launching commands ? – user1767316 Aug 01 '18 at 16:00
  • 3
    Are the multiple tasks executed in the listed order? If one task depends on another task, should I use notify-listen inside the handler? – user26767 Jan 20 '20 at 08:26
  • 1
    @user26767 from the linked documentation: `Notify handlers are always run in the same order they are defined, not in the order listed in the notify-statement. This is also the case for handlers using listen.` – swenzel Sep 03 '20 at 11:00
  • 1
    Unfortunately `listen` is bugged when used together with `include_role`, and handlers are executed multiple times instead of once. The issue is still present as of Ansible core version 2.11.4. See: https://github.com/ansible/ansible/issues/49371 – Person Sep 01 '21 at 17:08
59

In your handler file, chain the different steps together using notify.

- name: Restart conditionally
  debug: msg=Step1
  changed_when: True
  notify: Restart conditionally step 2

- name: Restart conditionally step 2
  debug: msg=Step2
  changed_when: True
  notify: Restart conditionally step 3

- name: Restart conditionally step 3
  debug: msg=Step3

Then refer to it from a task with notify: Restart conditionally.

Note that you can only notify to handlers below the current one. So for example, Restart conditionally step 2 can't notify Restart conditionally.

Source: #ansible at irc.freenode.net. I'm unsure whether this will continue to work in the future as it's not mentioned as a feature in the official documentation.

Tim Diels
  • 3,246
  • 2
  • 19
  • 22
  • 3
    This method helps with a fact for which there seems to be little literature: Handlers, once notified, will execute even if one of those handlers fails. Using different `notify` labels for those you may not wish to run if a previous handler fails is a good way to "fix" this if you don't want it to be so. – fbicknel Jul 20 '20 at 17:59
29

Edit: If you have Ansible 2.2 or above, use mkadan's answer. The answer below does not work with newer versions of Ansible. Also note that as per Enis Afgan's comment below, due to a bug, this answer does not work with Ansible versions between 2.0.2 and 2.1.2.


As of Ansible 2.0, you can use an include action in your handler to run multiple tasks.

For example, put your tasks in a separate file restart_tasks.yml (if you use roles, that would go into the tasks subdirectory, not in the handlers subdirectory):

- name: Restart conditionally step 1
  shell: check_is_started.sh
  register: result

- name: Restart conditionally step 2
  service: name=service state=restarted
  when: result

Your handler would then simply be:

- name: Restart conditionally
  include: restart_tasks.yml

Source: issue thread on github: https://github.com/ansible/ansible/issues/14270

Alexander Klauer
  • 957
  • 11
  • 18
  • 5
    Just a note that Ansible versions between 2.0.2 and 2.1.2 have a bug where this does not work: https://github.com/ansible/ansible/issues/15915 – Enis Afgan Aug 25 '16 at 14:11
  • 2
    For future readers - this did not work for me on Ansible 2.9.2. The fix was replacing `include` with `include_tasks`. – Martin Melka Jan 24 '20 at 10:42
  • @MartinMelka thanks, I have added a warning to the top of my answer that it is not meant for Ansible versions >= 2.2. – Alexander Klauer Jan 24 '20 at 13:39
  • Great, thanks :) When designing new handlers, the other answer is better. But when the Ansible inventory code is old and one needs to only make small changes (like I did), yours can still be useful with this little addition. – Martin Melka Jan 24 '20 at 13:44
  • 1
    If you have a complex order in which things must come down/come back up, "include_tasks" is the only sane way to do this. Chaining handlers is a valid solution but more often than not, include_tasks is functionally equivalent and is easier to follow. This is not without side effects so is not a one size fits all, but personally has resolved confusing and unnecessary logic issues using handlers alone. – evillive Apr 19 '21 at 14:46