diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index b7220516..6c6d90cc 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -50,7 +50,7 @@ jobs: matrix: ${{ fromJson(needs.prepare.outputs.matrix) }} env: FORCE_COLOR: 1 - PYTEST_REQPASS: 108 + PYTEST_REQPASS: 109 steps: - uses: actions/checkout@v4 with: diff --git a/src/ansible_compat/runtime.py b/src/ansible_compat/runtime.py index ddc7029b..030497ce 100644 --- a/src/ansible_compat/runtime.py +++ b/src/ansible_compat/runtime.py @@ -155,6 +155,7 @@ class Runtime: initialized: bool = False plugins: Plugins _has_playbook_cache: dict[tuple[str, Path | None], bool] = {} + require_module: bool = False def __init__( self, @@ -218,6 +219,7 @@ def __init__( msg = f"Found incompatible version of ansible runtime {self.version}, instead of {min_required_version} or newer." raise RuntimeError(msg) if require_module: + self.require_module = True self._ensure_module_available() # pylint: disable=import-outside-toplevel @@ -339,17 +341,20 @@ def _ensure_module_available(self) -> None: # https://github.com/ansible/ansible-lint/issues/2945 if not Runtime.initialized: col_path = [f"{self.cache_dir}/collections"] + # noinspection PyProtectedMember + from ansible.utils.collection_loader._collection_finder import ( # pylint: disable=import-outside-toplevel + _AnsibleCollectionFinder, + ) + if self.version >= Version("2.15.0.dev0"): # pylint: disable=import-outside-toplevel,no-name-in-module from ansible.plugins.loader import init_plugin_loader + _AnsibleCollectionFinder( # noqa: SLF001 + paths=col_path, + )._remove() # pylint: disable=protected-access init_plugin_loader(col_path) else: - # noinspection PyProtectedMember - from ansible.utils.collection_loader._collection_finder import ( # pylint: disable=import-outside-toplevel - _AnsibleCollectionFinder, - ) - # noinspection PyProtectedMember # pylint: disable=protected-access col_path += self.config.collections_paths @@ -622,6 +627,9 @@ def install_requirements( # noqa: C901 if result.returncode != 0: _logger.error(result.stderr) raise AnsibleCommandError(result) + if self.require_module: + Runtime.initialized = False + self._ensure_module_available() # pylint: disable=too-many-locals def prepare_environment( # noqa: C901 diff --git a/test/test_runtime.py b/test/test_runtime.py index 605dc028..edb5c6bb 100644 --- a/test/test_runtime.py +++ b/test/test_runtime.py @@ -13,6 +13,7 @@ from typing import TYPE_CHECKING, Any import pytest +from ansible.plugins.loader import module_loader from packaging.version import Version from ansible_compat.constants import INVALID_PREREQUISITES_RC @@ -742,6 +743,43 @@ def test_install_collection_from_disk( runtime.clean() +@pytest.mark.parametrize( + ("path", "expected_plugins"), + ( + pytest.param( + "test/collections/acme.goodies", + [ + "ansible.posix.patch", # from tests/requirements.yml + "community.crypto.acme_account", # from galaxy.yml as a git dependency + ], + id="modules", + ), + ), +) +def test_load_plugins( + path: str, + expected_plugins: list[str], +) -> None: + """Tests ability to load plugin from a collection installed by requirement.""" + with cwd(Path(path)): + from ansible_compat.prerun import get_cache_dir + + rmtree(get_cache_dir(Path.cwd()), ignore_errors=True) + runtime = Runtime(isolated=True, require_module=True) + runtime.prepare_environment(install_local=True) + for plugin_name in expected_plugins: + loaded_module = module_loader.find_plugin_with_context( + plugin_name, + ignore_deprecated=True, + check_aliases=True, + ) + assert ( + loaded_module.resolved_fqcn is not None + ), f"Unable to load module {plugin_name}" + + runtime.clean() + + def test_install_collection_from_disk_fail() -> None: """Tests that we fail to install a broken collection.""" with cwd(Path("test/collections/acme.broken")):