From 6dd566b9bb8a6aaaecaade85f3575b9fbafc82b1 Mon Sep 17 00:00:00 2001 From: Sloane Hertel <19572925+s-hertel@users.noreply.github.com> Date: Thu, 27 Apr 2023 16:58:52 -0400 Subject: [ansible-galaxy] Fix installing signed collections (#80661) (#80665) * Fix installing signed collections by using the fqcn, version, source, and type as a unique identifier. Define __hash__ and __eq__ methods to handle Candidate/Requirement comparison excluding signatures which aren't fully populated until install time. * Remove PinnedCandidateRequests since it is redundant now. * Fix verifying against a signed remote when the keyring isn't configured (cherry picked from commit d5e2e7a0a8ca9017a091922648430374539f878b) --- ...648-fix-ansible-galaxy-cache-signatures-bug.yml | 3 ++ lib/ansible/galaxy/collection/__init__.py | 2 +- .../galaxy/dependency_resolution/dataclasses.py | 7 +++++ .../galaxy/dependency_resolution/providers.py | 32 +--------------------- 4 files changed, 12 insertions(+), 32 deletions(-) create mode 100644 changelogs/fragments/80648-fix-ansible-galaxy-cache-signatures-bug.yml diff --git a/changelogs/fragments/80648-fix-ansible-galaxy-cache-signatures-bug.yml b/changelogs/fragments/80648-fix-ansible-galaxy-cache-signatures-bug.yml new file mode 100644 index 0000000000..eda4eb62f9 --- /dev/null +++ b/changelogs/fragments/80648-fix-ansible-galaxy-cache-signatures-bug.yml @@ -0,0 +1,3 @@ +bugfixes: + - ansible-galaxy - fix installing signed collections (https://github.com/ansible/ansible/issues/80648). + - ansible-galaxy collection verify - fix verifying signed collections when the keyring is not configured. diff --git a/lib/ansible/galaxy/collection/__init__.py b/lib/ansible/galaxy/collection/__init__.py index 07a81eee32..feea9da2e2 100644 --- a/lib/ansible/galaxy/collection/__init__.py +++ b/lib/ansible/galaxy/collection/__init__.py @@ -916,7 +916,7 @@ def verify_collections( # NOTE: If there are no Galaxy server signatures, only user-provided signature URLs, # NOTE: those alone validate the MANIFEST.json and the remote collection is not downloaded. # NOTE: The remote MANIFEST.json is only used in verification if there are no signatures. - if not signatures and not collection.signature_sources: + if artifacts_manager.keyring is None or not signatures: api_proxy.get_collection_version_metadata( remote_collection, ) diff --git a/lib/ansible/galaxy/dependency_resolution/dataclasses.py b/lib/ansible/galaxy/dependency_resolution/dataclasses.py index 0a3f8429a4..46b4e2fffc 100644 --- a/lib/ansible/galaxy/dependency_resolution/dataclasses.py +++ b/lib/ansible/galaxy/dependency_resolution/dataclasses.py @@ -167,6 +167,7 @@ def _is_concrete_artifact_pointer(tested_str): class _ComputedReqKindsMixin: + UNIQUE_ATTRS = ('fqcn', 'ver', 'src', 'type') def __init__(self, *args, **kwargs): if not self.may_have_offline_galaxy_info: @@ -181,6 +182,12 @@ class _ComputedReqKindsMixin: self.ver ) + def __hash__(self): + return hash(tuple(getattr(self, attr) for attr in _ComputedReqKindsMixin.UNIQUE_ATTRS)) + + def __eq__(self, candidate): + return hash(self) == hash(candidate) + @classmethod def from_dir_path_as_unknown( # type: ignore[misc] cls, # type: t.Type[Collection] diff --git a/lib/ansible/galaxy/dependency_resolution/providers.py b/lib/ansible/galaxy/dependency_resolution/providers.py index 93f0e7a00b..19fb2acae5 100644 --- a/lib/ansible/galaxy/dependency_resolution/providers.py +++ b/lib/ansible/galaxy/dependency_resolution/providers.py @@ -28,8 +28,6 @@ from ansible.galaxy.dependency_resolution.versioning import ( from ansible.module_utils.six import string_types from ansible.utils.version import SemanticVersion, LooseVersion -from collections.abc import Set - try: from resolvelib import AbstractProvider from resolvelib import __version__ as resolvelib_version @@ -46,34 +44,6 @@ RESOLVELIB_UPPERBOUND = SemanticVersion("1.1.0") RESOLVELIB_VERSION = SemanticVersion.from_loose_version(LooseVersion(resolvelib_version)) -class PinnedCandidateRequests(Set): - """Custom set class to store Candidate objects. Excludes the 'signatures' attribute when determining if a Candidate instance is in the set.""" - CANDIDATE_ATTRS = ('fqcn', 'ver', 'src', 'type') - - def __init__(self, candidates): - self._candidates = set(candidates) - - def __iter__(self): - return iter(self._candidates) - - def __contains__(self, value): - if not isinstance(value, Candidate): - raise ValueError(f"Expected a Candidate object but got {value!r}") - for candidate in self._candidates: - # Compare Candidate attributes excluding "signatures" since it is - # unrelated to whether or not a matching Candidate is user-requested. - # Candidate objects in the set are not expected to have signatures. - for attr in PinnedCandidateRequests.CANDIDATE_ATTRS: - if getattr(value, attr) != getattr(candidate, attr): - break - else: - return True - return False - - def __len__(self): - return len(self._candidates) - - class CollectionDependencyProviderBase(AbstractProvider): """Delegate providing a requirement interface for the resolver.""" @@ -117,7 +87,7 @@ class CollectionDependencyProviderBase(AbstractProvider): Requirement.from_requirement_dict, art_mgr=concrete_artifacts_manager, ) - self._pinned_candidate_requests = PinnedCandidateRequests( + self._pinned_candidate_requests = set( # NOTE: User-provided signatures are supplemental, so signatures # NOTE: are not used to determine if a candidate is user-requested Candidate(req.fqcn, req.ver, req.src, req.type, None) -- cgit v1.2.1