Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make collection location cache_dir aware #384

Merged
merged 1 commit into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 40 additions & 23 deletions src/ansible_compat/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
import re
import subprocess
from collections import UserDict
from typing import Literal
from typing import TYPE_CHECKING, Literal

from packaging.version import Version

from ansible_compat.constants import ANSIBLE_MIN_VERSION
from ansible_compat.errors import InvalidPrerequisiteError, MissingAnsibleError
from ansible_compat.ports import cache

if TYPE_CHECKING:
from pathlib import Path


# do not use lru_cache here, as environment can change between calls
def ansible_collections_path() -> str:
Expand Down Expand Up @@ -397,35 +400,49 @@ def __init__(
self,
config_dump: str | None = None,
data: dict[str, object] | None = None,
cache_dir: Path | None = None,
) -> None:
"""Load config dictionary."""
super().__init__()

self.cache_dir = cache_dir
if data:
self.data = copy.deepcopy(data)
return

if not config_dump:
env = os.environ.copy()
# Avoid possible ANSI garbage
env["ANSIBLE_FORCE_COLOR"] = "0"
config_dump = subprocess.check_output(
["ansible-config", "dump"], # noqa: S603
universal_newlines=True,
env=env,
)
else:
if not config_dump:
env = os.environ.copy()
# Avoid possible ANSI garbage
env["ANSIBLE_FORCE_COLOR"] = "0"
config_dump = subprocess.check_output(
["ansible-config", "dump"], # noqa: S603
universal_newlines=True,
env=env,
)

for match in re.finditer(
r"^(?P<key>[A-Za-z0-9_]+).* = (?P<value>.*)$",
config_dump,
re.MULTILINE,
):
key = match.groupdict()["key"]
value = match.groupdict()["value"]
try:
self[key] = ast.literal_eval(value)
except (NameError, SyntaxError, ValueError):
self[key] = value
for match in re.finditer(
r"^(?P<key>[A-Za-z0-9_]+).* = (?P<value>.*)$",
config_dump,
re.MULTILINE,
):
key = match.groupdict()["key"]
value = match.groupdict()["value"]
try:
self[key] = ast.literal_eval(value)
except (NameError, SyntaxError, ValueError):
self[key] = value
# inject isolation collections paths into the config
if self.cache_dir:
cpaths = self.data["COLLECTIONS_PATHS"]
if cpaths and isinstance(cpaths, list):
cpaths.insert(
0,
f"{self.cache_dir}/collections",
)
else: # pragma: no cover
msg = f"Unexpected data type for COLLECTIONS_PATHS: {cpaths}"
raise RuntimeError(msg)
if data:
return

def __getattribute__(self, attr_name: str) -> object:
"""Allow access of config options as attributes."""
Expand Down
23 changes: 5 additions & 18 deletions src/ansible_compat/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ def __init__(

if isolated:
self.cache_dir = get_cache_dir(self.project_dir)
self.config = AnsibleConfig()
self.config = AnsibleConfig(cache_dir=self.cache_dir)

# Add the sys.path to the collection paths if not isolated
self._add_sys_path_to_collection_paths()
Expand Down Expand Up @@ -273,13 +273,13 @@ def load_collections(self) -> None:
self.collections = OrderedDict()
no_collections_msg = "None of the provided paths were usable"

# do not use --path because it does not allow multiple values
proc = self.run(
[
"ansible-galaxy",
"collection",
"list",
"--format=json",
f"-p={':'.join(self.config.collections_paths)}",
],
)
if proc.returncode == RC_ANSIBLE_OPTIONS_ERROR and (
Expand Down Expand Up @@ -392,6 +392,8 @@ def run( # ruff: disable=PLR0913
# https://github.com/ansible/ansible-lint/issues/3522
env["ANSIBLE_VERBOSE_TO_STDERR"] = "True"

env["ANSIBLE_COLLECTIONS_PATH"] = ":".join(self.config.collections_paths)

for _ in range(self.max_retries + 1 if retry else 1):
result = run_func(
args,
Expand Down Expand Up @@ -520,7 +522,7 @@ def install_collection(
env={**self.environ, ansible_collections_path(): ":".join(cpaths)},
)
if process.returncode != 0:
msg = f"Command returned {process.returncode} code:\n{process.stdout}\n{process.stderr}"
msg = f"Command {' '.join(cmd)}, returned {process.returncode} code:\n{process.stdout}\n{process.stderr}"
_logger.error(msg)
raise InvalidPrerequisiteError(msg)

Expand Down Expand Up @@ -608,19 +610,10 @@ def install_requirements( # noqa: C901
)
else:
cmd.extend(["-r", str(requirement)])
cpaths = self.config.collections_paths
if self.cache_dir:
# we cannot use '-p' because it breaks galaxy ability to ignore already installed collections, so
# we hack ansible_collections_path instead and inject our own path there.
dest_path = f"{self.cache_dir}/collections"
if dest_path not in cpaths:
# pylint: disable=no-member
cpaths.insert(0, dest_path)
_logger.info("Running %s", " ".join(cmd))
result = self.run(
cmd,
retry=retry,
env={**os.environ, "ANSIBLE_COLLECTIONS_PATH": ":".join(cpaths)},
)
_logger.debug(result.stdout)
if result.returncode != 0:
Expand Down Expand Up @@ -757,12 +750,6 @@ def require_collection(
msg,
)

if self.cache_dir:
# if we have a cache dir, we want to be use that would be preferred
# destination when installing a missing collection
# https://github.com/PyCQA/pylint/issues/4667
paths.insert(0, f"{self.cache_dir}/collections") # pylint: disable=E1101

for path in paths:
collpath = Path(path) / "ansible_collections" / ns / coll
if collpath.exists():
Expand Down
2 changes: 1 addition & 1 deletion test/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def test_version_module() -> None:
# import kept here to allow mypy/pylint to run when module is not installed
# and the generated _version.py is missing.
# pylint: disable=no-name-in-module,no-member
import ansible_compat._version # type: ignore[import-not-found]
import ansible_compat._version # type: ignore[import-not-found,unused-ignore]

assert ansible_compat._version.__version__
assert ansible_compat._version.__version_tuple__
Expand Down
Loading