From 694ef5660d45fcb97c9beea5b2750f6eadcf5e93 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Tue, 3 Dec 2019 04:55:31 +1000 Subject: [PATCH] Fix using a URL for galaxy collection install (#65272) * Fix using a URL for galaxy collection install * Update lib/ansible/galaxy/collection.py Co-Authored-By: Sloane Hertel --- .../fragments/collection-install-url.yaml | 2 ++ lib/ansible/cli/galaxy.py | 10 +++++++- lib/ansible/galaxy/collection.py | 8 +++++-- test/units/cli/test_galaxy.py | 23 +++++++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/collection-install-url.yaml diff --git a/changelogs/fragments/collection-install-url.yaml b/changelogs/fragments/collection-install-url.yaml new file mode 100644 index 00000000000..4ec536ab87f --- /dev/null +++ b/changelogs/fragments/collection-install-url.yaml @@ -0,0 +1,2 @@ +bugfixes: +- ansible-galaxy - Fix ``collection install`` when installing from a URL or a file - https://github.com/ansible/ansible/issues/65109 diff --git a/lib/ansible/cli/galaxy.py b/lib/ansible/cli/galaxy.py index 487f21d0dbd..9cf3a8e87e5 100644 --- a/lib/ansible/cli/galaxy.py +++ b/lib/ansible/cli/galaxy.py @@ -29,12 +29,14 @@ from ansible.galaxy.role import GalaxyRole from ansible.galaxy.token import BasicAuthToken, GalaxyToken, KeycloakToken, NoTokenSentinel from ansible.module_utils.ansible_release import __version__ as ansible_version from ansible.module_utils._text import to_bytes, to_native, to_text +from ansible.module_utils import six from ansible.parsing.yaml.loader import AnsibleLoader from ansible.playbook.role.requirement import RoleRequirement from ansible.utils.display import Display from ansible.utils.plugin_docs import get_versioned_doclink display = Display() +urlparse = six.moves.urllib.parse.urlparse class GalaxyCLI(CLI): @@ -805,7 +807,13 @@ class GalaxyCLI(CLI): else: requirements = [] for collection_input in collections: - name, dummy, requirement = collection_input.partition(':') + requirement = None + if os.path.isfile(to_bytes(collection_input, errors='surrogate_or_strict')) or \ + urlparse(collection_input).scheme.lower() in ['http', 'https']: + # Arg is a file path or URL to a collection + name = collection_input + else: + name, dummy, requirement = collection_input.partition(':') requirements.append((name, requirement or '*', None)) output_path = GalaxyCLI._resolve_path(output_path) diff --git a/lib/ansible/galaxy/collection.py b/lib/ansible/galaxy/collection.py index 0569605a3de..ede9492251a 100644 --- a/lib/ansible/galaxy/collection.py +++ b/lib/ansible/galaxy/collection.py @@ -827,9 +827,13 @@ def _get_collection_info(dep_map, existing_collections, collection, requirement, if os.path.isfile(to_bytes(collection, errors='surrogate_or_strict')): display.vvvv("Collection requirement '%s' is a tar artifact" % to_text(collection)) b_tar_path = to_bytes(collection, errors='surrogate_or_strict') - elif urlparse(collection).scheme: + elif urlparse(collection).scheme.lower() in ['http', 'https']: display.vvvv("Collection requirement '%s' is a URL to a tar artifact" % collection) - b_tar_path = _download_file(collection, b_temp_path, None, validate_certs) + try: + b_tar_path = _download_file(collection, b_temp_path, None, validate_certs) + except urllib_error.URLError as err: + raise AnsibleError("Failed to download collection tar from '%s': %s" + % (to_native(collection), to_native(err))) if b_tar_path: req = CollectionRequirement.from_tar(b_tar_path, force, parent=parent) diff --git a/test/units/cli/test_galaxy.py b/test/units/cli/test_galaxy.py index bc6628d2b38..f8b544e09f8 100644 --- a/test/units/cli/test_galaxy.py +++ b/test/units/cli/test_galaxy.py @@ -890,6 +890,29 @@ def test_collection_install_in_collection_dir(collection_install, monkeypatch): assert mock_install.call_args[0][7] is False +def test_collection_install_with_url(collection_install): + mock_install, dummy, output_dir = collection_install + + galaxy_args = ['ansible-galaxy', 'collection', 'install', 'https://foo/bar/foo-bar-v1.0.0.tar.gz', + '--collections-path', output_dir] + GalaxyCLI(args=galaxy_args).run() + + collection_path = os.path.join(output_dir, 'ansible_collections') + assert os.path.isdir(collection_path) + + assert mock_install.call_count == 1 + assert mock_install.call_args[0][0] == [('https://foo/bar/foo-bar-v1.0.0.tar.gz', '*', None)] + assert mock_install.call_args[0][1] == collection_path + assert len(mock_install.call_args[0][2]) == 1 + assert mock_install.call_args[0][2][0].api_server == 'https://galaxy.ansible.com' + assert mock_install.call_args[0][2][0].validate_certs is True + assert mock_install.call_args[0][3] is True + assert mock_install.call_args[0][4] is False + assert mock_install.call_args[0][5] is False + assert mock_install.call_args[0][6] is False + assert mock_install.call_args[0][7] is False + + def test_collection_install_name_and_requirements_fail(collection_install): test_path = collection_install[2] expected = 'The positional collection_name arg and --requirements-file are mutually exclusive.'