Fix using the server's validate_certs config when downloading (#86705)

* Fix using the server's validate_certs configuration when downloading collections

* Fix validate_certs for verify

There is no GalaxyAPI on the collection object for verify since it wasn't created via the resolver

Remove unit test - would need more convoluted monkeypatching

* Simplify ConcreteArtifactsManager.save_collection_source by passing 2 arguments instead of 6

* Consolidate ConcreteArtifactsManager instance attrs _galaxy_collection_cache/_galaxy_collection_origin_cache
This commit is contained in:
Sloane Hertel
2026-03-25 11:20:34 -05:00
committed by GitHub
parent 03c851d681
commit a51536f311
4 changed files with 33 additions and 50 deletions
@@ -0,0 +1,4 @@
bugfixes:
- >-
ansible-galaxy collection - Fix using the server configuration for ``validate_certs``
when downloading collections. (https://github.com/ansible/ansible/issues/86694)
+4 -2
View File
@@ -78,8 +78,10 @@ def with_collection_artifacts_manager(wrapped_method):
if 'artifacts_manager' in kwargs: if 'artifacts_manager' in kwargs:
return wrapped_method(*args, **kwargs) return wrapped_method(*args, **kwargs)
# FIXME: use validate_certs context from Galaxy servers when downloading collections # configures validate_certs for type 'url' only
# .get used here for when this is used in a non-CLI context # type 'galaxy' inherits and overrides resolved_validate_certs
# type 'git' recalculates resolved_validate_certs
# NOTE: .get used here for when this is used in a non-CLI context
artifacts_manager_kwargs = {'validate_certs': context.CLIARGS.get('resolved_validate_certs', True)} artifacts_manager_kwargs = {'validate_certs': context.CLIARGS.get('resolved_validate_certs', True)}
keyring = context.CLIARGS.get('keyring', None) keyring = context.CLIARGS.get('keyring', None)
@@ -31,7 +31,7 @@ if t.TYPE_CHECKING:
from ansible import context from ansible import context
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.galaxy import get_collections_galaxy_meta_info from ansible.galaxy import get_collections_galaxy_meta_info
from ansible.galaxy.api import should_retry_error from ansible.galaxy.api import should_retry_error, CollectionVersionMetadata, GalaxyAPI
from ansible.galaxy.dependency_resolution.dataclasses import _GALAXY_YAML from ansible.galaxy.dependency_resolution.dataclasses import _GALAXY_YAML
from ansible.galaxy.user_agent import user_agent from ansible.galaxy.user_agent import user_agent
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
@@ -71,8 +71,7 @@ class ConcreteArtifactsManager:
self._artifact_cache = {} # type: dict[bytes, bytes] self._artifact_cache = {} # type: dict[bytes, bytes]
self._galaxy_artifact_cache = {} # type: dict[Candidate | Requirement, bytes] self._galaxy_artifact_cache = {} # type: dict[Candidate | Requirement, bytes]
self._artifact_meta_cache = {} # type: dict[bytes, dict[str, str | list[str] | dict[str, str] | None | t.Type[Sentinel]]] self._artifact_meta_cache = {} # type: dict[bytes, dict[str, str | list[str] | dict[str, str] | None | t.Type[Sentinel]]]
self._galaxy_collection_cache = {} # type: dict[Candidate | Requirement, tuple[str, str, GalaxyToken]] self._galaxy_collection_cache: dict[Candidate, tuple[CollectionVersionMetadata, GalaxyAPI]] = {}
self._galaxy_collection_origin_cache = {} # type: dict[Candidate, tuple[str, list[dict[str, str]]]]
self._b_working_directory = b_working_directory # type: bytes self._b_working_directory = b_working_directory # type: bytes
self._supplemental_signature_cache = {} # type: dict[str, str] self._supplemental_signature_cache = {} # type: dict[str, str]
self._keyring = keyring # type: str self._keyring = keyring # type: str
@@ -107,15 +106,12 @@ class ConcreteArtifactsManager:
def get_galaxy_artifact_source_info(self, collection): def get_galaxy_artifact_source_info(self, collection):
# type: (Candidate) -> dict[str, t.Union[str, list[dict[str, str]]]] # type: (Candidate) -> dict[str, t.Union[str, list[dict[str, str]]]]
server = collection.src.api_server
try: try:
download_url = self._galaxy_collection_cache[collection][0] metadata, server = self._galaxy_collection_cache[collection]
signatures_url, signatures = self._galaxy_collection_origin_cache[collection]
except KeyError as key_err: except KeyError as key_err:
raise RuntimeError( raise RuntimeError(
'The is no known source for {coll!s}'. f"There is no known source for {collection!s}"
format(coll=collection),
) from key_err ) from key_err
return { return {
@@ -123,14 +119,13 @@ class ConcreteArtifactsManager:
"namespace": collection.namespace, "namespace": collection.namespace,
"name": collection.name, "name": collection.name,
"version": collection.ver, "version": collection.ver,
"server": server, "server": server.api_server,
"version_url": signatures_url, "version_url": metadata.signatures_url,
"download_url": download_url, "download_url": metadata.download_url,
"signatures": signatures, "signatures": metadata.signatures,
} }
def get_galaxy_artifact_path(self, collection): def get_galaxy_artifact_path(self, collection: Candidate) -> bytes:
# type: (t.Union[Candidate, Requirement]) -> bytes
"""Given a Galaxy-stored collection, return a cached path. """Given a Galaxy-stored collection, return a cached path.
If it's not yet on disk, this method downloads the artifact first. If it's not yet on disk, this method downloads the artifact first.
@@ -141,11 +136,10 @@ class ConcreteArtifactsManager:
pass pass
try: try:
url, sha256_hash, token = self._galaxy_collection_cache[collection] metadata, api = self._galaxy_collection_cache[collection]
except KeyError as key_err: except KeyError as key_err:
raise RuntimeError( raise RuntimeError(
'There is no known source for {coll!s}'. f'There is no known source for {collection!s}'
format(coll=collection),
) from key_err ) from key_err
display.vvvv( display.vvvv(
@@ -155,39 +149,27 @@ class ConcreteArtifactsManager:
try: try:
b_artifact_path = _download_file( b_artifact_path = _download_file(
url, metadata.download_url,
self._b_working_directory, self._b_working_directory,
expected_hash=sha256_hash, expected_hash=metadata.artifact_sha256,
validate_certs=self._validate_certs, validate_certs=api.validate_certs,
token=token, token=api.token,
) # type: bytes ) # type: bytes
except URLError as err: except URLError as err:
raise AnsibleError( raise AnsibleError(
'Failed to download collection tar ' 'Failed to download collection tar '
"from '{coll_src!s}': {download_err!s}". f"from '{api!s}': {err!s}"
format(
coll_src=to_native(collection.src),
download_err=to_native(err),
),
) from err ) from err
except Exception as err: except Exception as err:
raise AnsibleError( raise AnsibleError(
'Failed to download collection tar ' 'Failed to download collection tar '
"from '{coll_src!s}' due to the following unforeseen error: " f"from '{api!s}' due to the following unforeseen error: "
'{download_err!s}'. f'{err!s}'
format(
coll_src=to_native(collection.src),
download_err=to_native(err),
),
) from err ) from err
else: else:
display.vvv( display.vvv(
"Collection '{coll!s}' obtained from " f"Collection '{collection!s}' obtained from server {api!s} "
'server {server!s} {url!s}'.format( f"{api.api_server!s}"
coll=collection, server=collection.src or 'Galaxy',
url=collection.src.api_server if collection.src is not None
else '',
)
) )
self._galaxy_artifact_cache[collection] = b_artifact_path self._galaxy_artifact_cache[collection] = b_artifact_path
@@ -368,15 +350,13 @@ class ConcreteArtifactsManager:
# NOTE: Using None as a sentinel since it's not a valid value otherwise. # NOTE: Using None as a sentinel since it's not a valid value otherwise.
return runtime.get("requires_ansible") return runtime.get("requires_ansible")
def save_collection_source(self, collection, url, sha256_hash, token, signatures_url, signatures): def save_collection_source(self, collection: Candidate, metadata: CollectionVersionMetadata, api: GalaxyAPI) -> None:
# type: (Candidate, str, str, GalaxyToken, str, list[dict[str, str]]) -> None """Store collection version metadata and origin.
"""Store collection URL, SHA256 hash and Galaxy API token.
This is a hook that is supposed to be called before attempting to This is a hook that is supposed to be called before attempting to
download Galaxy-based collections with ``get_galaxy_artifact_path()``. download Galaxy-based collections with ``get_galaxy_artifact_path()``.
""" """
self._galaxy_collection_cache[collection] = url, sha256_hash, token self._galaxy_collection_cache[collection] = (metadata, api)
self._galaxy_collection_origin_cache[collection] = signatures_url, signatures
@classmethod @classmethod
@contextmanager @contextmanager
@@ -146,11 +146,8 @@ class MultiGalaxyAPIProxy:
else: else:
self._concrete_art_mgr.save_collection_source( self._concrete_art_mgr.save_collection_source(
collection_candidate, collection_candidate,
version_metadata.download_url, version_metadata,
version_metadata.artifact_sha256, api
api.token,
version_metadata.signatures_url,
version_metadata.signatures,
) )
return version_metadata return version_metadata