Including Handlers from Different File

The handlers I have are not being run by the playbook or tasks

I have the following directory structur:

<project>
  - playbook.yml
  - <roles>
  -<handler>
     - main.yml
  -<meta>
  -<tasks>
      -main.yml

The problem is the handler is never called.

tasks/main.yml:

  - name: run task1
    command: run_task
    notify: "test me now"


handler/main.yml:

   - name: tested
     register: val1
     listen: "test me now"

The playbook just calls the task/main.yml and has host:all

Do I ned an include/import? I tried in playbook but it didn't help

3 Answers

The play below works as expected

- hosts: all
  tasks:
    - include_tasks: tasks/main.yml
    - meta: flush_handlers
    - debug:
        var: val1.stdout
  handlers:
    - import_tasks: handlers/main.yml
  • handlers must be imported to be present when a task notifies a handler
  • tasks may be included, or imported
  • flush_handlers is needed if you want to use the variable val1 in the playbook

A module is missing in handler/main.yml. This would cause:

ERROR! no action detected in task. This often indicates a misspelled module name, or incorrect module path.

Use some module in handler/main.yml. For example:

- name: tested
  command: "echo 'running handler'"
  register: val1
  listen: "test me now"

Running such play gives

  val1.stdout: running handler

Example of a complete playbook for testing

shell> cat playbook.yml 
- hosts: localhost
  tasks:
    - include_tasks: tasks/main.yml
  handlers:
    - import_tasks: handlers/main.yml
shell> cat tasks/main.yml
- command: date
  register: result
  notify: test me now
shell> cat handlers/main.yml
- name: test me now
  debug:
    msg: "{{ result.stdout }} Running handler."

gives

shell> ansible-playbook playbook.yml 

PLAY [localhost] *****************************************************************************

TASK [Gathering Facts] ***********************************************************************
ok: [localhost]

TASK [include_tasks] *************************************************************************
included: /export/scratch/tmp8/test-801/tasks/main.yml for localhost

TASK [command] *******************************************************************************
changed: [localhost]

RUNNING HANDLER [test me now] ****************************************************************
ok: [localhost] => 
  msg: Mon 25 Apr 2022 04:59:02 PM CEST Running handler.

PLAY RECAP ***********************************************************************************
localhost: ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
6

You should have the structure described in thus the directory should be called handlers (and not handler)

Handlers folder with no imports ❌

The question of wether handlers in handlersfolder are loaded automatically. Like it's the case with roles.

Answer: NO, they are not loaded automatically

  • you have to import them
  • If you do
- main.yml # playbook
- handlers
  - simple.yml
  - some.yml

And you don't import any handler. None will be loaded and available for notification.

✅ Importing handlers ✅

The rules, behaviors and QA

  • import them in handlers: of a playbook

  • import them with import_tasks ✅

    • Not include_tasks ❌
    • Doc refs
    • import_tasks
      • run at parsing time statically
      • Does resolve recursively all the tasks
        • make a one list
        • That list would be the list of handlers to add.
        • It's not a handler itself.
          • And it's name block, can't be referenced as a handler itself.
    • include_tasks
      • Is run at run time, dynamically
      • The block is the handler himself.
      • The tasks in the file will be loaded and executed dynamically once the handler (block) is notified and run.
        • All of them at once.
        • It's just bundling multiple tasks to run when the handler is notified. Instead of just a one module call.
  • What about flushing them ?

    • Not needed (at least in ansible 2.15.4 Where i tested)
      • And the logic is
        • handlers run when they are flushed
        • flush_handlers is automatically run once the tasks finish.
          • generally if some tasks fail. All handlers will not run with the default at end flushing
        • flush_handlers give us control to flush at the time that we want.
        • when a task call notify
          • A handler is added to the list of handlers to flush. Or notified handlers.
          • Flushing is running that list of handlers (the notified handlers list)

More questions

  • Q: Can we import the file with a name and notify that name to run all handlers inside

    • A: No => The import basically register the handler inside as if they were copied. (c++ and php, and conf include)
      • So basically when you import any file, or if a file that you import does in it's turn import other files. => All will be resolved to a list of handlers and you can refer any of the final resolved handlers by there names.
        • There is no notion of grouping handlers under another. But only a one list of all resolved handlers.
      • Assure you have unique names cross the files
      • That behavior started in v2.8
      • Doc ref

      Beginning in version 2.8, a task cannot notify import_tasks or a static include that is specified in handlers.

      The goal of a static import is to act as a pre-processor, where the import is replaced by the tasks defined within the imported file. When using an import, a task can notify any of the named tasks within the imported file, but not the name of the import itself.

      To achieve the results of notifying a single name but running multiple handlers, utilize include_tasks, or listen Handlers: running operations on change.

  • Q: What happens if two handlers in separate files have the same name ?

    • A: The first handler in the first loaded file would take precedence (inverse of last take precedence)
      • And clearly the best is to have unique names
  • Q: Can we import roles within handlers ?

    • A: NO we can't, it's a limitation of handlers
  • Q: Can we use include_tasks for handlers

    • A: Depends
      • The way include_tasks: works is that the name of the block, is a handler itself.
        • When you call that handler. You will execute all the tasks in include_tasks file
        • See next section, it does include doc ref too.
Alexander Ross

Alexander Ross

Gaming, Esports & Interactive Media Writer

Alexander Ross has covered the video game industry for a decade, writing deep dives on game design, esports tournaments, VR developments, and gaming culture.