From a2c856d8b2e9edb5bf7fb7e970ecbed697b6a101 Mon Sep 17 00:00:00 2001 From: emil Date: Mon, 22 Jun 2026 18:22:26 +0000 Subject: [PATCH] DEVX-4: feat: add --no-ansible-collections option to setup tool --- .taskid | 2 +- src/devx/tools/setup.py | 23 ++++++++++++++- tests/unit/test_setup.py | 62 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/.taskid b/.taskid index 4b58f93..e4b81e6 100644 --- a/.taskid +++ b/.taskid @@ -1 +1 @@ -DEVX-3 +DEVX-4 diff --git a/src/devx/tools/setup.py b/src/devx/tools/setup.py index 5b68c84..aceb9ab 100644 --- a/src/devx/tools/setup.py +++ b/src/devx/tools/setup.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Project setup: install Python deps, pre-commit hooks, and tea CLI login. +"""Project setup: install Python deps, Ansible collections, pre-commit hooks, and tea CLI login. Usage:: @@ -38,6 +38,16 @@ def _install_pre_commit_hooks(bin_dir: str) -> None: _run([pre_commit, "install", "--hook-type", hook_type]) +def _install_ansible_collections(bin_dir: str) -> None: + """Install required Ansible Galaxy collections if requirements exist.""" + galaxy = str(Path(bin_dir) / "ansible-galaxy") + requirements = Path("ansible/requirements.yml") + if not requirements.exists(): + click.echo(" ansible/requirements.yml not found — skipping collections.") + return + _run([galaxy, "collection", "install", "-r", str(requirements)]) + + def _configure_tea_login() -> None: """Configure tea CLI login from .env if REPO_TOKEN is set. @@ -119,11 +129,18 @@ def _verify(bin_dir: str) -> None: default=False, help="Skip tea CLI login configuration.", ) +@click.option( + "--no-ansible-collections", + is_flag=True, + default=False, + help="Skip Ansible Galaxy collection installation.", +) def main( bin_dir: str, extras: str, no_pre_commit: bool, no_tea_login: bool, + no_ansible_collections: bool, ) -> None: """Install Python deps, pre-commit hooks, and configure tea CLI.""" if not Path(bin_dir).exists(): @@ -132,6 +149,10 @@ def main( click.echo(f"Installing Python dependencies (extras: {extras})...") _install_python_deps(bin_dir, extras) + if not no_ansible_collections: + click.echo("Installing Ansible Galaxy collections...") + _install_ansible_collections(bin_dir) + if not no_pre_commit: click.echo("Installing pre-commit hooks...") _install_pre_commit_hooks(bin_dir) diff --git a/tests/unit/test_setup.py b/tests/unit/test_setup.py index 7265576..2fd529c 100644 --- a/tests/unit/test_setup.py +++ b/tests/unit/test_setup.py @@ -9,6 +9,7 @@ from click.testing import CliRunner from devx.tools.setup import ( _configure_tea_login, + _install_ansible_collections, _install_pre_commit_hooks, _install_python_deps, _run, @@ -58,6 +59,24 @@ class TestInstallPreCommitHooks: assert "pre-push" in hook_types +class TestInstallAnsibleCollections: + @patch("devx.tools.setup._run") + def test_installs_from_requirements(self, mock_run: MagicMock, tmp_path: Path) -> None: + req = tmp_path / "ansible" / "requirements.yml" + req.parent.mkdir(parents=True) + req.write_text("collections: []") + with patch("devx.tools.setup.Path") as mock_path: + mock_path.return_value.exists.return_value = True + mock_path.return_value.__str__ = lambda _: str(req) + _install_ansible_collections(".venv/bin") + mock_run.assert_called_once() + + @patch("devx.tools.setup._run") + def test_skips_when_no_requirements(self, mock_run: MagicMock) -> None: + _install_ansible_collections(".venv/bin") + mock_run.assert_not_called() + + class TestConfigureTeaLogin: @patch("devx.tools.setup.shutil.which", return_value=None) def test_tea_not_installed(self, mock_which: MagicMock) -> None: @@ -142,10 +161,12 @@ class TestMain: @patch("devx.tools.setup._configure_tea_login") @patch("devx.tools.setup._verify") @patch("devx.tools.setup._install_pre_commit_hooks") + @patch("devx.tools.setup._install_ansible_collections") @patch("devx.tools.setup._install_python_deps") def test_main_success( self, mock_install_deps: MagicMock, + mock_install_ansible: MagicMock, mock_install_hooks: MagicMock, mock_verify: MagicMock, mock_tea: MagicMock, @@ -157,6 +178,7 @@ class TestMain: result = runner.invoke(main, ["--bin", str(bin_dir)]) assert result.exit_code == 0 mock_install_deps.assert_called_once() + mock_install_ansible.assert_called_once() mock_install_hooks.assert_called_once() mock_verify.assert_called_once() mock_tea.assert_called_once() @@ -164,10 +186,12 @@ class TestMain: @patch("devx.tools.setup._configure_tea_login") @patch("devx.tools.setup._verify") @patch("devx.tools.setup._install_pre_commit_hooks") + @patch("devx.tools.setup._install_ansible_collections") @patch("devx.tools.setup._install_python_deps") def test_main_no_pre_commit( self, mock_install_deps: MagicMock, + mock_install_ansible: MagicMock, mock_install_hooks: MagicMock, mock_verify: MagicMock, mock_tea: MagicMock, @@ -184,10 +208,33 @@ class TestMain: @patch("devx.tools.setup._configure_tea_login") @patch("devx.tools.setup._verify") @patch("devx.tools.setup._install_pre_commit_hooks") + @patch("devx.tools.setup._install_ansible_collections") + @patch("devx.tools.setup._install_python_deps") + def test_main_no_ansible_collections( + self, + mock_install_deps: MagicMock, + mock_install_ansible: MagicMock, + mock_install_hooks: MagicMock, + mock_verify: MagicMock, + mock_tea: MagicMock, + tmp_path: Path, + ) -> None: + bin_dir = tmp_path / "bin" + bin_dir.mkdir() + runner = CliRunner() + result = runner.invoke(main, ["--bin", str(bin_dir), "--no-ansible-collections"]) + assert result.exit_code == 0 + mock_install_ansible.assert_not_called() + + @patch("devx.tools.setup._configure_tea_login") + @patch("devx.tools.setup._verify") + @patch("devx.tools.setup._install_pre_commit_hooks") + @patch("devx.tools.setup._install_ansible_collections") @patch("devx.tools.setup._install_python_deps") def test_main_custom_extras( self, mock_install_deps: MagicMock, + mock_install_ansible: MagicMock, mock_install_hooks: MagicMock, mock_verify: MagicMock, mock_tea: MagicMock, @@ -203,10 +250,12 @@ class TestMain: @patch("devx.tools.setup._configure_tea_login") @patch("devx.tools.setup._verify") @patch("devx.tools.setup._install_pre_commit_hooks") + @patch("devx.tools.setup._install_ansible_collections") @patch("devx.tools.setup._install_python_deps") def test_main_no_tea_login( self, mock_install_deps: MagicMock, + mock_install_ansible: MagicMock, mock_install_hooks: MagicMock, mock_verify: MagicMock, mock_tea: MagicMock, @@ -233,9 +282,10 @@ def test_main_module_block(tmp_path: Path) -> None: with patch.dict("os.environ", {}, clear=True): with patch("devx.tools.setup._install_python_deps") as mock_deps: with patch("devx.tools.setup._install_pre_commit_hooks"): - with patch("devx.tools.setup._configure_tea_login"): - with patch("devx.tools.setup._verify"): - runner = CliRunner() - result = runner.invoke(main, ["--bin", str(bin_dir)]) - assert result.exit_code == 0 - mock_deps.assert_called_once() + with patch("devx.tools.setup._install_ansible_collections"): + with patch("devx.tools.setup._configure_tea_login"): + with patch("devx.tools.setup._verify"): + runner = CliRunner() + result = runner.invoke(main, ["--bin", str(bin_dir)]) + assert result.exit_code == 0 + mock_deps.assert_called_once()