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:
return wrapped_method(*args, **kwargs)
# FIXME: use validate_certs context from Galaxy servers when downloading collections
# .get used here for when this is used in a non-CLI context
# configures validate_certs for type 'url' only
# 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)}
keyring = context.CLIARGS.get('keyring', None)
@@ -31,7 +31,7 @@ if t.TYPE_CHECKING:
from ansible import context
from ansible.errors import AnsibleError
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.user_agent import user_agent
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._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._galaxy_collection_cache = {} # type: dict[Candidate | Requirement, tuple[str, str, GalaxyToken]]
self._galaxy_collection_origin_cache = {} # type: dict[Candidate, tuple[str, list[dict[str, str]]]]
self._galaxy_collection_cache: dict[Candidate, tuple[CollectionVersionMetadata, GalaxyAPI]] = {}
self._b_working_directory = b_working_directory # type: bytes
self._supplemental_signature_cache = {} # type: dict[str, str]
self._keyring = keyring # type: str
@@ -107,15 +106,12 @@ class ConcreteArtifactsManager:
def get_galaxy_artifact_source_info(self, collection):
# type: (Candidate) -> dict[str, t.Union[str, list[dict[str, str]]]]
server = collection.src.api_server
try:
download_url = self._galaxy_collection_cache[collection][0]
signatures_url, signatures = self._galaxy_collection_origin_cache[collection]
metadata, server = self._galaxy_collection_cache[collection]
except KeyError as key_err:
raise RuntimeError(
'The is no known source for {coll!s}'.
format(coll=collection),
f"There is no known source for {collection!s}"
) from key_err
return {
@@ -123,14 +119,13 @@ class ConcreteArtifactsManager:
"namespace": collection.namespace,
"name": collection.name,
"version": collection.ver,
"server": server,
"version_url": signatures_url,
"download_url": download_url,
"signatures": signatures,
"server": server.api_server,
"version_url": metadata.signatures_url,
"download_url": metadata.download_url,
"signatures": metadata.signatures,
}
def get_galaxy_artifact_path(self, collection):
# type: (t.Union[Candidate, Requirement]) -> bytes
def get_galaxy_artifact_path(self, collection: Candidate) -> bytes:
"""Given a Galaxy-stored collection, return a cached path.
If it's not yet on disk, this method downloads the artifact first.
@@ -141,11 +136,10 @@ class ConcreteArtifactsManager:
pass
try:
url, sha256_hash, token = self._galaxy_collection_cache[collection]
metadata, api = self._galaxy_collection_cache[collection]
except KeyError as key_err:
raise RuntimeError(
'There is no known source for {coll!s}'.
format(coll=collection),
f'There is no known source for {collection!s}'
) from key_err
display.vvvv(
@@ -155,39 +149,27 @@ class ConcreteArtifactsManager:
try:
b_artifact_path = _download_file(
url,
metadata.download_url,
self._b_working_directory,
expected_hash=sha256_hash,
validate_certs=self._validate_certs,
token=token,
expected_hash=metadata.artifact_sha256,
validate_certs=api.validate_certs,
token=api.token,
) # type: bytes
except URLError as err:
raise AnsibleError(
'Failed to download collection tar '
"from '{coll_src!s}': {download_err!s}".
format(
coll_src=to_native(collection.src),
download_err=to_native(err),
),
f"from '{api!s}': {err!s}"
) from err
except Exception as err:
raise AnsibleError(
'Failed to download collection tar '
"from '{coll_src!s}' due to the following unforeseen error: "
'{download_err!s}'.
format(
coll_src=to_native(collection.src),
download_err=to_native(err),
),
f"from '{api!s}' due to the following unforeseen error: "
f'{err!s}'
) from err
else:
display.vvv(
"Collection '{coll!s}' obtained from "
'server {server!s} {url!s}'.format(
coll=collection, server=collection.src or 'Galaxy',
url=collection.src.api_server if collection.src is not None
else '',
)
f"Collection '{collection!s}' obtained from server {api!s} "
f"{api.api_server!s}"
)
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.
return runtime.get("requires_ansible")
def save_collection_source(self, collection, url, sha256_hash, token, signatures_url, signatures):
# type: (Candidate, str, str, GalaxyToken, str, list[dict[str, str]]) -> None
"""Store collection URL, SHA256 hash and Galaxy API token.
def save_collection_source(self, collection: Candidate, metadata: CollectionVersionMetadata, api: GalaxyAPI) -> None:
"""Store collection version metadata and origin.
This is a hook that is supposed to be called before attempting to
download Galaxy-based collections with ``get_galaxy_artifact_path()``.
"""
self._galaxy_collection_cache[collection] = url, sha256_hash, token
self._galaxy_collection_origin_cache[collection] = signatures_url, signatures
self._galaxy_collection_cache[collection] = (metadata, api)
@classmethod
@contextmanager
@@ -146,11 +146,8 @@ class MultiGalaxyAPIProxy:
else:
self._concrete_art_mgr.save_collection_source(
collection_candidate,
version_metadata.download_url,
version_metadata.artifact_sha256,
api.token,
version_metadata.signatures_url,
version_metadata.signatures,
version_metadata,
api
)
return version_metadata