I am not sure is there anything like this for ansible: I need a global shared handlers, which I can call anywhere I want, ie, from playbook, from any roles inside. I can’t find any official document or tutorial anywhere regarding my use case, so I have to cook up myself, and share here. Hopefully it can help someone who need it.
The dilemma of normal ansible handler
Normal handler is too simple to handle complicate situation
For example, for apache handler, it could be as simple as this
handlers:
- name: Restart apache
ansible.builtin.service:
name: httpd
state: restarted
In reality, for some kind of service, it fast more complicated than this:
## Fake codes for demo purpose
handlers:
- name: Restart complicated service
block:
- name: stop complicated service
command: "complicate_service stop"
- name: Check until process is stop
command: "ps -elf | grep java -c"
retry: 100
register: cmd_out
until: cmd_out.stdout == "0"
- name: Start complicated service
command: "complicate_service start"
- name: wait for until it fully available
wait_for:
port: "
#.... something more like this
So, in this case, the better way is to extract the code into a separate task and include that:
- name: restart
include_tasks: restart_demo.yml
At first place, I believe the separate task restart_demo.yml should reside the same folder as handlers, something like this:
demo_service/
├── handlers
│ ├── main.yml
│ ├── restart_demo.yml
│ ├── start_demo.yml
│ └── stop_demo.yml
└── tasks
└── main.yml
But, as matter of fact, all those tasks can be placed side by side with tasks folder, which gave way to a more reusable approach to be discussed later.
demo_service
├── handlers
│ └── main.yml
└── tasks
├── main.yml
├── restart_demo.yml
├── start_demo.yml
└── stop_demo.yml
Sharing handler is a pain, shared handler role come to rescue
What if we need to call same handler in another role ? Simple way is to copy the handlers folder and tasks files alongside into the role’s folder. No doubt, it’s the dumbest thing ever to do. It will cause duplicate code here and there, very hard to manage.
The best way I am aware of is to create a role to share common handler among other roles. There are multiple ways to reference shared handlers role in other roles or tasks:
-
reference by include_role
- include_role: name: demo_service tags: always -
reference by role
roles: - { role: demo_service, tags: always } -
reference by dependencies
meta/main.ymldependencies: - role: demo_service tags: always
:Warning: **Always TAGS Your shared handler as
alwaysto make sure it always run **
But, it leads to one problem: the main task main.yml will always run as well when the role is included or referred.
└── tasks
├── main.yml
One possible way is to add one placeholder block to only execute when specific vars is provided.
For example, nothing will be executed unless we provide service_name variable, but the handlers inside is always included.
- name: Service Placeholder
debug:
msg: ""
changed_when: true
when: service_name is defined
notify: ""
Need to reuse handler code as task not as handler
In some rare case, we need to call the handler as task to reuse code as much as possible.
For example, we need to stop service immediately before some task or some depended service must be restarted immediately to make the change to take effect, so that the depending step can proceed.
immediately stop demo service
include_role:
name: demo_service
tasks_from: stop_demo.yml
immediately restart demo service
include_role:
name: demo_service
tasks_from: restart_demo.yml
As mentioned before, we place those task files along side the main.yml, so it can be easily resued by include_role and tasks_from as above.
demo_service
├── handlers
│ └── main.yml
└── tasks
├── main.yml
├── restart_demo.yml
├── start_demo.yml
└── stop_demo.yml
A Full Demo Code for Scenarios Discussed Above
---
- name: Shared handler Scenario 1
hosts: testhost
become: true
gather_facts: no
tasks:
# Must include demo_service role, so that shared handler can be found
- include_role:
name: demo_service
tags: always
- name: "Something change, need to notify to restart demo"
shell: "true"
notify: restart demo
- name: Shared handler Scenario 2
hosts: testhost
become: true
gather_facts: no
tasks:
- name: Restart demo service immediately without waiting for handler(case 1)
include_role:
name: demo_service
tasks_from: restart_demo.yml
- name: Restart demo service directly with handler (case 2)
include_role:
name: demo_service
vars:
service_name: restart demo
- name: Shared handler Scenario 3
hosts: testhost
become: true
gather_facts: no
roles:
# Must include demo_service role, so that shared handler can be found
- { role: demo_service, tags: always }
- { role: call_shared_handler_role, tags: test }
Share Demo Service as Handler and Task Playbook Run
ansible-playbook test_handlers.yml -i inventory/inventory
PLAY [Shared handler Scenario 1] *******************************************************************
TASK [include_role : demo_service] *****************************************************************
TASK [demo_service : Service Placeholder] **********************************************************
skipping: [testhost]
TASK [Something change, need to notify to restart demo] ********************************************
changed: [testhost]
RUNNING HANDLER [demo_service : restart demo] ******************************************************
included: /home/tom/ansible-shared-handlers/roles/demo_service/tasks/restart_demo.yml for testhost
RUNNING HANDLER [demo_service : include_tasks] *****************************************************
included: /home/tom/ansible-shared-handlers/roles/demo_service/tasks/stop_demo.yml for testhost
RUNNING HANDLER [demo_service : Stop very complicate service] **************************************
ok: [testhost] => {
"msg": "Stop complicate demo service successfully "
}
RUNNING HANDLER [demo_service : include_tasks] *****************************************************
included: /home/tom/ansible-shared-handlers/roles/demo_service/tasks/start_demo.yml for testhost
RUNNING HANDLER [demo_service : Start very complicate service] *************************************
ok: [testhost] => {
"msg": "start complicate demo service successfully"
}
PLAY [Shared handler Scenario 2] *******************************************************************
TASK [Restart demo service immediately without waiting for handler(case 1)] ************************
TASK [demo_service : include_tasks] ****************************************************************
included: /home/tom/ansible-shared-handlers/roles/demo_service/tasks/stop_demo.yml for testhost
TASK [demo_service : Stop very complicate service] *************************************************
ok: [testhost] => {
"msg": "Stop complicate demo service successfully "
}
TASK [demo_service : include_tasks] ****************************************************************
included: /home/tom/ansible-shared-handlers/roles/demo_service/tasks/start_demo.yml for testhost
TASK [demo_service : Start very complicate service] ************************************************
ok: [testhost] => {
"msg": "start complicate demo service successfully"
}
TASK [Restart demo service directly with handler (case 2)] *****************************************
TASK [demo_service : Service Placeholder] **********************************************************
changed: [testhost] => {
"msg": "restart demo"
}
RUNNING HANDLER [demo_service : restart demo] ******************************************************
included: /home/tom/ansible-shared-handlers/roles/demo_service/tasks/restart_demo.yml for testhost
RUNNING HANDLER [demo_service : include_tasks] *****************************************************
included: /home/tom/ansible-shared-handlers/roles/demo_service/tasks/stop_demo.yml for testhost
RUNNING HANDLER [demo_service : Stop very complicate service] **************************************
ok: [testhost] => {
"msg": "Stop complicate demo service successfully "
}
RUNNING HANDLER [demo_service : include_tasks] *****************************************************
included: /home/tom/ansible-shared-handlers/roles/demo_service/tasks/start_demo.yml for testhost
RUNNING HANDLER [demo_service : Start very complicate service] *************************************
ok: [testhost] => {
"msg": "start complicate demo service successfully"
}
PLAY [Shared handler Scenario 3] *******************************************************************
TASK [demo_service : Service Placeholder] **********************************************************
skipping: [testhost]
TASK [call_shared_handler_role : Something change inside role, need to notify to restart demo service] ***
changed: [testhost]
RUNNING HANDLER [demo_service : restart demo] ******************************************************
included: /home/tom/ansible-shared-handlers/roles/demo_service/tasks/restart_demo.yml for testhost
RUNNING HANDLER [demo_service : include_tasks] *****************************************************
included: /home/tom/ansible-shared-handlers/roles/demo_service/tasks/stop_demo.yml for testhost
RUNNING HANDLER [demo_service : Stop very complicate service] **************************************
ok: [testhost] => {
"msg": "Stop complicate demo service successfully "
}
RUNNING HANDLER [demo_service : include_tasks] *****************************************************
included: /home/tom/ansible-shared-handlers/roles/demo_service/tasks/start_demo.yml for testhost
RUNNING HANDLER [demo_service : Start very complicate service] *************************************
ok: [testhost] => {
"msg": "start complicate demo service successfully"
}
PLAY RECAP *****************************************************************************************
testhost : ok=22 changed=3 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
Full code avaible here