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>
pull/77906/head
Sloane Hertel 2 years ago committed by GitHub
parent 621e782ed0
commit 82f3a57bee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,2 @@
bugfixes:
- ansible-galaxy - handle unsupported versions of resolvelib gracefully.

@ -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, PackageNotFoundError
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
@ -1566,6 +1588,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 PackageNotFoundError:
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 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,

@ -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

@ -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):

@ -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):

@ -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):

@ -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:

@ -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 }}'

@ -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

@ -4,54 +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/galaxy.py import-3.11 # unguarded indirect resolvelib import
lib/ansible/galaxy/collection/__init__.py import-3.11 # unguarded resolvelib import
lib/ansible/galaxy/collection/concrete_artifact_manager.py import-3.11 # unguarded resolvelib import
lib/ansible/galaxy/collection/galaxy_api_proxy.py import-3.11 # unguarded resolvelib imports
lib/ansible/galaxy/collection/gpg.py import-3.11 # unguarded resolvelib imports
lib/ansible/galaxy/dependency_resolution/__init__.py import-3.11 # circular imports
lib/ansible/galaxy/dependency_resolution/dataclasses.py import-3.11 # circular imports
lib/ansible/galaxy/dependency_resolution/errors.py import-3.11 # circular imports
lib/ansible/galaxy/dependency_resolution/providers.py import-3.11 # circular imports
lib/ansible/galaxy/dependency_resolution/reporters.py import-3.11 # circular imports
lib/ansible/galaxy/dependency_resolution/resolvers.py import-3.11 # circular imports
lib/ansible/galaxy/dependency_resolution/versioning.py import-3.11 # 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

Loading…
Cancel
Save