Run an Ansible handler only once for the entire playbook

ansible-playbook multiple hosts
ansible handlers
a handler will run only once after all the tasks are completed in a particular play true or false
ansible handlers in roles
ansible run handler immediately
ansible modules apply the changes every time one runs playbook.
ansible roles
ansible_connection=local

I would like to run a handler only once in an entire playbook.

I attempted using an include statement in the following in the playbook file, but this resulted in the handler being run multiple times, once for each play:

- name: Configure common config
  hosts: all
  become: true
  vars:
        OE: "{{ ansible_hostname[5] }}"
  roles:
    - { role: common }
  handlers:
    - include: handlers/main.yml

- name: Configure metadata config
  hosts: metadata
  become: true
  vars:
        OE: "{{ ansible_hostname[5] }}"
  roles:
    - { role: metadata }
  handlers:
    - include: handlers/main.yml

Here is the content of handlers/main.yml:

- name: restart autofs
  service:
    name: autofs.service
    state: restarted

Here is an example of one of the tasks that notifies the handler:

- name: Configure automount - /opt/local/xxx in /etc/auto.direct
  lineinfile:
     dest: /etc/auto.direct
     regexp: "^/opt/local/xxx"
     line: "/opt/local/xxx   -acdirmin=0,acdirmax=0,rdirplus,rw,hard,intr,bg,retry=2  nfs_server:/vol/xxx"
  notify: restart autofs

How can I get the playbook to only execute the handler once for the entire playbook?

The answer

The literal answer to the question in the title is: no.

Playbook is a list of plays. Playbook has no namespace, no variables, no state. All the configuration, logic, and tasks are defined in plays.

Handler is a task with a different calling schedule (not sequential, but conditional, once at the end of a play, or triggered by the meta: flush_handlers task).

A handler belongs to a play, not a playbook, and there is no way to trigger it outside of the play (i.e. at the end of the playbook).


Solution

The solution to the problem is possible without referring to handlers.

You can use group_by module to create an ad-hoc group based on the result of the tasks at the bottom of each play.

Then you can define a separate play at the end of the playbook restarting the service on targets belonging to the above ad-hoc group.

Refer to the below stub for the idea:

- hosts: all
  roles:
    # roles declaration
  tasks:
    - # an example task modifying Nginx configuration
      register: nginx_configuration

    # ... other tasks ...

    - name: the last task in the play
      group_by:
        key: hosts_to_restart_{{ 'nginx' if nginx_configuration is changed else '' }}

# ... other plays ...

- hosts: hosts_to_restart_nginx
  gather_facts: no
  tasks:
    - service:
        name: nginx
        state: restarted

Are Ansible handlers defined in roles ran after the entire playbook or , [ ] a handler, it will run only once, after all of the tasks complete in a particular play​. Handler names and listen topics live in a global namespace. including handlers from different file. The handlers I have are not being run by the playbook or tasks. Run an Ansible handler only once for the entire

Possible solution

Use handlers to add hosts to in-memory inventory. Then add play to run restart service only for these hosts. See this example:

If task is changed, it notify mark to restart to set fact, that host needs service restart.

Second handler add host is quite special, because add_host task only run once for whole play even in handler, see also documentation. But if notified, it will run after marking is done implied from handlers order. Handler loops over hosts on which tasks were run and check if host service needs restart, if yes, add to special hosts_to_restart group.

Because facts are persistent across plays, notify third handler clear mark for affected hosts.

A lot of lines you hide with moving handlers to separate file and include them.

inventory file

10.1.1.[1:10]
[primary]
10.1.1.1
10.1.1.5

test.yml

---

- hosts: all
  gather_facts: no
  tasks:
      - name: Random change to notify trigger
        debug: msg="test"
        changed_when: "1|random == 1"
        notify:
            - mark to restart
            - add host
            - clear mark
  handlers:
      - name: mark to restart
        set_fact: restart_service=true
      - name: add host
        add_host:
            name: "{{item}}"
            groups: "hosts_to_restart"
        when: hostvars[item].restart_service is defined and hostvars[item].restart_service
        with_items: "{{ansible_play_batch}}"
      - name: clear mark
        set_fact: restart_service=false

- hosts: primary
  gather_facts: no
  tasks:
      - name: Change to notify trigger
        debug: msg="test"
        changed_when: true
        notify:
            - mark to restart
            - add host
            - clear mark
  handlers:
      - name: mark to restart
        set_fact: restart_service=true
      - name: add host
        add_host:
            name: "{{item}}"
            groups: "hosts_to_restart"
        when: hostvars[item].restart_service is defined and hostvars[item].restart_service
        with_items: "{{ansible_play_batch}}"
      - name: clear mark
        set_fact: restart_service=false


- hosts: hosts_to_restart
  gather_facts: no
  tasks:
      - name: Restart service
        debug: msg="Service restarted"
        changed_when: true

Intro to Playbooks, For starters, here's a playbook, verify-apache.yml that contains just one play: tasks notify a handler, it will run only once, after all of the tasks complete in a  On version 2.0, the handler only runs once, against the host utilized for the notifying play. Bug #12972 appears to reference a different use-case, but I wonder if the fix (56e5e72) could have caused the behavior change that I am seeing. STEPS TO REPRODUCE. The following playbook was run under both ansible 1.9 and 2.0.

It's not clear to me what your handler should do. Anyway, as for official documentation, handlers

are triggered at the end of each block of tasks in a play, and will only be triggered once even if notified by multiple different tasks [...] As of Ansible 2.2, handlers can also "listen" to generic topics, and tasks can notify those topics as follows:

So handlers are notified / executed once for each block of tasks. May be you get your goal just keeping handlers after "all" target hosts, but it doesn't seem a clean use of handlers. .

Delegation, Rolling Updates, and Local Actions, If you want to perform a task on one host with reference to other hosts, use the To run an entire playbook locally, just set the “hosts:” line to “hosts: 127.0.0.1”  Writing some ansible that is registering some agents to a master and need to run a handler that bounces some services on the master after. Problem is I don't need to bounce the master, let's say 5 times if I register 5 servers. It only needs to be done once. Is there a way to do this?

Running a task only once, At times, a specific task in a role may need to be executed only once during a playbook execution, even though the role is applied to many hosts. Handlers are just like normal tasks in an Ansible playbook but they run only when if the Task contains a “notify” directive. It also indicates that it changed something. It also indicates that it changed something.

Handlers: For Handling Serious Biz > Ansible for Automation , 155 lines ansible/playbook.yml A handler only runs if the task that's notifying it changed. The important one in our case is "Enable Symfony Config". Ansible handler does not run multiple handler tasks. We have one Ansible role that needs to run three tasks in the handlers/main.yml task file, but it only runs the first task.

Using block for handlers in Ansible - OpsOps, Sometimes you want to run few operations for your handler instead of just one. In my case, a ansible-playbook -i localhost, play.yaml -c local. Handlers are lists of tasks, not really any different from regular tasks, that are referenced by a globally unique name, and are notified by notifiers. If nothing notifies a handler, it will not run. Regardless of how many tasks notify a handler, it will run only once, after all of the tasks complete in a particular play.

Comments
  • Your playbook looks a bit wild. What is the general idea of this playbook and why do you include the handler here? A handler should be notified by a task, so it is a part of the role. Have a look at the docs how to use a handler: docs.ansible.com/ansible/playbooks_intro.html. Another way to run a specific task only once is the following: docs.ansible.com/ansible/playbooks_delegation.html#run-once
  • Not sure what is meant by wild. The playbook is a series of plays, of which there are two in the example. I want to use a single handler for multiple plays, and have it execute once for the entire playbook. I hope that makes sense. Don't need to run a task only once, need to run a handler only once.
  • @techraf There are multiple tasks in the plays that notify the handler. I edited the question and added one of the tasks.
  • @user3155618 I think you might want to check my new suggested way for achieving this. It is simpler than previous.
  • I think you might want to check my new suggested way, as fixing on the old one gets pretty cumbersome.
  • If the handler is included only in the play for "all" target hosts, then it will not get run when notified in the other plays.
  • You are right. But what your "handlers/main.yml" is doing?
  • Edited the original question, please see the content of "handlers/main.yml" in the post.