From e24eb59de55c55a6da09757fc5947d9fd8936788 Mon Sep 17 00:00:00 2001 From: Sloane Hertel <19572925+s-hertel@users.noreply.github.com> Date: Tue, 10 Aug 2021 18:00:03 -0400 Subject: [PATCH] Improve ansible-galaxy error for InconsistentCandidate exception (#75235) * Improve error for InconsistentCandidate exceptions * Add test case for installing a collection with an inconsistent version * Add test case for installing a collection that has a dependency with an inconsistent version Co-authored-by: Abhijeet Kasurde Co-authored-by: Sviatoslav Sydorenko --- ...le-galaxy-inconsistent-candidate-error.yml | 2 + lib/ansible/galaxy/collection/__init__.py | 31 ++++++++ .../galaxy/dependency_resolution/errors.py | 1 + .../tasks/install.yml | 75 +++++++++++++++++++ 4 files changed, 109 insertions(+) create mode 100644 changelogs/fragments/75235-ansible-galaxy-inconsistent-candidate-error.yml diff --git a/changelogs/fragments/75235-ansible-galaxy-inconsistent-candidate-error.yml b/changelogs/fragments/75235-ansible-galaxy-inconsistent-candidate-error.yml new file mode 100644 index 00000000000..e56d524961a --- /dev/null +++ b/changelogs/fragments/75235-ansible-galaxy-inconsistent-candidate-error.yml @@ -0,0 +1,2 @@ +bugfixes: + - ansible-galaxy - Improve error message from dependency resolution when a candidate has inconsistent requirements (https://github.com/ansible/ansible/issues/75139). diff --git a/lib/ansible/galaxy/collection/__init__.py b/lib/ansible/galaxy/collection/__init__.py index 9af3068c8e8..3a38dba32c5 100644 --- a/lib/ansible/galaxy/collection/__init__.py +++ b/lib/ansible/galaxy/collection/__init__.py @@ -113,6 +113,7 @@ from ansible.galaxy.dependency_resolution.dataclasses import ( ) 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 @@ -1353,3 +1354,33 @@ def _resolve_depenency_map( AnsibleError('\n'.join(error_msg_lines)), dep_exc, ) + except CollectionDependencyInconsistentCandidate as dep_exc: + parents = [ + "%s.%s:%s" % (p.namespace, p.name, p.ver) + for p in dep_exc.criterion.iter_parent() + if p is not None + ] + + error_msg_lines = [ + ( + 'Failed to resolve the requested dependencies map. ' + 'Got the candidate {req.fqcn!s}:{req.ver!s} ({dep_origin!s}) ' + 'which didn\'t satisfy all of the following requirements:'. + format( + req=dep_exc.candidate, + dep_origin='direct request' + if not parents else 'dependency of {parent!s}'. + format(parent=', '.join(parents)) + ) + ) + ] + + for req in dep_exc.criterion.iter_requirement(): + error_msg_lines.append( + '* {req.fqcn!s}:{req.ver!s}'.format(req=req) + ) + + raise raise_from( # NOTE: Leading "raise" is a hack for mypy bug #9717 + AnsibleError('\n'.join(error_msg_lines)), + dep_exc, + ) diff --git a/lib/ansible/galaxy/dependency_resolution/errors.py b/lib/ansible/galaxy/dependency_resolution/errors.py index e57bd06e572..f5339a4d519 100644 --- a/lib/ansible/galaxy/dependency_resolution/errors.py +++ b/lib/ansible/galaxy/dependency_resolution/errors.py @@ -8,4 +8,5 @@ __metaclass__ = type from resolvelib.resolvers import ( ResolutionImpossible as CollectionDependencyResolutionImpossible, + InconsistentCandidate as CollectionDependencyInconsistentCandidate, ) diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/install.yml b/test/integration/targets/ansible-galaxy-collection/tasks/install.yml index 44c2fcd99fc..8b25039a7b6 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/install.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/install.yml @@ -163,6 +163,81 @@ - '"Installing ''namespace3.name:1.0.0'' to" in install_tarball.stdout' - (install_tarball_actual.content | b64decode | from_json).collection_info.version == '1.0.0' +- name: write a requirements file using the artifact and a conflicting version + copy: + content: | + collections: + - name: {{ galaxy_dir }}/namespace3.tar.gz + version: 1.2.0 + dest: '{{ galaxy_dir }}/test_req.yml' + +- name: install the requirements file with mismatched versions + command: ansible-galaxy collection install -r '{{ galaxy_dir }}/test_req.yml' {{ galaxy_verbosity }} + ignore_errors: True + register: result + +- name: remove the requirements file + file: + path: '{{ galaxy_dir }}/test_req.yml' + state: absent + +- assert: + that: error == expected_error + vars: + reset_color: '\x1b\[0m' + color: '\x1b\[[0-9];[0-9]{2}m' + error: "{{ result.stderr | regex_replace(reset_color) | regex_replace(color) | regex_replace('\\n', ' ') }}" + expected_error: >- + ERROR! Failed to resolve the requested dependencies map. + Got the candidate namespace3.name:1.0.0 (direct request) + which didn't satisfy all of the following requirements: + * namespace3.name:1.2.0 + +- name: test error for mismatched dependency versions + vars: + reset_color: '\x1b\[0m' + color: '\x1b\[[0-9];[0-9]{2}m' + error: "{{ result.stderr | regex_replace(reset_color) | regex_replace(color) | regex_replace('\\n', ' ') }}" + expected_error: >- + ERROR! Failed to resolve the requested dependencies map. + Got the candidate namespace3.name:1.0.0 (dependency of tmp_parent.name:1.0.0) + which didn't satisfy all of the following requirements: + * namespace3.name:1.2.0 + block: + - name: init a new parent collection + command: ansible-galaxy collection init tmp_parent.name --init-path '{{ galaxy_dir }}/scratch' + + - name: replace the dependencies + lineinfile: + path: "{{ galaxy_dir }}/scratch/tmp_parent/name/galaxy.yml" + regexp: "^dependencies:*" + line: "dependencies: { '{{ galaxy_dir }}/namespace3.tar.gz': '1.2.0' }" + + - name: build the new artifact + command: ansible-galaxy collection build {{ galaxy_dir }}/scratch/tmp_parent/name + args: + chdir: "{{ galaxy_dir }}" + + - name: install the artifact to verify the error is handled + command: ansible-galaxy collection install '{{ galaxy_dir }}/tmp_parent-name-1.0.0.tar.gz' + ignore_errors: yes + register: result + + - debug: msg="Actual - {{ error }}" + + - debug: msg="Expected - {{ expected_error }}" + + - assert: + that: error == expected_error + always: + - name: clean up collection skeleton and artifact + file: + state: absent + path: "{{ item }}" + loop: + - "{{ galaxy_dir }}/scratch/tmp_parent/" + - "{{ galaxy_dir }}/tmp_parent-name-1.0.0.tar.gz" + - name: setup bad tarball - {{ test_name }} script: build_bad_tar.py {{ galaxy_dir | quote }}