diff --git a/changelogs/fragments/72170-action-loader-collection-list.yml b/changelogs/fragments/72170-action-loader-collection-list.yml new file mode 100644 index 00000000000..db1edff4d59 --- /dev/null +++ b/changelogs/fragments/72170-action-loader-collection-list.yml @@ -0,0 +1,3 @@ +bugfixes: +- Collections - Ensure ``action_loader.get`` is called with ``collection_list`` to properly find collections + when ``collections:`` search is specified (https://github.com/ansible/ansible/issues/72170) diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py index b9349ddaaa0..a0f916e602f 100644 --- a/lib/ansible/executor/task_executor.py +++ b/lib/ansible/executor/task_executor.py @@ -738,7 +738,7 @@ class TaskExecutor: # we need the 'normal' action handler for the status check, so get it # now via the action_loader async_handler = self._shared_loader_obj.action_loader.get( - 'async_status', + 'ansible.legacy.async_status', task=async_task, connection=self._connection, play_context=self._play_context, diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py index 47ed8591602..9f2bcae5ea6 100644 --- a/lib/ansible/plugins/strategy/__init__.py +++ b/lib/ansible/plugins/strategy/__init__.py @@ -1016,7 +1016,7 @@ class StrategyBase: bypass_host_loop = False try: - action = plugin_loader.action_loader.get(handler.action, class_only=True) + action = plugin_loader.action_loader.get(handler.action, class_only=True, collection_list=handler.collections) if getattr(action, 'BYPASS_HOST_LOOP', False): bypass_host_loop = True except KeyError: diff --git a/lib/ansible/plugins/strategy/free.py b/lib/ansible/plugins/strategy/free.py index 455aaa049ef..00cf83b5491 100644 --- a/lib/ansible/plugins/strategy/free.py +++ b/lib/ansible/plugins/strategy/free.py @@ -156,7 +156,7 @@ class StrategyModule(StrategyBase): (state, task) = iterator.get_next_task_for_host(host) try: - action = action_loader.get(task.action, class_only=True) + action = action_loader.get(task.action, class_only=True, collection_list=task.collections) except KeyError: # we don't care here, because the action may simply not have a # corresponding action plugin diff --git a/lib/ansible/plugins/strategy/linear.py b/lib/ansible/plugins/strategy/linear.py index 00458695173..415d3cd4388 100644 --- a/lib/ansible/plugins/strategy/linear.py +++ b/lib/ansible/plugins/strategy/linear.py @@ -265,7 +265,7 @@ class StrategyModule(StrategyBase): task.action = templar.template(task.action) try: - action = action_loader.get(task.action, class_only=True) + action = action_loader.get(task.action, class_only=True, collection_list=task.collections) except KeyError: # we don't care here, because the action may simply not have a # corresponding action plugin diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/bypass_host_loop.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/bypass_host_loop.py new file mode 100644 index 00000000000..b15493d9c78 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/bypass_host_loop.py @@ -0,0 +1,17 @@ +# Copyright: (c) 2020, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.plugins.action import ActionBase + + +class ActionModule(ActionBase): + + BYPASS_HOST_LOOP = True + + def run(self, tmp=None, task_vars=None): + result = super(ActionModule, self).run(tmp, task_vars) + result['bypass_inventory_hostname'] = task_vars['inventory_hostname'] + return result diff --git a/test/integration/targets/collections/runme.sh b/test/integration/targets/collections/runme.sh index 15ffb415434..dc01a6c0765 100755 --- a/test/integration/targets/collections/runme.sh +++ b/test/integration/targets/collections/runme.sh @@ -68,6 +68,9 @@ if [[ ${INVENTORY_PATH} != *.winrm ]]; then ansible-playbook -i "${INVENTORY_PATH}" -v invocation_tests.yml "$@" fi +echo "--- validating bypass_host_loop with collection search" +ansible-playbook -i host1,host2, -v test_bypass_host_loop.yml "$@" + echo "--- validating inventory" # test collection inventories ansible-playbook inventory_test.yml -i a.statichost.yml -i redirected.statichost.yml "$@" diff --git a/test/integration/targets/collections/test_bypass_host_loop.yml b/test/integration/targets/collections/test_bypass_host_loop.yml new file mode 100644 index 00000000000..e95262b87be --- /dev/null +++ b/test/integration/targets/collections/test_bypass_host_loop.yml @@ -0,0 +1,22 @@ +- name: Test collection lookup bypass host list + hosts: all + connection: local + gather_facts: false + collections: + - testns.testcoll + tasks: + - meta: end_host + when: lookup('pipe', ansible_playbook_python ~ ' -c "import jinja2; print(jinja2.__version__)"') is version('2.7', '<') + + - bypass_host_loop: + register: bypass + + - run_once: true + vars: + bypass_hosts: '{{ hostvars|dictsort|map(attribute="1.bypass.bypass_inventory_hostname")|select("defined")|unique }}' + block: + - debug: + var: bypass_hosts + + - assert: + that: bypass_hosts|length == 1