From 6a370a251dbea5b7e83f0591c9145b5fe68717e6 Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Mon, 6 Jan 2025 11:17:37 +0000 Subject: [PATCH] Define ANSIBLE_HOME to isolate from user environment --- .vscode/settings.json | 2 +- src/ansible_compat/prerun.py | 38 ++++++++++++++++++++++------------- src/ansible_compat/runtime.py | 4 ++-- test/test_runtime.py | 13 +++++++++--- 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a4369f34..2d8713b5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,7 +10,7 @@ "source.fixAll": "explicit", "source.organizeImports": "explicit" }, - "editor.defaultFormatter": "ms-python.black-formatter", + "editor.defaultFormatter": "charliermarsh.ruff", "editor.formatOnSave": true }, "editor.formatOnSave": true, diff --git a/src/ansible_compat/prerun.py b/src/ansible_compat/prerun.py index 9448269d..4edb50ae 100644 --- a/src/ansible_compat/prerun.py +++ b/src/ansible_compat/prerun.py @@ -1,22 +1,32 @@ """Utilities for configuring ansible runtime environment.""" -import hashlib import os +import warnings from pathlib import Path def get_cache_dir(project_dir: Path) -> Path: - """Compute cache directory to be used based on project path.""" - # we only use the basename instead of the full path in order to ensure that - # we would use the same key regardless the location of the user home - # directory or where the project is clones (as long the project folder uses - # the same name). - basename = project_dir.resolve().name.encode(encoding="utf-8") - # 6 chars of entropy should be enough - cache_key = hashlib.sha256(basename).hexdigest()[:6] - cache_dir = ( - Path(os.getenv("XDG_CACHE_HOME", "~/.cache")).expanduser() - / "ansible-compat" - / cache_key - ) + """Compute cache directory to be used based on project path. + + If isolated=True, it will ensure ANSIBLE_HOME is defined. + """ + ansible_home: Path + if "ANSIBLE_HOME" in os.environ: + cache_dir = Path(os.environ["ANSIBLE_HOME"]) + ansible_home = cache_dir + elif "VIRTUAL_ENV" in os.environ: + cache_dir = Path(os.environ["VIRTUAL_ENV"]) / ".ansible" + ansible_home = cache_dir + msg = f"ANSIBLE_HOME is not set, will will define it to {ansible_home} to ensure test isolation." + warnings.warn(msg, stacklevel=1) + os.environ["ANSIBLE_HOME"] = ansible_home.as_posix() + else: + cache_dir = Path("~/.ansible").expanduser() / ".ansible" + ansible_home = cache_dir + + # create expected directory structure in order to avoid possible warnings + # or errors from ansible (ansible-galaxy in particular) + for subdir in ("roles",): + (Path(ansible_home) / subdir).mkdir(parents=True, exist_ok=True) + return cache_dir diff --git a/src/ansible_compat/runtime.py b/src/ansible_compat/runtime.py index ca35e021..e8b0aba3 100644 --- a/src/ansible_compat/runtime.py +++ b/src/ansible_compat/runtime.py @@ -208,8 +208,8 @@ def __init__( if "PYTHONWARNINGS" not in self.environ: # pragma: no cover self.environ["PYTHONWARNINGS"] = "ignore:Blowfish has been deprecated" - if isolated: - self.cache_dir = get_cache_dir(self.project_dir) + self.cache_dir = get_cache_dir(self.project_dir) + self.config = AnsibleConfig(cache_dir=self.cache_dir) # Add the sys.path to the collection paths if not isolated diff --git a/test/test_runtime.py b/test/test_runtime.py index b665f3ed..2e482b7b 100644 --- a/test/test_runtime.py +++ b/test/test_runtime.py @@ -143,6 +143,7 @@ def test_runtime_prepare_ansible_paths_validation() -> None: runtime._prepare_ansible_paths() +@pytest.mark.filterwarnings("ignore::UserWarning") @pytest.mark.parametrize( ("folder", "role_name", "isolated"), ( @@ -652,10 +653,10 @@ def test_upgrade_collection(runtime_tmp: Runtime) -> None: runtime_tmp.require_collection("community.molecule", "0.1.0") -def test_require_collection_no_cache_dir() -> None: +@pytest.mark.filterwarnings("ignore::UserWarning") +def test_require_collection_not_isolated() -> None: """Check require_collection without a cache directory.""" - runtime = Runtime() - assert not runtime.cache_dir + runtime = Runtime(isolated=False) runtime.require_collection("community.molecule", "0.1.0", install=True) @@ -1018,10 +1019,16 @@ def test_get_galaxy_role_name_invalid() -> None: assert _get_galaxy_role_name(galaxy_infos) == "" +@pytest.mark.filterwarnings("ignore::UserWarning") def test_runtime_has_playbook() -> None: """Tests has_playbook method.""" runtime = Runtime(require_module=True) + runtime.prepare_environment( + required_collections={"community.molecule": "0.1.0"}, + install_local=True, + ) + assert not runtime.has_playbook("this-does-not-exist.yml") # call twice to ensure cache is used: assert not runtime.has_playbook("this-does-not-exist.yml")