Skip to content

Commit

Permalink
Add install_collection_from_disk method (#67)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ssbarnea committed Sep 4, 2021
1 parent 67c25f7 commit 39fb500
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 3 deletions.
37 changes: 35 additions & 2 deletions src/ansible_compat/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import re
import shutil
import subprocess
import tempfile
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union

import packaging
Expand Down Expand Up @@ -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.
Expand All @@ -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:
Expand All @@ -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):
Expand Down
1 change: 1 addition & 0 deletions test/collections/acme.broken/galaxy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo: that is not a valid collection!
Binary file not shown.
30 changes: 30 additions & 0 deletions test/collections/acme.goodies/galaxy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: goodies
namespace: acme
version: 1.0.0
readme: README.md
authors:
- Red Hat
description: Sample collection to use with molecule
build_ignore:
- "*.egg-info"
- .DS_Store
- .eggs
- .gitignore
- .mypy_cache
- .pytest_cache
- .stestr
- .stestr.conf
- .tox
- .vscode
- MANIFEST.in
- build
- dist
- doc
- report.html
- setup.cfg
- setup.py
- "tests/unit/*.*"
- README.rst
- tox.ini

license_file: LICENSE
7 changes: 7 additions & 0 deletions test/collections/acme.goodies/molecule/default/converge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
- name: Converge
hosts: localhost
tasks:
- name: "Include sample role from current collection"
include_role:
name: acme.goodies.baz
11 changes: 11 additions & 0 deletions test/collections/acme.goodies/molecule/default/molecule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
dependency:
name: galaxy
driver:
name: delegated
platforms:
- name: instance
provisioner:
name: ansible
verifier:
name: ansible
3 changes: 3 additions & 0 deletions test/collections/acme.goodies/roles/baz/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- name: "some task inside foo.bar collection"
debug:
msg: "hello world!"
27 changes: 27 additions & 0 deletions test/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,3 +568,30 @@ def test_runtime_version_in_range(
"""Validate functioning of version_in_range."""
runtime = Runtime()
assert runtime.version_in_range(lower=lower, upper=upper) is expected


def test_install_collection_from_disk() -> 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(".")
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 39fb500

Please sign in to comment.