Skip to content

Commit

Permalink
Fix for galaxy with system site packages (#147)
Browse files Browse the repository at this point in the history
  • Loading branch information
cidrblock authored May 1, 2024
1 parent 57b47a0 commit 6db8cd3
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 20 deletions.
26 changes: 26 additions & 0 deletions src/ansible_dev_environment/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import json
import os
import shutil
import subprocess
import sys

Expand Down Expand Up @@ -101,6 +102,31 @@ def interpreter(self: Config) -> Path:
"""Return the current interpreter."""
return Path(sys.executable)

@property
def galaxy_bin(self: Config) -> Path | None:
"""Find the ansible galaxy command.
Prefer the venv over the system package over the PATH.
"""
within_venv = self.venv_bindir / "ansible-galaxy"
if within_venv.exists():
msg = f"Found ansible-galaxy in virtual environment: {within_venv}"
self._output.debug(msg)
return within_venv
system_pkg = self.site_pkg_path / "bin" / "ansible-galaxy"
if system_pkg.exists():
msg = f"Found ansible-galaxy in system packages: {system_pkg}"
self._output.debug(msg)
return system_pkg
last_resort = shutil.which("ansible-galaxy")
if last_resort:
msg = f"Found ansible-galaxy in PATH: {last_resort}"
self._output.debug(msg)
return Path(last_resort)
msg = "Failed to find ansible-galaxy."
self._output.critical(msg)
return None

def _set_interpreter(
self: Config,
) -> None:
Expand Down
8 changes: 4 additions & 4 deletions src/ansible_dev_environment/subcommands/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def _install_galaxy_collections(
shutil.rmtree(collection.site_pkg_path)

command = (
f"{self._config.venv_bindir / 'ansible-galaxy'} collection"
f"{self._config.galaxy_bin} collection"
f" install {collections_str}"
f" -p {self._config.site_pkg_path}"
" --force"
Expand Down Expand Up @@ -191,7 +191,7 @@ def _install_galaxy_requirements(self: Installer) -> None:
shutil.rmtree(cpath)

command = (
f"{self._config.venv_bindir / 'ansible-galaxy'} collection"
f"{self._config.galaxy_bin} collection"
f" install -r {self._config.args.requirement}"
f" -p {self._config.site_pkg_path}"
" --force"
Expand Down Expand Up @@ -359,7 +359,7 @@ def _install_local_collection(

command = (
f"cd {collection.build_dir} &&"
f" {self._config.venv_bindir / 'ansible-galaxy'} collection build"
f" {self._config.galaxy_bin} collection build"
f" --output-path {collection.build_dir}"
" --force"
)
Expand Down Expand Up @@ -412,7 +412,7 @@ def _install_local_collection(
shutil.rmtree(info_dir)

command = (
f"{self._config.venv_bindir / 'ansible-galaxy'} collection"
f"{self._config.galaxy_bin} collection"
f" install {tarball} -p {self._config.site_pkg_path}"
" --force"
)
Expand Down
216 changes: 200 additions & 16 deletions tests/unit/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,66 @@
from __future__ import annotations

import argparse
import shutil

from pathlib import Path
from typing import TYPE_CHECKING

import pytest

from ansible_dev_environment.config import Config
from ansible_dev_environment.output import Output
from ansible_dev_environment.utils import TermFeatures


if TYPE_CHECKING:
from pathlib import Path
from ansible_dev_environment.output import Output


def gen_args(
venv: str,
system_site_packages: bool = False, # noqa: FBT001, FBT002
) -> argparse.Namespace:
"""Generate the arguments.
Args:
venv: The virtual environment.
system_site_packages: Whether to include system site packages.
Returns:
The arguments.
"""
return argparse.Namespace(
verbose=0,
venv=venv,
system_site_packages=system_site_packages,
)


@pytest.mark.parametrize(
"system_site_packages",
((True, False)),
ids=["ssp_true", "ssp_false"],
)
def test_paths(tmpdir: Path, system_site_packages: bool) -> None: # noqa: FBT001
def test_paths(
tmpdir: Path,
system_site_packages: bool, # noqa: FBT001
output: Output,
) -> None:
"""Test the paths.
Several of the found directories should have a parent of the tmpdir / test_venv
Args:
tmpdir: A temporary directory.
system_site_packages: Whether to include system site packages.
output: The output fixture.
"""
venv = tmpdir / "test_venv"
args = argparse.Namespace(
args = gen_args(
venv=str(venv),
system_site_packages=system_site_packages,
verbose=0,
)
term_features = TermFeatures(color=False, links=False)

output = Output(
log_file=str(tmpdir / "test_log.log"),
log_level="debug",
log_append="false",
term_features=term_features,
verbosity=0,
)

config = Config(args=args, output=output, term_features=term_features)
config = Config(args=args, output=output, term_features=output.term_features)
config.init()

assert config.venv == venv
Expand All @@ -59,3 +74,172 @@ def test_paths(tmpdir: Path, system_site_packages: bool) -> None: # noqa: FBT00
"venv_interpreter",
):
assert venv in getattr(config, attr).parents


def test_galaxy_bin_venv(
tmpdir: Path,
monkeypatch: pytest.MonkeyPatch,
output: Output,
) -> None:
"""Test the galaxy_bin property found in venv.
Args:
tmpdir: A temporary directory.
monkeypatch: A pytest fixture for monkey patching.
output: The output fixture.
"""
venv = tmpdir / "test_venv"
args = gen_args(venv=str(venv))

config = Config(args=args, output=output, term_features=output.term_features)
config.init()

orig_exists = Path.exists
exists_called = False

def _exists(path: Path) -> bool:
if path.name != "ansible-galaxy":
return orig_exists(path)
if path.parent == config.venv_bindir:
nonlocal exists_called
exists_called = True
return True
return False

monkeypatch.setattr(Path, "exists", _exists)

assert config.galaxy_bin == venv / "bin" / "ansible-galaxy"
assert exists_called


def test_galaxy_bin_site(
tmpdir: Path,
monkeypatch: pytest.MonkeyPatch,
output: Output,
) -> None:
"""Test the galaxy_bin property found in site.
Args:
tmpdir: A temporary directory.
monkeypatch: A pytest fixture for monkey patching.
output: The output fixture.
"""
venv = tmpdir / "test_venv"
args = gen_args(venv=str(venv))

config = Config(args=args, output=output, term_features=output.term_features)
config.init()

orig_exists = Path.exists
exists_called = False

def _exists(path: Path) -> bool:
if path.name != "ansible-galaxy":
return orig_exists(path)
if path.parent == config.site_pkg_path / "bin":
nonlocal exists_called
exists_called = True
return True
return False

monkeypatch.setattr(Path, "exists", _exists)

assert config.galaxy_bin == config.site_pkg_path / "bin" / "ansible-galaxy"
assert exists_called


def test_galaxy_bin_path(
tmpdir: Path,
monkeypatch: pytest.MonkeyPatch,
output: Output,
) -> None:
"""Test the galaxy_bin property found in path.
Args:
tmpdir: A temporary directory.
monkeypatch: A pytest fixture for monkey patching.
output: The output fixture.
"""
venv = tmpdir / "test_venv"
args = gen_args(venv=str(venv))

config = Config(args=args, output=output, term_features=output.term_features)
config.init()

orig_exists = Path.exists
exists_called = False

def _exists(path: Path) -> bool:
if path.name != "ansible-galaxy":
return orig_exists(path)
nonlocal exists_called
exists_called = True
return False

monkeypatch.setattr(Path, "exists", _exists)

orig_which = shutil.which
which_called = False

def _which(name: str) -> str | None:
if not name.endswith("ansible-galaxy"):
return orig_which(name)
nonlocal which_called
which_called = True
return "patched"

monkeypatch.setattr(shutil, "which", _which)

assert config.galaxy_bin == Path("patched")
assert exists_called
assert which_called


def test_galaxy_bin_not_found(
tmpdir: Path,
monkeypatch: pytest.MonkeyPatch,
output: Output,
) -> None:
"""Test the galaxy_bin property found in venv.
Args:
tmpdir: A temporary directory.
monkeypatch: A pytest fixture for monkey patching.
output: The output fixture.
"""
venv = tmpdir / "test_venv"
args = gen_args(venv=str(venv))

config = Config(args=args, output=output, term_features=output.term_features)
config.init()

orig_exists = Path.exists
exist_called = False

def _exists(path: Path) -> bool:
if path.name == "ansible-galaxy":
nonlocal exist_called
exist_called = True
return False
return orig_exists(path)

monkeypatch.setattr(Path, "exists", _exists)

orig_which = shutil.which
which_called = False

def _which(name: str) -> str | None:
if name.endswith("ansible-galaxy"):
nonlocal which_called
which_called = True
return None
return orig_which(name)

monkeypatch.setattr(shutil, "which", _which)

with pytest.raises(SystemExit) as exc:
assert config.galaxy_bin is None

assert exc.value.code == 1
assert exist_called
assert which_called

0 comments on commit 6db8cd3

Please sign in to comment.