summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSloane Hertel <19572925+s-hertel@users.noreply.github.com>2022-06-09 11:13:28 -0400
committerGitHub <noreply@github.com>2022-06-09 10:13:28 -0500
commit977c2480dbc000574ccc0a9cb4c6ed1c1f5baede (patch)
tree1431b263a5a2886ca37e8be89f82092e276cb516
parent39579134856f1acb584586a86589a16161bfeeb6 (diff)
downloadansible-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>
-rw-r--r--changelogs/fragments/77630-ansible-galaxy-fix-unsupported-resolvelib-version.yml2
-rw-r--r--lib/ansible/galaxy/collection/__init__.py57
-rw-r--r--lib/ansible/galaxy/dependency_resolution/errors.py15
-rw-r--r--lib/ansible/galaxy/dependency_resolution/providers.py18
-rw-r--r--lib/ansible/galaxy/dependency_resolution/reporters.py6
-rw-r--r--lib/ansible/galaxy/dependency_resolution/resolvers.py6
-rw-r--r--test/integration/targets/ansible-galaxy-collection/tasks/main.yml6
-rw-r--r--test/integration/targets/ansible-galaxy-collection/tasks/unsupported_resolvelib.yml42
-rw-r--r--test/integration/targets/ansible-galaxy-collection/vars/main.yml5
-rw-r--r--test/sanity/ignore.txt36
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