diff options
author | Sloane Hertel <19572925+s-hertel@users.noreply.github.com> | 2022-06-09 11:13:28 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-06-09 10:13:28 -0500 |
commit | 977c2480dbc000574ccc0a9cb4c6ed1c1f5baede (patch) | |
tree | 1431b263a5a2886ca37e8be89f82092e276cb516 | |
parent | 39579134856f1acb584586a86589a16161bfeeb6 (diff) | |
download | ansible-977c2480dbc000574ccc0a9cb4c6ed1c1f5baede.tar.gz |
[2.13] Fix ansible-galaxy traceback when unexpected version of resolvelib is installed (#77901)
* Fix ansible-galaxy traceback when unexpected version of resolvelib is installed (#77630)
* Fix traceback when a supported version of resolvelib is not installed
Try to read the supported version range from the package distribution info and fall back to a hardcoded lowerbound/upperbound (>=0.5.3,<0.6.0).
* Add tests for unsupported resolvelib versions
* Resolve remaining import sanity test issues.
Co-authored-by: Matt Clay <matt@mystile.com>
Co-authored-by: Matt Martz <matt@sivel.net>
(cherry picked from commit 82f3a57bee274b52db8bbf1d1c072997f9c4f5f2)
* Fix boolean condition so that ansible-galaxy collection install works when a valid resolvelib is installed. (#77906)
(cherry picked from commit 6fbc8bd2bcc0c3375cc77c4df38f25e858e7225d)
* ansible-galaxy - ensure variable is defined for any error when getting the ansible-core distribution (#77993)
(cherry picked from commit db335498d0af11f01b1384487896729a154bc977)
Co-authored-by: Felix Fontein <felix@fontein.de>
10 files changed, 142 insertions, 51 deletions
diff --git a/changelogs/fragments/77630-ansible-galaxy-fix-unsupported-resolvelib-version.yml b/changelogs/fragments/77630-ansible-galaxy-fix-unsupported-resolvelib-version.yml new file mode 100644 index 0000000000..22f512674a --- /dev/null +++ b/changelogs/fragments/77630-ansible-galaxy-fix-unsupported-resolvelib-version.yml @@ -0,0 +1,2 @@ +bugfixes: + - ansible-galaxy - handle unsupported versions of resolvelib gracefully. diff --git a/lib/ansible/galaxy/collection/__init__.py b/lib/ansible/galaxy/collection/__init__.py index cdb80069dd..220285adba 100644 --- a/lib/ansible/galaxy/collection/__init__.py +++ b/lib/ansible/galaxy/collection/__init__.py @@ -27,8 +27,19 @@ from collections import namedtuple from contextlib import contextmanager from hashlib import sha256 from io import BytesIO +from importlib.metadata import distribution from itertools import chain +try: + from packaging.requirements import Requirement as PkgReq +except ImportError: + class PkgReq: # type: ignore[no-redef] + pass + + HAS_PACKAGING = False +else: + HAS_PACKAGING = True + if t.TYPE_CHECKING: from ansible.galaxy.collection.concrete_artifact_manager import ( ConcreteArtifactsManager, @@ -79,16 +90,27 @@ from ansible.galaxy.collection.gpg import ( get_signature_from_source, GPG_ERROR_MAP, ) -from ansible.galaxy.dependency_resolution import ( - build_collection_dependency_resolver, -) +try: + from ansible.galaxy.dependency_resolution import ( + build_collection_dependency_resolver, + ) + from ansible.galaxy.dependency_resolution.errors import ( + CollectionDependencyResolutionImpossible, + CollectionDependencyInconsistentCandidate, + ) + from ansible.galaxy.dependency_resolution.providers import ( + RESOLVELIB_VERSION, + RESOLVELIB_LOWERBOUND, + RESOLVELIB_UPPERBOUND, + ) +except ImportError: + HAS_RESOLVELIB = False +else: + HAS_RESOLVELIB = True + from ansible.galaxy.dependency_resolution.dataclasses import ( Candidate, Requirement, _is_installed_collection_dir, ) -from ansible.galaxy.dependency_resolution.errors import ( - CollectionDependencyResolutionImpossible, - CollectionDependencyInconsistentCandidate, -) from ansible.galaxy.dependency_resolution.versioning import meets_requirements from ansible.module_utils.six import raise_from from ansible.module_utils._text import to_bytes, to_native, to_text @@ -1580,6 +1602,27 @@ def _resolve_depenency_map( include_signatures, # type: bool ): # type: (...) -> dict[str, Candidate] """Return the resolved dependency map.""" + if not HAS_RESOLVELIB: + raise AnsibleError("Failed to import resolvelib, check that a supported version is installed") + if not HAS_PACKAGING: + raise AnsibleError("Failed to import packaging, check that a supported version is installed") + try: + dist = distribution('ansible-core') + except Exception: + req = None + else: + req = next((rr for r in (dist.requires or []) if (rr := PkgReq(r)).name == 'resolvelib'), None) + finally: + if req is None: + # TODO: replace the hardcoded versions with a warning if the dist info is missing + # display.warning("Unable to find 'ansible-core' distribution requirements to verify the resolvelib version is supported.") + if not RESOLVELIB_LOWERBOUND <= RESOLVELIB_VERSION < RESOLVELIB_UPPERBOUND: + raise AnsibleError( + f"ansible-galaxy requires resolvelib<{RESOLVELIB_UPPERBOUND.vstring},>={RESOLVELIB_LOWERBOUND.vstring}" + ) + elif not req.specifier.contains(RESOLVELIB_VERSION.vstring): + raise AnsibleError(f"ansible-galaxy requires {req.name}{req.specifier}") + collection_dep_resolver = build_collection_dependency_resolver( galaxy_apis=galaxy_apis, concrete_artifacts_manager=concrete_artifacts_manager, diff --git a/lib/ansible/galaxy/dependency_resolution/errors.py b/lib/ansible/galaxy/dependency_resolution/errors.py index f5339a4d51..ae3b4396d7 100644 --- a/lib/ansible/galaxy/dependency_resolution/errors.py +++ b/lib/ansible/galaxy/dependency_resolution/errors.py @@ -6,7 +6,14 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from resolvelib.resolvers import ( - ResolutionImpossible as CollectionDependencyResolutionImpossible, - InconsistentCandidate as CollectionDependencyInconsistentCandidate, -) +try: + from resolvelib.resolvers import ( + ResolutionImpossible as CollectionDependencyResolutionImpossible, + InconsistentCandidate as CollectionDependencyInconsistentCandidate, + ) +except ImportError: + class CollectionDependencyResolutionImpossible(Exception): # type: ignore[no-redef] + pass + + class CollectionDependencyInconsistentCandidate(Exception): # type: ignore[no-redef] + pass diff --git a/lib/ansible/galaxy/dependency_resolution/providers.py b/lib/ansible/galaxy/dependency_resolution/providers.py index 08aeb2074c..bb375e5994 100644 --- a/lib/ansible/galaxy/dependency_resolution/providers.py +++ b/lib/ansible/galaxy/dependency_resolution/providers.py @@ -25,10 +25,24 @@ from ansible.galaxy.dependency_resolution.versioning import ( meets_requirements, ) from ansible.module_utils.six import string_types -from ansible.utils.version import SemanticVersion +from ansible.utils.version import SemanticVersion, LooseVersion from collections.abc import Set -from resolvelib import AbstractProvider + +try: + from resolvelib import AbstractProvider + from resolvelib import __version__ as resolvelib_version +except ImportError: + class AbstractProvider: # type: ignore[no-redef] + pass + + resolvelib_version = '0.0.0' + + +# TODO: add python requirements to ansible-test's ansible-core distribution info and remove the hardcoded lowerbound/upperbound fallback +RESOLVELIB_LOWERBOUND = SemanticVersion("0.5.3") +RESOLVELIB_UPPERBOUND = SemanticVersion("0.6.0") +RESOLVELIB_VERSION = SemanticVersion.from_loose_version(LooseVersion(resolvelib_version)) class PinnedCandidateRequests(Set): diff --git a/lib/ansible/galaxy/dependency_resolution/reporters.py b/lib/ansible/galaxy/dependency_resolution/reporters.py index d8eacb70df..69908b2243 100644 --- a/lib/ansible/galaxy/dependency_resolution/reporters.py +++ b/lib/ansible/galaxy/dependency_resolution/reporters.py @@ -6,7 +6,11 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from resolvelib import BaseReporter +try: + from resolvelib import BaseReporter +except ImportError: + class BaseReporter: # type: ignore[no-redef] + pass class CollectionDependencyReporter(BaseReporter): diff --git a/lib/ansible/galaxy/dependency_resolution/resolvers.py b/lib/ansible/galaxy/dependency_resolution/resolvers.py index 1b3e30ff86..87ca38d5d4 100644 --- a/lib/ansible/galaxy/dependency_resolution/resolvers.py +++ b/lib/ansible/galaxy/dependency_resolution/resolvers.py @@ -6,7 +6,11 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from resolvelib import Resolver +try: + from resolvelib import Resolver +except ImportError: + class Resolver: # type: ignore[no-redef] + pass class CollectionDependencyResolver(Resolver): diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/main.yml b/test/integration/targets/ansible-galaxy-collection/tasks/main.yml index 598784d3ad..c3b124e995 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/main.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/main.yml @@ -50,6 +50,12 @@ src: ansible.cfg.j2 dest: '{{ galaxy_dir }}/ansible.cfg' +- name: test install command using an unsupported version of resolvelib + include_tasks: unsupported_resolvelib.yml + loop: "{{ unsupported_resolvelib_versions }}" + loop_control: + loop_var: resolvelib_version + - name: run ansible-galaxy collection publish tests for {{ test_name }} include_tasks: publish.yml args: diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/unsupported_resolvelib.yml b/test/integration/targets/ansible-galaxy-collection/tasks/unsupported_resolvelib.yml new file mode 100644 index 0000000000..d9667b9331 --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/unsupported_resolvelib.yml @@ -0,0 +1,42 @@ +- vars: + venv_cmd: "{{ ansible_python_interpreter ~ ' -m venv' }}" + venv_dest: "{{ galaxy_dir }}/test_resolvelib_{{ resolvelib_version }}" + block: + - name: install another version of resolvelib that is unsupported by ansible-galaxy + pip: + name: resolvelib + version: "{{ resolvelib_version }}" + state: present + virtualenv_command: "{{ venv_cmd }}" + virtualenv: "{{ venv_dest }}" + virtualenv_site_packages: True + + - name: create test collection install directory - {{ test_name }} + file: + path: '{{ galaxy_dir }}/ansible_collections' + state: directory + + - name: install simple collection from first accessible server (expected failure) + command: "ansible-galaxy collection install namespace1.name1 {{ galaxy_verbosity }}" + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + PATH: "{{ venv_dest }}/bin:{{ ansible_env.PATH }}" + register: resolvelib_version_error + ignore_errors: yes + + - assert: + that: + - resolvelib_version_error is failed + - compat_error in resolvelib_version_error.stderr or import_error in resolvelib_version_error.stderr + vars: + import_error: "Failed to import resolvelib" + compat_error: "ansible-galaxy requires resolvelib<0.6.0,>=0.5.3" + + always: + - name: cleanup venv and install directory + file: + path: '{{ galaxy_dir }}/ansible_collections' + state: absent + loop: + - '{{ galaxy_dir }}/ansible_collections' + - '{{ venv_dest }}' diff --git a/test/integration/targets/ansible-galaxy-collection/vars/main.yml b/test/integration/targets/ansible-galaxy-collection/vars/main.yml index 604ff1ab6a..260f90e70f 100644 --- a/test/integration/targets/ansible-galaxy-collection/vars/main.yml +++ b/test/integration/targets/ansible-galaxy-collection/vars/main.yml @@ -2,6 +2,11 @@ galaxy_verbosity: "{{ '' if not ansible_verbosity else '-' ~ ('v' * ansible_verb gpg_homedir: "{{ galaxy_dir }}/gpg" +unsupported_resolvelib_versions: + - "0.2.0" # Fails on import + - "0.5.1" + - "0.6.0" # Fails on dependency resolution + pulp_repositories: - published - secondary diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index 005475736a..959bfdf266 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -4,42 +4,6 @@ docs/docsite/rst/locales/ja/LC_MESSAGES/dev_guide.po no-smart-quotes # Translat examples/scripts/ConfigureRemotingForAnsible.ps1 pslint:PSCustomUseLiteralPath examples/scripts/upgrade_to_ps3.ps1 pslint:PSCustomUseLiteralPath examples/scripts/upgrade_to_ps3.ps1 pslint:PSUseApprovedVerbs -lib/ansible/cli/galaxy.py import-3.8 # unguarded indirect resolvelib import -lib/ansible/galaxy/collection/__init__.py import-3.8 # unguarded resolvelib import -lib/ansible/galaxy/collection/concrete_artifact_manager.py import-3.8 # unguarded resolvelib import -lib/ansible/galaxy/collection/galaxy_api_proxy.py import-3.8 # unguarded resolvelib imports -lib/ansible/galaxy/collection/gpg.py import-3.8 # unguarded resolvelib imports -lib/ansible/galaxy/dependency_resolution/__init__.py import-3.8 # circular imports -lib/ansible/galaxy/dependency_resolution/dataclasses.py import-3.8 # circular imports -lib/ansible/galaxy/dependency_resolution/errors.py import-3.8 # circular imports -lib/ansible/galaxy/dependency_resolution/providers.py import-3.8 # circular imports -lib/ansible/galaxy/dependency_resolution/reporters.py import-3.8 # circular imports -lib/ansible/galaxy/dependency_resolution/resolvers.py import-3.8 # circular imports -lib/ansible/galaxy/dependency_resolution/versioning.py import-3.8 # circular imports -lib/ansible/cli/galaxy.py import-3.9 # unguarded indirect resolvelib import -lib/ansible/galaxy/collection/__init__.py import-3.9 # unguarded resolvelib import -lib/ansible/galaxy/collection/concrete_artifact_manager.py import-3.9 # unguarded resolvelib import -lib/ansible/galaxy/collection/galaxy_api_proxy.py import-3.9 # unguarded resolvelib imports -lib/ansible/galaxy/collection/gpg.py import-3.9 # unguarded resolvelib imports -lib/ansible/galaxy/dependency_resolution/__init__.py import-3.9 # circular imports -lib/ansible/galaxy/dependency_resolution/dataclasses.py import-3.9 # circular imports -lib/ansible/galaxy/dependency_resolution/errors.py import-3.9 # circular imports -lib/ansible/galaxy/dependency_resolution/providers.py import-3.9 # circular imports -lib/ansible/galaxy/dependency_resolution/reporters.py import-3.9 # circular imports -lib/ansible/galaxy/dependency_resolution/resolvers.py import-3.9 # circular imports -lib/ansible/galaxy/dependency_resolution/versioning.py import-3.9 # circular imports -lib/ansible/cli/galaxy.py import-3.10 # unguarded indirect resolvelib import -lib/ansible/galaxy/collection/__init__.py import-3.10 # unguarded resolvelib import -lib/ansible/galaxy/collection/concrete_artifact_manager.py import-3.10 # unguarded resolvelib import -lib/ansible/galaxy/collection/galaxy_api_proxy.py import-3.10 # unguarded resolvelib imports -lib/ansible/galaxy/collection/gpg.py import-3.10 # unguarded resolvelib imports -lib/ansible/galaxy/dependency_resolution/__init__.py import-3.10 # circular imports -lib/ansible/galaxy/dependency_resolution/dataclasses.py import-3.10 # circular imports -lib/ansible/galaxy/dependency_resolution/errors.py import-3.10 # circular imports -lib/ansible/galaxy/dependency_resolution/providers.py import-3.10 # circular imports -lib/ansible/galaxy/dependency_resolution/reporters.py import-3.10 # circular imports -lib/ansible/galaxy/dependency_resolution/resolvers.py import-3.10 # circular imports -lib/ansible/galaxy/dependency_resolution/versioning.py import-3.10 # circular imports lib/ansible/cli/scripts/ansible_connection_cli_stub.py shebang lib/ansible/config/base.yml no-unwanted-files lib/ansible/executor/playbook_executor.py pylint:disallowed-name |