From 9a7d114d969dcd0647da7de375e5f11ff9440ab5 Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Mon, 27 Jan 2025 11:46:13 +0000 Subject: [PATCH] Remove code that modifies ansible import paths As part of new test-isolation strategy, molecule will no longer take care itself about modification of: - ANSIBLE_COLLECTIONS_PATH - ANSIBLE_ROLES_PATH - ANSIBLE_LIBRARY - ANSIBLE_FILTER_PLUGINS Test isolation is supposed to be covered transparently by ansible-compat runtime when code runs inside a virtual environment. Related: https://ansible.readthedocs.io/projects/dev-tools/user-guide/test-isolation/ --- .gitignore | 1 + docs/faq.md | 2 +- docs/guides/monolith.md | 3 - pyproject.toml | 2 +- src/molecule/provisioner/ansible.py | 192 ------------------------- tests/unit/provisioner/test_ansible.py | 146 ------------------- tests/unit/verifier/test_testinfra.py | 3 - tox.ini | 1 + 8 files changed, 4 insertions(+), 346 deletions(-) diff --git a/.gitignore b/.gitignore index 183987cd30..dec2f12d43 100644 --- a/.gitignore +++ b/.gitignore @@ -178,3 +178,4 @@ tests/fixtures/resources/.extensions/ # docs output _readthedocs/ +.ansible diff --git a/docs/faq.md b/docs/faq.md index 536dfaa707..a6a7ee81c9 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -93,7 +93,7 @@ alter them when needed. Yes, roles contained in a [monorepo](https://en.wikipedia.org/wiki/Monorepo) with other roles are -automatically picked up and `ANSIBLE_ROLES_PATH` is set accordingly. See +automatically picked up. See [this page](guides/monolith.md) for more information. diff --git a/docs/guides/monolith.md b/docs/guides/monolith.md index 181788a983..e45ba38d4b 100644 --- a/docs/guides/monolith.md +++ b/docs/guides/monolith.md @@ -41,9 +41,6 @@ $ molecule --debug test DEBUG: ANSIBLE ENVIRONMENT --- ANSIBLE_CONFIG: /private/tmp/monolith-repo/roles/baz/molecule/default/.molecule/ansible.cfg -ANSIBLE_FILTER_PLUGINS: /Users/jodewey/.pyenv/versions/2.7.13/lib/python2.7/site-packages/molecule/provisioner/ansible/plugins/filters:/private/tmp/monolith-repo/roles/baz/plugins/filters:/private/tmp/monolith-repo/roles/baz/molecule/default/.molecule/plugins/filters -ANSIBLE_LIBRARY: /Users/jodewey/.pyenv/versions/2.7.13/lib/python2.7/site-packages/molecule/provisioner/ansible/plugins/libraries:/private/tmp/monolith-repo/roles/baz/library:/private/tmp/monolith-repo/roles/baz/molecule/default/.molecule/library -ANSIBLE_ROLES_PATH: /private/tmp/monolith-repo/roles:/private/tmp/monolith-repo/roles/baz/molecule/default/.molecule/roles ``` Molecule can be customized any number of ways. Updating the diff --git a/pyproject.toml b/pyproject.toml index c30696543b..0c6a6a5214 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -350,7 +350,7 @@ markers = [ "serial: Run this test serially via filelock.", "extensive: marks tests that we want to skip by default, as they are indirectly covered by other tests" ] -norecursedirs = ["scenarios"] +norecursedirs = ["scenarios", ".ansible"] testpaths = "tests" verbosity_assertions = 2 diff --git a/src/molecule/provisioner/ansible.py b/src/molecule/provisioner/ansible.py index 111c5933fb..42fcb67fda 100644 --- a/src/molecule/provisioner/ansible.py +++ b/src/molecule/provisioner/ansible.py @@ -1,4 +1,3 @@ -# pylint: disable=too-many-lines # Copyright (c) 2015-2018 Cisco Systems, Inc. # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -34,7 +33,6 @@ from ansible_compat.ports import cached_property from molecule import util -from molecule.api import drivers from molecule.provisioner import ansible_playbook, ansible_playbooks, base @@ -211,18 +209,6 @@ class Ansible(base.Base): This feature should be considered experimental. - Environment variables. Molecule does its best to handle common Ansible - paths. The defaults are as follows. - - :: - - ANSIBLE_ROLES_PATH: - $runtime_cache_dir/roles:$ephemeral_directory/roles/:$project_directory/../:~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles - ANSIBLE_LIBRARY: - $ephemeral_directory/modules/:$project_directory/library/:~/.ansible/plugins/modules:/usr/share/ansible/plugins/modules - ANSIBLE_FILTER_PLUGINS: - $ephemeral_directory/plugins/filter/:$project_directory/filter/plugins/:~/.ansible/plugins/filter:/usr/share/ansible/plugins/modules - Environment variables can be passed to the provisioner. Variables in this section which match the names above will be appended to the above defaults, and converted to absolute paths, where the relative parent is the @@ -471,81 +457,13 @@ def default_env(self) -> dict[str, str]: Returns: Default set of environment variables. """ - # Finds if the current project is part of an ansible_collections hierarchy - collection_indicator = "ansible_collections" - # isolating test environment by injects ephemeral scenario directory on - # top of the collection_path_list. This prevents dependency commands - # from installing dependencies to user list of collections. - collections_path_list = [ - util.abs_path( - os.path.join( # noqa: PTH118 - self._config.scenario.ephemeral_directory, - "collections", - ), - ), - ] - if collection_indicator in self._config.project_directory: - collection_path, right = self._config.project_directory.rsplit( - collection_indicator, - 1, - ) - collections_path_list.append(util.abs_path(collection_path)) - collections_path_list.extend( - [ - util.abs_path( - os.path.join( # noqa: PTH118 - os.path.expanduser("~"), # noqa: PTH111 - ".ansible/collections", - ), - ), - "/usr/share/ansible/collections", - "/etc/ansible/collections", - ], - ) - - if os.environ.get("ANSIBLE_COLLECTIONS_PATH", ""): - collections_path_list.extend( - list( - map( - util.abs_path, - os.environ["ANSIBLE_COLLECTIONS_PATH"].split(":"), - ), - ), - ) - - roles_path_list = [ - util.abs_path( - os.path.join(self._config.scenario.ephemeral_directory, "roles"), # noqa: PTH118 - ), - util.abs_path( - os.path.join(self._config.project_directory, os.path.pardir), # noqa: PTH118 - ), - util.abs_path( - os.path.join(os.path.expanduser("~"), ".ansible", "roles"), # noqa: PTH111, PTH118 - ), - "/usr/share/ansible/roles", - "/etc/ansible/roles", - ] - - if os.environ.get("ANSIBLE_ROLES_PATH", ""): - roles_path_list.extend( - list(map(util.abs_path, os.environ["ANSIBLE_ROLES_PATH"].split(":"))), - ) - env = util.merge_dicts( dict(os.environ), { "ANSIBLE_CONFIG": self.config_file, - "ANSIBLE_ROLES_PATH": ":".join(roles_path_list), - "ANSIBLE_COLLECTIONS_PATH": ":".join(collections_path_list), - "ANSIBLE_LIBRARY": ":".join(self._get_modules_directories()), - "ANSIBLE_FILTER_PLUGINS": ":".join( - self._get_filter_plugins_directories(), - ), }, ) env = util.merge_dicts(env, self._config.env) - return env # noqa: RET504 @property @@ -607,25 +525,6 @@ def env(self) -> dict[str, str]: env = self._config.config["provisioner"]["env"].copy() # ensure that all keys and values are strings env = {str(k): str(v) for k, v in env.items()} - - library_path = default_env["ANSIBLE_LIBRARY"] - filter_plugins_path = default_env["ANSIBLE_FILTER_PLUGINS"] - - try: - path = self._absolute_path_for(env, "ANSIBLE_LIBRARY") - library_path = f"{library_path}:{path}" - except KeyError: - pass - - try: - path = self._absolute_path_for(env, "ANSIBLE_FILTER_PLUGINS") - filter_plugins_path = f"{filter_plugins_path}:{path}" - except KeyError: - pass - - env["ANSIBLE_LIBRARY"] = library_path - env["ANSIBLE_FILTER_PLUGINS"] = filter_plugins_path - return util.merge_dicts(default_env, env) @property @@ -1024,96 +923,5 @@ def _default_to_regular( def _get_plugin_directory(self) -> str: return os.path.join(self.directory, "plugins") # noqa: PTH118 - def _get_modules_directories(self) -> list[str]: - """Return list of ansible module includes directories. - - Adds modules directory from molecule and its plugins. - - Returns: - List of module includes directories. - """ - paths: list[str | None] = [] - if os.environ.get("ANSIBLE_LIBRARY"): - paths = list(map(util.abs_path, os.environ["ANSIBLE_LIBRARY"].split(":"))) - - paths.append( - util.abs_path(os.path.join(self._get_plugin_directory(), "modules")), # noqa: PTH118 - ) - - for d in drivers().values(): - p = d.modules_dir() - if p: - paths.append(p) - paths.extend( - [ - util.abs_path( - os.path.join( # noqa: PTH118 - self._config.scenario.ephemeral_directory, - "library", - ), - ), - util.abs_path( - os.path.join(self._config.project_directory, "library"), # noqa: PTH118 - ), - util.abs_path( - os.path.join( # noqa: PTH118 - os.path.expanduser("~"), # noqa: PTH111 - ".ansible", - "plugins", - "modules", - ), - ), - "/usr/share/ansible/plugins/modules", - ], - ) - - return [path for path in paths if path is not None] - - def _get_filter_plugin_directory(self) -> str: - return util.abs_path(os.path.join(self._get_plugin_directory(), "filter")) # noqa: PTH118 - - def _get_filter_plugins_directories(self) -> list[str]: - """Return list of ansible filter plugins includes directories. - - Returns: - List of filter includes directories. - """ - paths: list[str | None] = [] - if os.environ.get("ANSIBLE_FILTER_PLUGINS"): - paths = list( - map(util.abs_path, os.environ["ANSIBLE_FILTER_PLUGINS"].split(":")), - ) - - paths.extend( - [ - self._get_filter_plugin_directory(), - util.abs_path( - os.path.join( # noqa: PTH118 - self._config.scenario.ephemeral_directory, - "plugins", - "filter", - ), - ), - util.abs_path( - os.path.join( # noqa: PTH118 - self._config.project_directory, - "plugins", - "filter", - ), - ), - util.abs_path( - os.path.join( # noqa: PTH118 - os.path.expanduser("~"), # noqa: PTH111 - ".ansible", - "plugins", - "filter", - ), - ), - "/usr/share/ansible/plugins/filter", - ], - ) - - return [path for path in paths if path is not None] - def _absolute_path_for(self, env: dict[str, str], key: str) -> str: return ":".join([self.abs_path(p) for p in env[key].split(":")]) diff --git a/tests/unit/provisioner/test_ansible.py b/tests/unit/provisioner/test_ansible.py index b01988e629..eba660fa0b 100644 --- a/tests/unit/provisioner/test_ansible.py +++ b/tests/unit/provisioner/test_ansible.py @@ -32,7 +32,6 @@ if TYPE_CHECKING: - from pathlib import Path from unittest.mock import MagicMock, Mock from pytest_mock import MockerFixture @@ -71,9 +70,6 @@ def _provisioner_section_data(): # type: ignore[no-untyped-def] # noqa: ANN202 "options": {"foo": "bar", "become": True, "v": True}, "env": { "FOO": "bar", - "ANSIBLE_ROLES_PATH": "foo/bar", - "ANSIBLE_LIBRARY": "foo/bar", - "ANSIBLE_FILTER_PLUGINS": "foo/bar", }, "inventory": { "hosts": { @@ -147,9 +143,6 @@ def test_ansible_default_env_property(instance): # type: ignore[no-untyped-def] assert "MOLECULE_SCENARIO_DIRECTORY" in instance.default_env assert "MOLECULE_INSTANCE_CONFIG" in instance.default_env assert "ANSIBLE_CONFIG" in instance.env - assert "ANSIBLE_ROLES_PATH" in instance.env - assert "ANSIBLE_LIBRARY" in instance.env - assert "ANSIBLE_FILTER_PLUGINS" in instance.env def test_provisioner_name_property(instance): # type: ignore[no-untyped-def] # noqa: ANN201, D103 @@ -224,48 +217,6 @@ def test_provisioner_env_property(instance): # type: ignore[no-untyped-def] # assert instance.env["FOO"] == "bar" -@pytest.mark.parametrize( - "config_instance", - ["_provisioner_section_data"], # noqa: PT007 - indirect=True, -) -def test_env_appends_env_property(instance): # type: ignore[no-untyped-def] # noqa: ANN201, D103 - x = instance._get_modules_directories() - x.append( - util.abs_path( - os.path.join(instance._config.scenario.directory, "foo", "bar"), # noqa: PTH118 - ), - ) - assert x == instance.env["ANSIBLE_LIBRARY"].split(":") - - x = [ - instance._get_filter_plugin_directory(), - util.abs_path( - os.path.join( # noqa: PTH118 - instance._config.scenario.ephemeral_directory, - "plugins", - "filter", - ), - ), - util.abs_path( - os.path.join(instance._config.project_directory, "plugins", "filter"), # noqa: PTH118 - ), - util.abs_path( - os.path.join( # noqa: PTH118 - os.path.expanduser("~"), # noqa: PTH111 - ".ansible", - "plugins", - "filter", - ), - ), - "/usr/share/ansible/plugins/filter", - util.abs_path( - os.path.join(instance._config.scenario.directory, "foo", "bar"), # noqa: PTH118 - ), - ] - assert x == instance.env["ANSIBLE_FILTER_PLUGINS"].split(":") - - @pytest.mark.parametrize( "config_instance", ["_provisioner_section_data"], # noqa: PT007 @@ -792,103 +743,6 @@ def test_get_plugin_directory(instance): # type: ignore[no-untyped-def] # noqa assert parts[-4:] == ("molecule", "provisioner", "ansible", "plugins") -def test_get_modules_directories_default( - instance: ansible.Ansible, - monkeypatch: pytest.MonkeyPatch, - test_cache_path: Path, -) -> None: - """Test the default module directories. - - Args: - instance: The instance of the config. - monkeypatch: The monkeypatch fixture. - test_cache_path: The path to the cache directory for the test. - """ - monkeypatch.delenv("ANSIBLE_LIBRARY", raising=False) - - paths = instance._get_modules_directories() - number_paths = 5 - assert len(paths) == number_paths - assert paths[0].endswith("molecule/provisioner/ansible/plugins/modules") - assert paths[1] == f"{test_cache_path}/library" - assert paths[2] == f"{test_cache_path}/library" - assert paths[3].endswith(".ansible/plugins/modules") - assert paths[4] == "/usr/share/ansible/plugins/modules" - - -def test_get_modules_directories_single_ansible_library(instance: ansible.Ansible, monkeypatch): # type: ignore[no-untyped-def] # noqa: ANN201, D103 - monkeypatch.setenv("ANSIBLE_LIBRARY", "/abs/path/lib") - - paths = instance._get_modules_directories() - - assert len(paths) == 6 # noqa: PLR2004 - assert paths[0] == "/abs/path/lib" - - -def test_get_modules_directories_multi_ansible_library(instance: ansible.Ansible, monkeypatch): # type: ignore[no-untyped-def] # noqa: ANN201, D103 - monkeypatch.setenv("ANSIBLE_LIBRARY", "relpath/lib:/abs/path/lib") - - paths = instance._get_modules_directories() - - assert len(paths) == 7 # noqa: PLR2004 - assert paths[0].endswith("relpath/lib") - assert paths[1] == "/abs/path/lib" - - -def test_get_filter_plugin_directory(instance): # type: ignore[no-untyped-def] # noqa: ANN201, D103 - result = instance._get_filter_plugin_directory() - parts = os_split(result) - x = ("molecule", "provisioner", "ansible", "plugins", "filter") - - assert x == parts[-5:] - - -def test_get_filter_plugins_directories_default( # noqa: D103 - instance: ansible.Ansible, - monkeypatch: pytest.MonkeyPatch, - test_cache_path: Path, -) -> None: - monkeypatch.delenv("ANSIBLE_FILTER_PLUGINS", raising=False) - - paths = instance._get_filter_plugins_directories() - - number_paths = 5 - assert len(paths) == number_paths - assert paths[0].endswith("molecule/provisioner/ansible/plugins/filter") - assert paths[1] == f"{test_cache_path}/plugins/filter" - assert paths[2] == f"{test_cache_path}/plugins/filter" - assert paths[3].endswith(".ansible/plugins/filter") - assert paths[4] == "/usr/share/ansible/plugins/filter" - - -def test_get_filter_plugins_directories_single_ansible_filter_plugins( # type: ignore[no-untyped-def] # noqa: ANN201, D103 - instance, - monkeypatch, -): - monkeypatch.setenv("ANSIBLE_FILTER_PLUGINS", "/abs/path/plugins/filter") - - paths = instance._get_filter_plugins_directories() - - assert len(paths) == 6 # noqa: PLR2004 - assert paths[0] == "/abs/path/plugins/filter" - - -def test_get_filter_plugins_directories_multi_ansible_filter_plugins( # type: ignore[no-untyped-def] # noqa: ANN201, D103 - instance, - monkeypatch, -): - monkeypatch.setenv( - "ANSIBLE_FILTER_PLUGINS", - "relpath/plugins/filter:/abs/path/plugins/filter", - ) - - paths = instance._get_filter_plugins_directories() - - assert len(paths) == 7 # noqa: PLR2004 - assert paths[0].endswith("relpath/plugins/filter") - assert paths[1] == "/abs/path/plugins/filter" - - def test_absolute_path_for(instance): # type: ignore[no-untyped-def] # noqa: ANN201, D103 env = {"foo": "foo:bar"} x = ":".join( diff --git a/tests/unit/verifier/test_testinfra.py b/tests/unit/verifier/test_testinfra.py index c587897678..a575786e1b 100644 --- a/tests/unit/verifier/test_testinfra.py +++ b/tests/unit/verifier/test_testinfra.py @@ -147,9 +147,6 @@ def test_additional_files_or_dirs_property(_instance): # type: ignore[no-untype def test_testinfra_env_property(_instance): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, D103 assert _instance.env["FOO"] == "bar" assert "ANSIBLE_CONFIG" in _instance.env - assert "ANSIBLE_ROLES_PATH" in _instance.env - assert "ANSIBLE_LIBRARY" in _instance.env - assert "ANSIBLE_FILTER_PLUGINS" in _instance.env def test_testinfra_name_property(_instance): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, D103 diff --git a/tox.ini b/tox.ini index 305b558406..38c85ee8aa 100644 --- a/tox.ini +++ b/tox.ini @@ -28,6 +28,7 @@ pass_env = TERM USER set_env = + ANSIBLE_HOME = {toxinidir}/.ansible COVERAGE_COMBINED = {envdir}/.coverage COVERAGE_FILE = {env:COVERAGE_FILE:{envdir}/.coverage.{envname}} COVERAGE_PROCESS_START = {toxinidir}/pyproject.toml