From 39fb50022ca496c5be6c90ff78d69947c90bcc0d Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Sat, 4 Sep 2021 11:07:12 +0100 Subject: [PATCH] Add install_collection_from_disk method (#67) As ansible-galaxy can install collections from disk only with 2.10+, we add a new method that can be used with all supported versions of Ansible. This also enables install_collection to have a force argument. --- src/ansible_compat/runtime.py | 37 +++++++++++++++++- test/collections/acme.broken/galaxy.yml | 1 + .../acme.goodies/acme-goodies-1.0.0.tar.gz | Bin 0 -> 1276 bytes test/collections/acme.goodies/galaxy.yml | 30 ++++++++++++++ .../molecule/default/converge.yml | 7 ++++ .../molecule/default/molecule.yml | 11 ++++++ .../acme.goodies/roles/baz/tasks/main.yml | 3 ++ test/test_runtime.py | 27 +++++++++++++ tox.ini | 2 +- 9 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 test/collections/acme.broken/galaxy.yml create mode 100644 test/collections/acme.goodies/acme-goodies-1.0.0.tar.gz create mode 100644 test/collections/acme.goodies/galaxy.yml create mode 100644 test/collections/acme.goodies/molecule/default/converge.yml create mode 100644 test/collections/acme.goodies/molecule/default/molecule.yml create mode 100644 test/collections/acme.goodies/roles/baz/tasks/main.yml diff --git a/src/ansible_compat/runtime.py b/src/ansible_compat/runtime.py index 75b46e23..9d6c7416 100644 --- a/src/ansible_compat/runtime.py +++ b/src/ansible_compat/runtime.py @@ -7,6 +7,7 @@ import re import shutil import subprocess +import tempfile from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union import packaging @@ -167,7 +168,10 @@ def version_in_range( return True def install_collection( - self, collection: str, destination: Optional[Union[str, pathlib.Path]] = None + self, + collection: str, + destination: Optional[Union[str, pathlib.Path]] = None, + force: bool = False, ) -> None: """Install an Ansible collection. @@ -182,7 +186,7 @@ def install_collection( # ansible-galaxy before 2.11 fails to upgrade collection unless --force # is present, newer versions do not need it - if self.version_in_range(upper="2.11"): + if force or self.version_in_range(upper="2.11"): cmd.append("--force") if destination: @@ -199,6 +203,35 @@ def install_collection( _logger.error(msg) raise InvalidPrerequisiteError(msg) + def install_collection_from_disk( + self, path: str, destination: Optional[Union[str, pathlib.Path]] = None + ) -> None: + """Build and install collection from a given disk path.""" + if not self.version_in_range(upper="2.11"): + self.install_collection(path, destination=destination, force=True) + return + # older versions of ansible able unable to install without building + with tempfile.TemporaryDirectory() as tmp_dir: + cmd = [ + "ansible-galaxy", + "collection", + "build", + "--output-path", + tmp_dir, + path, + ] + _logger.info("Running %s", " ".join(cmd)) + run = self.exec(cmd, retry=False) + if run.returncode != 0: + _logger.error(run.stdout) + raise AnsibleCommandError(run) + for archive_file in os.listdir(tmp_dir): + self.install_collection( + os.path.join(tmp_dir, archive_file), + destination=destination, + force=True, + ) + def install_requirements(self, requirement: str, retry: bool = False) -> None: """Install dependencies from a requirements.yml.""" if not os.path.exists(requirement): diff --git a/test/collections/acme.broken/galaxy.yml b/test/collections/acme.broken/galaxy.yml new file mode 100644 index 00000000..599fd5b0 --- /dev/null +++ b/test/collections/acme.broken/galaxy.yml @@ -0,0 +1 @@ +foo: that is not a valid collection! diff --git a/test/collections/acme.goodies/acme-goodies-1.0.0.tar.gz b/test/collections/acme.goodies/acme-goodies-1.0.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..80ee22fc9d9cb6a7d4d776ba77fb53dfb070c90d GIT binary patch literal 1276 zcmV_a`qepIu!28T`{!ZJ;zz zYBw}7Leq}@`LO?ppIyGVxavH!?_S#8Y`TK(=1!&8ubXXgJr*=;MY*_BG^lK0B!w19 z90`sEB$`Og1rG_+Q4&#=k`PF$S%js*oX1IGw22aFz(7KPrF7^-RTp*+&k-5Nu~U5X zfcl+D;Q62De+T@JVn#gwI|bQ=nB&>_5B^6(8Q%4OEV=W4&;O?3e~wLzB<({-)J6gl zbRmC_c$lmnRCkHTNOTTa$F5|bICRY6p0T>h2Rb>W!DXV}e-oq2RyD7iZ)kd1sH_Zb ziu`^bZrSCR=;bH)rHZ5>v0;D}25FRtAPqweEao(13Wo~E09ptEn(L79h|`EEC1l7M z(RlIyhH;1BPxR@-j;#4x4#zcXDG=`Y-$kKr%#R*Pih;hP| z5mal=RYG|bF)CAIPPmKT4rU{{MyiFFri~N5c^N{O=TU zhFogKrBtc{koygwtZXs^D6Og>QT5(2J9lo+K(bw(yZi_$nw71$hCHv(+p5mZPYVwm zKK}do|2@Zlg1L|XCnUEC`6Jr+iv2&%{rb=2zn}l<@t=nt|4$MB#{*f90sq|_VZ!6T z$A3Tnp8tE96#w@R7eAo?e`)`ZlKcM8IOTr-Zwfg(JDcCW^?Ktr_WJ$6mCDt-o4Kj8 zH&E}?269-b7R-E;tJWQcZd~EGisySAz>Kip^jZj*+WrOvkoP}Bb-hf5}-w5|%Iw{;C=doOwDB None: + """Tests ability to install a local collection.""" + with remember_cwd("test/collections/acme.goodies"): + runtime = Runtime(isolated=True) + runtime.install_collection_from_disk(".") + # that molecule converge playbook can be used without molecule and + # should validate that the installed collection is available. + result = runtime.exec(["ansible-playbook", "molecule/default/converge.yml"]) + assert result.returncode == 0, result.stdout + runtime.clean() + + +def test_install_collection_from_disk_fail() -> None: + """Tests that we fail to install a broken collection.""" + with remember_cwd("test/collections/acme.broken"): + runtime = Runtime(isolated=True) + exception: Type[Exception] + if runtime.version_in_range(upper="2.11"): + exception = AnsibleCommandError + msg = "Got 1 exit code while running: ansible-galaxy collection build" + else: + exception = InvalidPrerequisiteError + msg = "is missing the following mandatory" + with pytest.raises(exception, match=msg): + runtime.install_collection_from_disk(".") diff --git a/tox.ini b/tox.ini index 62177e75..958a8fc7 100644 --- a/tox.ini +++ b/tox.ini @@ -47,7 +47,7 @@ setenv = PIP_DISABLE_PIP_VERSION_CHECK = 1 PIP_CONSTRAINT = {toxinidir}/constraints.txt PRE_COMMIT_COLOR = always - PYTEST_REQPASS = 67 + PYTEST_REQPASS = 69 FORCE_COLOR = 1 allowlist_externals = sh