Try all galaxy servers when locating available versions for a collection (#75468) (#75750)

* If an exception occurs when getting a collection's metadata, continue to the next in the server list.

* Warn for unknown exceptions when finding versions of a collection

* Test that an invalid server is no longer fatal if a subsequent server has the collection

* Fix server for verify tests - compare checksums against the server from which it was installed

* Add tests for verify and fix that code path to mirror install/download behavior for server errors

Co-authored-by: Sviatoslav Sydorenko <wk.cvs.github@sydorenko.org.ua>
(cherry picked from commit 469b559ebe)
pull/75864/head
Sloane Hertel 3 years ago committed by GitHub
parent afbddce345
commit d9d5d2d93e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,9 @@
bugfixes:
- >-
ansible-galaxy - Fix handling HTTP exceptions from Galaxy servers.
Continue to the next server in the list until the collection is found.
minor_changes:
- >-
ansible-galaxy - Non-HTTP exceptions from Galaxy servers are now a warning and only fatal if
the collection to download|install|verify is not available from any of the servers
(https://github.com/ansible/ansible/issues/75443).

@ -24,6 +24,11 @@ if TYPE_CHECKING:
) )
from ansible.galaxy.api import GalaxyAPI, GalaxyError from ansible.galaxy.api import GalaxyAPI, GalaxyError
from ansible.module_utils._text import to_text
from ansible.utils.display import Display
display = Display()
class MultiGalaxyAPIProxy: class MultiGalaxyAPIProxy:
@ -35,6 +40,47 @@ class MultiGalaxyAPIProxy:
self._apis = apis self._apis = apis
self._concrete_art_mgr = concrete_artifacts_manager self._concrete_art_mgr = concrete_artifacts_manager
def _get_collection_versions(self, requirement):
# type: (Requirement, Iterator[GalaxyAPI]) -> Iterator[Tuple[GalaxyAPI, str]]
"""Helper for get_collection_versions.
Yield api, version pairs for all APIs,
and reraise the last error if no valid API was found.
"""
found_api = False
last_error = None
api_lookup_order = (
(requirement.src, )
if isinstance(requirement.src, GalaxyAPI)
else self._apis
)
for api in api_lookup_order:
try:
versions = api.get_collection_versions(requirement.namespace, requirement.name)
except GalaxyError as api_err:
last_error = api_err
except Exception as unknown_err:
display.warning(
"Skipping Galaxy server {server!s}. "
"Got an unexpected error when getting "
"available versions of collection {fqcn!s}: {err!s}".
format(
server=api.api_server,
fqcn=requirement.fqcn,
err=to_text(unknown_err),
)
)
last_error = unknown_err
else:
found_api = True
for version in versions:
yield api, version
if not found_api and last_error is not None:
raise last_error
def get_collection_versions(self, requirement): def get_collection_versions(self, requirement):
# type: (Requirement) -> Iterable[Tuple[str, GalaxyAPI]] # type: (Requirement) -> Iterable[Tuple[str, GalaxyAPI]]
"""Get a set of unique versions for FQCN on Galaxy servers.""" """Get a set of unique versions for FQCN on Galaxy servers."""
@ -54,9 +100,8 @@ class MultiGalaxyAPIProxy:
) )
return set( return set(
(version, api) (version, api)
for api in api_lookup_order for api, version in self._get_collection_versions(
for version in api.get_collection_versions( requirement,
requirement.namespace, requirement.name,
) )
) )
@ -78,6 +123,21 @@ class MultiGalaxyAPIProxy:
) )
except GalaxyError as api_err: except GalaxyError as api_err:
last_err = api_err last_err = api_err
except Exception as unknown_err:
# `verify` doesn't use `get_collection_versions` since the version is already known.
# Do the same as `install` and `download` by trying all APIs before failing.
# Warn for debugging purposes, since the Galaxy server may be unexpectedly down.
last_err = unknown_err
display.warning(
"Skipping Galaxy server {server!s}. "
"Got an unexpected error when getting "
"available versions of collection {fqcn!s}: {err!s}".
format(
server=api.api_server,
fqcn=collection_candidate.fqcn,
err=to_text(unknown_err),
)
)
else: else:
self._concrete_art_mgr.save_collection_source( self._concrete_art_mgr.save_collection_source(
collection_candidate, collection_candidate,

@ -4,6 +4,38 @@
path: '{{ galaxy_dir }}/ansible_collections' path: '{{ galaxy_dir }}/ansible_collections'
state: directory state: directory
- name: install simple collection from first accessible server
command: ansible-galaxy collection install namespace1.name1 {{ galaxy_verbosity }}
environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
register: from_first_good_server
- name: get installed files of install simple collection from first good server
find:
path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1'
file_type: file
register: install_normal_files
- name: get the manifest of install simple collection from first good server
slurp:
path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1/MANIFEST.json'
register: install_normal_manifest
- name: assert install simple collection from first good server
assert:
that:
- '"Installing ''namespace1.name1:1.0.9'' to" in from_first_good_server.stdout'
- install_normal_files.files | length == 3
- install_normal_files.files[0].path | basename in ['MANIFEST.json', 'FILES.json', 'README.md']
- install_normal_files.files[1].path | basename in ['MANIFEST.json', 'FILES.json', 'README.md']
- install_normal_files.files[2].path | basename in ['MANIFEST.json', 'FILES.json', 'README.md']
- (install_normal_manifest.content | b64decode | from_json).collection_info.version == '1.0.9'
- name: Remove the collection
file:
path: '{{ galaxy_dir }}/ansible_collections/namespace1'
state: absent
- name: install simple collection with implicit path - {{ test_name }} - name: install simple collection with implicit path - {{ test_name }}
command: ansible-galaxy collection install namespace1.name1 -s '{{ test_name }}' {{ galaxy_verbosity }} command: ansible-galaxy collection install namespace1.name1 -s '{{ test_name }}' {{ galaxy_verbosity }}
environment: environment:

@ -176,9 +176,10 @@
environment: environment:
ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg'
vars: vars:
test_api_fallback: 'pulp_v2'
test_api_fallback_versions: 'v1, v2'
test_name: 'galaxy_ng' test_name: 'galaxy_ng'
test_server: '{{ galaxy_ng_server }}' test_server: '{{ galaxy_ng_server }}'
vX: "v3/"
- name: run ansible-galaxy collection list tests - name: run ansible-galaxy collection list tests
include_tasks: list.yml include_tasks: list.yml

@ -25,12 +25,24 @@
"ERROR! 'file' type is not supported. The format namespace.name is expected." in verify.stderr "ERROR! 'file' type is not supported. The format namespace.name is expected." in verify.stderr
- name: install the collection from the server - name: install the collection from the server
command: ansible-galaxy collection install ansible_test.verify:1.0.0 command: ansible-galaxy collection install ansible_test.verify:1.0.0 -s {{ test_api_fallback }} {{ galaxy_verbosity }}
environment: environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}' ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}'
- name: verify the collection against the first valid server
command: ansible-galaxy collection verify ansible_test.verify:1.0.0 -vvv {{ galaxy_verbosity }}
environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}'
register: verify
- assert:
that:
- verify is success
- >-
"Found API version '{{ test_api_fallback_versions }}' with Galaxy server {{ test_api_fallback }}" in verify.stdout
- name: verify the installed collection against the server - name: verify the installed collection against the server
command: ansible-galaxy collection verify ansible_test.verify:1.0.0 command: ansible-galaxy collection verify ansible_test.verify:1.0.0 -s {{ test_name }} {{ galaxy_verbosity }}
environment: environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}' ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}'
register: verify register: verify
@ -41,12 +53,12 @@
- "'Collection ansible_test.verify contains modified content' not in verify.stdout" - "'Collection ansible_test.verify contains modified content' not in verify.stdout"
- name: verify the installed collection against the server, with unspecified version in CLI - name: verify the installed collection against the server, with unspecified version in CLI
command: ansible-galaxy collection verify ansible_test.verify command: ansible-galaxy collection verify ansible_test.verify -s {{ test_name }} {{ galaxy_verbosity }}
environment: environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}' ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}'
- name: verify a collection that doesn't appear to be installed - name: verify a collection that doesn't appear to be installed
command: ansible-galaxy collection verify ansible_test.verify:1.0.0 command: ansible-galaxy collection verify ansible_test.verify:1.0.0 -s {{ test_name }} {{ galaxy_verbosity }}
register: verify register: verify
failed_when: verify.rc == 0 failed_when: verify.rc == 0
@ -82,7 +94,7 @@
chdir: '{{ galaxy_dir }}' chdir: '{{ galaxy_dir }}'
- name: verify a version of a collection that isn't installed - name: verify a version of a collection that isn't installed
command: ansible-galaxy collection verify ansible_test.verify:2.0.0 command: ansible-galaxy collection verify ansible_test.verify:2.0.0 -s {{ test_name }} {{ galaxy_verbosity }}
environment: environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}' ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}'
register: verify register: verify
@ -94,12 +106,12 @@
- '"ansible_test.verify has the version ''1.0.0'' but is being compared to ''2.0.0''" in verify.stdout' - '"ansible_test.verify has the version ''1.0.0'' but is being compared to ''2.0.0''" in verify.stdout'
- name: install the new version from the server - name: install the new version from the server
command: ansible-galaxy collection install ansible_test.verify:2.0.0 --force command: ansible-galaxy collection install ansible_test.verify:2.0.0 --force -s {{ test_name }} {{ galaxy_verbosity }}
environment: environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}' ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}'
- name: verify the installed collection against the server - name: verify the installed collection against the server
command: ansible-galaxy collection verify ansible_test.verify:2.0.0 command: ansible-galaxy collection verify ansible_test.verify:2.0.0 -s {{ test_name }} {{ galaxy_verbosity }}
environment: environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}' ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}'
register: verify register: verify
@ -145,7 +157,7 @@
- "updated_file.stat.checksum != file.stat.checksum" - "updated_file.stat.checksum != file.stat.checksum"
- name: test verifying checksumes of the modified collection - name: test verifying checksumes of the modified collection
command: ansible-galaxy collection verify ansible_test.verify:2.0.0 command: ansible-galaxy collection verify ansible_test.verify:2.0.0 -s {{ test_name }} {{ galaxy_verbosity }}
register: verify register: verify
environment: environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}' ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}'
@ -165,7 +177,7 @@
diff: true diff: true
- name: ensure a modified FILES.json is validated - name: ensure a modified FILES.json is validated
command: ansible-galaxy collection verify ansible_test.verify:2.0.0 command: ansible-galaxy collection verify ansible_test.verify:2.0.0 -s {{ test_name }} {{ galaxy_verbosity }}
register: verify register: verify
environment: environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}' ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}'
@ -189,7 +201,7 @@
line: ' "chksum_sha256": "{{ manifest_info.stat.checksum }}",' line: ' "chksum_sha256": "{{ manifest_info.stat.checksum }}",'
- name: ensure the MANIFEST.json is validated against the uncorrupted file from the server - name: ensure the MANIFEST.json is validated against the uncorrupted file from the server
command: ansible-galaxy collection verify ansible_test.verify:2.0.0 command: ansible-galaxy collection verify ansible_test.verify:2.0.0 -s {{ test_name }} {{ galaxy_verbosity }}
register: verify register: verify
environment: environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}' ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}'
@ -219,7 +231,7 @@
dest: '{{ galaxy_dir }}/ansible_collections/ansible_test/verify/galaxy.yml' dest: '{{ galaxy_dir }}/ansible_collections/ansible_test/verify/galaxy.yml'
- name: test we only verify collections containing a MANIFEST.json with the version on the server - name: test we only verify collections containing a MANIFEST.json with the version on the server
command: ansible-galaxy collection verify ansible_test.verify:2.0.0 command: ansible-galaxy collection verify ansible_test.verify:2.0.0 -s {{ test_name }} {{ galaxy_verbosity }}
register: verify register: verify
environment: environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}' ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}'

@ -1,7 +1,10 @@
[galaxy] [galaxy]
# Ensures subsequent unstable reruns don't use the cached information causing another failure # Ensures subsequent unstable reruns don't use the cached information causing another failure
cache_dir={{ remote_tmp_dir }}/galaxy_cache cache_dir={{ remote_tmp_dir }}/galaxy_cache
server_list=pulp_v2,pulp_v3,galaxy_ng,secondary server_list=offline,pulp_v2,pulp_v3,galaxy_ng,secondary
[galaxy_server.offline]
url=https://test-hub.demolab.local/api/galaxy/content/api/
[galaxy_server.pulp_v2] [galaxy_server.pulp_v2]
url={{ pulp_server }}published/api/ url={{ pulp_server }}published/api/

Loading…
Cancel
Save