Are Ansible handlers defined in roles ran after the entire playbook or the role?
I'm running Ansible 2.0, and I could just run this, but I could also be tricked in to believing something that isn't true by my empirical tests and I can find no documentation to tell me when handlers are supposed to be ran.
If handlers aren't ran at the end of their tasks, this is my conundrum. I've got a playbook with 5 roles in it, I want to add a 6 role to the end that needs to have the handlers of the 4th role completed before it can start.
Is there any way to run Ansible to rely on a handler being completed (i.e. a role being completely completed) before doing something else or am I using handlers wrong?
Solution 1:
Handlers are executed:
- at the end of a play (not playbook)
- on executing the
meta: flush_handlers
task
So "to add a 6 role to the end that needs to have the handlers of the 4th role" you need:
- either to split the role assignment into separate plays;
-
or add a meta task and include the 6th role with
include_role
module:roles: - role4 tasks: - meta: flush_handlers - include_role: name: role6
For your use case, I'd suggest the first method as the include_role
module is still very fresh and there are quirks when using it (see this question on SO).
Moreover, please notice that handlers' names and listen calls are global, so two handlers in separate roles will be in conflict if they had the same name and both roles were assigned in a single play. (ref. Handlers: Running Operations On Change)
Handlers [ ] are referenced by a globally unique name, and are notified by notifiers. [ ] 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.
-
Empirical proof (run this shell script to confirm handlers are executed at the end of the play - there were contradicting comments and answers here):
#!/bin/bash mkdir -p ./sf831880/roles/role1 mkdir -p ./sf831880/roles/role1/handlers mkdir -p ./sf831880/roles/role1/tasks mkdir -p ./sf831880/roles/role2 mkdir -p ./sf831880/roles/role2/handlers mkdir -p ./sf831880/roles/role2/tasks cat >./sf831880/roles/role1/tasks/main.yml <<TASKS1_END --- - name: Always true in role1 command: echo role1 notify: handler1 TASKS1_END cat >./sf831880/roles/role2/tasks/main.yml <<TASKS2_END --- - name: Always true in role2 command: echo role2 notify: handler2 TASKS2_END cat >./sf831880/roles/role1/handlers/main.yml <<HANDLERS1_END --- - name: handler1 debug: msg: "This is a handler in role1" HANDLERS1_END cat >./sf831880/roles/role2/handlers/main.yml <<HANDLERS2_END --- - name: handler2 debug: msg: "This is a handler in role2" HANDLERS2_END cat >./sf831880/playbook.yml <<PLAYBOOK_END --- - hosts: localhost gather_facts: no connection: local roles: - role1 - role2 tasks: - debug: msg: "This is a task in a play" PLAYBOOK_END ansible-playbook ./sf831880/playbook.yml
Result:
PLAY [localhost] *************************************************************** TASK [role1 : Always true in role1] ******************************************** changed: [localhost] TASK [role2 : Always true in role2] ******************************************** changed: [localhost] TASK [debug] ******************************************************************* ok: [localhost] => { "msg": "This is a task in a play" } RUNNING HANDLER [role1 : handler1] ********************************************* ok: [localhost] => { "msg": "This is a handler in role1" } RUNNING HANDLER [role2 : handler2] ********************************************* ok: [localhost] => { "msg": "This is a handler in role2"
-
Play modified to contain
meta: flush_handlers
:--- - hosts: localhost gather_facts: no connection: local roles: - role1 - role2 tasks: - meta: flush_handlers - debug: msg: "This is a task in a play"
The result:
PLAY [localhost] *************************************************************** TASK [role1 : Always true in role1] ******************************************** changed: [localhost] TASK [role2 : Always true in role2] ******************************************** changed: [localhost] RUNNING HANDLER [role1 : handler1] ********************************************* ok: [localhost] => { "msg": "This is a handler in role1" } RUNNING HANDLER [role2 : handler2] ********************************************* ok: [localhost] => { "msg": "This is a handler in role2" } TASK [debug] ******************************************************************* ok: [localhost] => { "msg": "This is a task in a play"
Solution 2:
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. ansible doc
1) Handlers that do the same thing should be named the same.restart nginx
ALWAYS restarts nginx, not handler1
and handler2
2) Handlers are run at the END of the entire "Play" a play scoped to your sections.
3) I would use the register
and when
functions for tasks that should be restarted, note this var should carry with you.
Code Source
PLAY [localhost] ***************************************************************
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": "Play 1"
}
TASK [role1 : Always true in role1] ********************************************
changed: [localhost]
TASK [role1 : Always true in role1] ********************************************
changed: [localhost]
TASK [role1 : Always true in role1] ********************************************
changed: [localhost]
TASK [role1 : Always true in role1] ********************************************
changed: [localhost]
TASK [role1 : Always true in role1] ********************************************
changed: [localhost]
TASK [role2 : Run if change in task c of role 1] *******************************
changed: [localhost]
TASK [role2 : Always true in role2] ********************************************
changed: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": "This is a task in a play"
}
RUNNING HANDLER [role1 : handler] **********************************************
ok: [localhost] => {
"msg": "This is a handler in role1"
}
PLAY [localhost] ***************************************************************
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": "Play 2"
}
TASK [role1 : Always true in role1] ********************************************
changed: [localhost]
TASK [role1 : Always true in role1] ********************************************
changed: [localhost]
TASK [role1 : Always true in role1] ********************************************
changed: [localhost]
TASK [role1 : Always true in role1] ********************************************
changed: [localhost]
TASK [role1 : Always true in role1] ********************************************
changed: [localhost]
TASK [role2 : Run if change in task c of role 1] *******************************
changed: [localhost]
TASK [role2 : Always true in role2] ********************************************
changed: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": "This is a task in a play"
}
RUNNING HANDLER [role1 : handler] **********************************************
ok: [localhost] => {
"msg": "This is a handler in role1"
}
PLAY RECAP *********************************************************************
localhost : ok=20 changed=14 unreachable=0 failed=0
Lots of ways to do the same task. Handlers were designed to prevent restarting the same process multiple times, such as multiple changes to a nginx server that has websites, ssl certs, and other tasks that need service restarts.