arenginsm na
arenginsm na

Reputation: 171

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

Upvotes: 9

Views: 12186

Answers (3)

Vladimir Botka
Vladimir Botka

Reputation: 68024

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

Upvotes: 11

Mohamed Allal
Mohamed Allal

Reputation: 20870

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.

include_tasks (vs import)

handlers:
  - name: Include Handler
    include_tasks: handlers/restart.yml
  - name: Import other handlers
    import_tasks: handlers/some.yml
  - name: Import another list of handlers
    import_tasks: handlers/another.yml

In the above

  • The import one will register the tasks in the files as handlers.
    • imports are processed statically. At the parsing time.
    • Import other handlers or Import another list of handlers are not handlers themselves. But only the resolved handlers within the files ( recursively for all static imports)
  • The include one is a handler itself (no resolving). There is no importing handlers in other files.
    • It's just a one handler.
    • The name is Include Handler above.
    • Dynamically when called, will load thee lists and run the tasks in it.

Why would you like to use include

  • If you want to run more than one task, when a handler get notified.
    • Remember handlers are just tasks, that run on notification and after all tasks finish (default flushing point)
    • if flush_handlers is used. Or multiple ones. At each flush the notified one will be run. And after task another default flush_handlers will be called as well
# restarts.yml
- name: Restart apache
  ansible.builtin.service:
    name: apache
    state: restarted

- name: Restart mysql
  ansible.builtin.service:
    name: mysql
    state: restarted
  • You would include that, if you want to run both tasks.
- name: Trigger an included (dynamic) handler
  hosts: localhost
  handlers:
    - name: Restart services
      include_tasks: restarts.yml
  tasks:
    - command: "true"
      notify: Restart services
  • We can see Restart services That's a handler itself. Not something that import. At it's run. It would run all tasks within the file.
    • import ones are not handlers themselves. But what is imported are. Resolving recursively. Till building the whole list.

Doc ref

Example and running tests

Let's take this structure

- main.yml # playbook
  - handlers
    - simple.yml
    - simple2.yml

Handlers list

  • simple.yml
- name: "Test_it"
  ansible.builtin.debug:
    msg: "JUST SAYING HI - from simple 1"
- name: "Another handler"
  ansible.builtin.command:
    cmd: "echo 'Hello la frindo'"
  changed_when: true

  • simple2.yml a clone of simple.yml

    • with the difference of - from simple 2
      • so we can differentiate in a later test. (u'll see)
  • The playbook main.yml

---
- name: "Local testing"
  gather_facts: false
  hosts: localhost
  connection: local
  tasks:
    - name: Notify simple handler
      ansible.builtin.debug:
        msg: "About to notify the simple handler"
      changed_when: true # will be notified on change
      # changed_when: false # will not if no change (no running)
      notify:
        - "Test_it"
        - "Another handler"

  handlers:
    - name: Simple handler
      import_tasks: "handlers/simple.yml"

Output and conclusions

Run 1: importing simple.yml only

PLAY [Setup bitwol server] ************************************************

TASK [Notify simple handler in handlers folder (without any import) - Are files in handlers folder loaded in the playbook (not role ????)] ***
changed: [localhost] => {
    "msg": "About to notify th e simple handler"
}

RUNNING HANDLER [Test_it] ************************************************
ok: [localhost] => {
    "msg": "JUST SAYING HI - from simple 1"
}

RUNNING HANDLER [Another handler] ************************************************
changed: [localhost]

PLAY RECAP **********************************************************************************************************
localhost                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

The handlers run perfectly

Run 2: Same thing but notifying task not changing

  • We switch change_when to false
changed_when: false

The handlers doesn't run. And as expected when the tasks doesn't change. Nothing get notified.

Run 3: We import simple2.yml as well

handlers:
  - name: Simple handler
    ansible.builtin.import_tasks: "handlers/simple.yml"
  - name: Simple handler 2
    ansible.builtin.import_tasks: "handlers/simple2.yml"

Output >

PLAY [Setup bitwol server] ******************************

TASK [Notify simple handler in handlers folder (without any import) - Are files in handlers folder loaded in the playbook (not role ????)] ***
changed: [localhost] => {
    "msg": "About to notify th e simple handler"
}

RUNNING HANDLER [Test_it] ******************************
ok: [localhost] => {
    "msg": "JUST SAYING HI - from simple 1"
}

RUNNING HANDLER [Another handler] ******************************
changed: [localhost]

PLAY RECAP ********************************************************************************************************************************
localhost                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

You can see that in both simple1.yml and simple2.yml there is the same handler Test_it.

  • Only the one from the first file was run simple1.yml

Run 4: Keep all the same switch the order of simple1|2 imports

handlers:
  - name: Simple handler 2
    ansible.builtin.import_tasks: "handlers/simple2.yml"
  - name: Simple handler
    ansible.builtin.import_tasks: "handlers/simple.yml"

Output >

PLAY [Setup bitwol server] *****************************************************

TASK [Notify simple handler in handlers folder (without any import) - Are files in handlers folder loaded in the playbook (not role ????)] ***
changed: [localhost] => {
    "msg": "About to notify th e simple handler"
}

RUNNING HANDLER [Test_it] ******************************************************
ok: [localhost] => {
    "msg": "JUST SAYING HI - from simple 2"
}

RUNNING HANDLER [Another handler] **********************************************
changed: [localhost]

PLAY RECAP ********************************************************************************************************************************
localhost  
  • You can see that the 2 is the one that got to run.

Conclusion if handlers with same name cross files are notified. Only the first one will execute.

  • Files import order first
  • Then Tasks in file order (in short the order after handlers list resolution)
  • You should always name the handlers uniquely

Run 5: No imports at all. Only handlers folder

  • Remove the whole hundlers: block in the playbook

Output>

PLAY [Setup bitwol server] ***********************************************

TASK [Notify simple handler in handlers folder (without any import) - Are files in handlers folder loaded in the playbook (not role ????)] ***
ERROR! The requested handler 'Test_it' was not found in either the main handlers list nor in the listening handlers list
  • No handlers were automatically loaded
ERROR! The requested handler 'Test_it' was not found in either the main handlers list nor in the listening handlers list

Not like roles. You need to import and no automatic loading.

flush_handlers

  • Doc ref

  • By default handlers run at the end after tasks finish

  • flush_handlers allow us to run the already notified handlers. At the moment of flush_handlers call. Handlers will run. Than after them, the list of tasks to run continue.

  • Handlers by default if tasks fail will not be run. If you want to force handlers to run at a specific time. For whatever reason. You can use flush_handlers

When does handlers run

  • Handlers triggered with flush_handlers or not
    • They do run when they get notified
    • flush_handlers does run already notified handlers
    • A handler will run, if get notified, and get notified if the task with notify block, does change
      • If a task does have notify block but doesn't change the handler will not run.
      • If your handlers are not running. Make sure the notifying task did change
      • with debug module, u'll need to use
changed_when: true
- name: Notify simple handler
  ansible.builtin.debug:
    msg: "About to notify the simple handler"
  changed_when: true
  # changed_when: false
  notify:
    - "Test_it"
    - "Another handler"

Handlers in roles

We can add .yml files to handlers folder within the role folder. And they would automatically be loaded. No import needed.

Issues and docs mentions

Upvotes: 4

ttavi
ttavi

Reputation: 81

You should have the structure described in https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html thus the directory should be called handlers (and not handler)

Upvotes: 4

Related Questions