I came across something interesting while refactoring an existing Ansible playbook.
Here is a reduced version of it:
|_ showcase.yml
\_ sc_role
\_ tasks
|_ main.yml
\_ block-with-rescue.yml
File contents:
showcase.yml
--- - hosts: [a, b] gather_facts: false tasks: - include_role: name: sc_role
sc_role/tasks/main.yml
--- - include_tasks: file: block-with-rescue.yml - debug: msg: "run_once task" run_once: true delegate_to: localhost
sc_role/tasks/block-with-rescue.yml
--- - block: - debug: msg: "Provoke exception {{ u_n_d_e_f_i_n_e_d }}" when: inventory_hostname == "a" rescue: - debug: msg: "rescue task"
Usecase: In the original playbook the include block-with-rescue.yml
contains tasks that do maintance on a couple of hosts (simulated by "a" and "b" here).
The maintance produces logfiles that are moved to a ZIP and send out to a reporting server. This is simulated by the debug
task with the "run_once task" message here.
The reduced playbook can be executed in a vanilla Ansible setup without further configuration (I used Ansible 2.10.8 + Python 3.10.12) like this:
ansible-playbook -c local -i a,b, showcase.yml
When the playbook executes and host "a" fails at the provoked exception, execution continues for "b" at the "run_once" task. Then the rescue handler is run for "a" and the "run_once" task is executed a second time. This is of course not intended, because it messes up the already send out reporting data.
I am aware of the differences between "include" and "import". But the effect is the same for both.
This does not happen, when I include the role within a "roles" section of "showcase.yml" or when the "run_once" task is moved to "showcase.yml" behind the "[import/include]_role" statement.
So I know how to work around this, but I want to understand WHY it is happening. I don't feel confident using roles with rescue-handlers, if their behavior depends on the way, they have been included.
Here is an example job-output:
PLAY [a,b] ******************************************************************************************************************************************************************************************************************************************************************
TASK [include_role : sc_role] ***********************************************************************************************************************************************************************************************************************************************
TASK [sc_role : include_tasks] **********************************************************************************************************************************************************************************************************************************************
included: (..)/sc_role/tasks/block-with-rescue.yml for a, b
TASK [sc_role : debug] ******************************************************************************************************************************************************************************************************************************************************
fatal: [a]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'u_n_d_e_f_i_n_e_d' is undefined\n\nThe error appears to be in '(..)/sc_role/tasks/block-with-rescue.yml': line 3, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n- block:\n - debug:\n ^ here\n"}
skipping: [b]
TASK [sc_role : debug] ******************************************************************************************************************************************************************************************************************************************************
ok: [b] => {
"msg": "run_once task" <===== First execution for "b"
}
TASK [sc_role : debug] ******************************************************************************************************************************************************************************************************************************************************
ok: [a] => {
"msg": "rescue task"
}
TASK [sc_role : debug] ******************************************************************************************************************************************************************************************************************************************************
ok: [a] => {
"msg": "run_once task" <===== Second execution for "a"
}
PLAY RECAP ******************************************************************************************************************************************************************************************************************************************************************
a : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0
b : ok=2 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0