This is an automated email from the ASF dual-hosted git repository.
arm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/main by this push:
new 71d78ad Add support for Maven staging (repository.a.o) - using 4443
Nexus 3 new version
71d78ad is described below
commit 71d78add2ebb70e9b44ae11299ef5618e3a1ead5
Author: Alastair McFarlane <[email protected]>
AuthorDate: Thu Jan 15 12:09:33 2026 +0000
Add support for Maven staging (repository.a.o) - using 4443 Nexus 3 new
version
---
atr/models/sql.py | 1 +
atr/storage/writers/distributions.py | 92 ++++++++++++++++++++++++++++++++----
2 files changed, 85 insertions(+), 8 deletions(-)
diff --git a/atr/models/sql.py b/atr/models/sql.py
index 296c65d..2a58dde 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -123,6 +123,7 @@ class DistributionPlatform(enum.Enum):
gh_slug="maven",
template_url="https://search.maven.org/solrsearch/select?q=g:{owner_namespace}+AND+a:{package}+AND+v:{version}&core=gav&rows=20&wt=json",
# Java ASF projects use staging URLs along the lines of
+
template_staging_url="https://repository.apache.org:4443/repository/maven-staging/{owner_namespace}/{package}/maven-metadata.xml",
#
https://repository.apache.org/content/repositories/orgapachePROJECT-NNNN/
# There's no JSON, but each individual package has maven-metadata.xml
requires_owner_namespace=True,
diff --git a/atr/storage/writers/distributions.py
b/atr/storage/writers/distributions.py
index 9179eed..4569b1c 100644
--- a/atr/storage/writers/distributions.py
+++ b/atr/storage/writers/distributions.py
@@ -197,12 +197,22 @@ class CommitteeMember(CommitteeParticipant):
dd: distribution.Data,
) -> tuple[sql.Distribution, bool, distribution.Metadata]:
template_url = await self.__template_url(dd, staging)
- api_url = template_url.format(
- owner_namespace=dd.owner_namespace,
- package=dd.package,
- version=dd.version,
- )
- api_oc = await self.__json_from_distribution_platform(api_url,
dd.platform, dd.version)
+
+ if dd.platform == sql.DistributionPlatform.MAVEN and staging:
+ owner = (dd.owner_namespace or "").replace(".", "/")
+ api_url = template_url.format(
+ owner_namespace=owner,
+ package=dd.package,
+ version=dd.version,
+ )
+ api_oc = await self.__json_from_maven_xml(api_url, dd.version)
+ else:
+ api_url = template_url.format(
+ owner_namespace=dd.owner_namespace,
+ package=dd.package,
+ version=dd.version,
+ )
+ api_oc = await self.__json_from_distribution_platform(api_url,
dd.platform, dd.version)
match api_oc:
case outcome.Result(result):
pass
@@ -337,6 +347,68 @@ class CommitteeMember(CommitteeParticipant):
return outcome.Error(e)
return outcome.Result(result)
+ async def __json_from_maven_xml(self, api_url: str, version: str) ->
outcome.Outcome[basic.JSON]:
+ import datetime
+ import xml.etree.ElementTree as ET
+
+ try:
+ async with aiohttp.ClientSession() as session:
+ async with session.get(api_url) as response:
+ response.raise_for_status()
+ xml_text = await response.text()
+
+ # Parse the XML
+ root = ET.fromstring(xml_text)
+
+ # Extract versioning info
+ group = root.find("groupId")
+ artifact = root.find("artifactId")
+ versioning = root.find("versioning")
+ if versioning is None:
+ e = RuntimeError("No versioning element found in Maven
metadata")
+ return outcome.Error(e)
+
+ # Get lastUpdated timestamp (format: yyyyMMddHHmmss)
+ last_updated_elem = versioning.find("lastUpdated")
+ if last_updated_elem is None or not last_updated_elem.text:
+ e = RuntimeError("No lastUpdated timestamp found in Maven
metadata")
+ return outcome.Error(e)
+
+ # Convert lastUpdated string to Unix timestamp in milliseconds
+ last_updated_str = last_updated_elem.text
+ dt = datetime.datetime.strptime(last_updated_str, "%Y%m%d%H%M%S")
+ dt = dt.replace(tzinfo=datetime.UTC)
+ timestamp_ms = int(dt.timestamp() * 1000)
+
+ # Verify the version exists
+ versions_elem = versioning.find("versions")
+ if versions_elem is not None:
+ versions = [v.text for v in versions_elem.findall("version")
if v.text]
+ if version not in versions:
+ e = RuntimeError(f"Version '{version}' not found in Maven
metadata")
+ return outcome.Error(e)
+
+ # Convert to dict matching MavenResponse structure
+ result_dict = {
+ "response": {
+ "start": 0,
+ "docs": [
+ {
+ "g": group.text if group is not None else "",
+ "a": artifact.text if artifact is not None else "",
+ "v": version,
+ "timestamp": timestamp_ms,
+ }
+ ],
+ }
+ }
+ result = basic.as_json(result_dict)
+ return outcome.Result(result)
+ except aiohttp.ClientError as e:
+ return outcome.Error(e)
+ except ET.ParseError as e:
+ return outcome.Error(RuntimeError(f"Failed to parse Maven XML:
{e}"))
+
async def __template_url(
self,
dd: distribution.Data,
@@ -345,9 +417,13 @@ class CommitteeMember(CommitteeParticipant):
if staging is False:
return dd.platform.value.template_url
- supported = {sql.DistributionPlatform.ARTIFACT_HUB,
sql.DistributionPlatform.PYPI}
+ supported = {
+ sql.DistributionPlatform.ARTIFACT_HUB,
+ sql.DistributionPlatform.PYPI,
+ sql.DistributionPlatform.MAVEN,
+ }
if dd.platform not in supported:
- raise storage.AccessError("Staging is currently supported only for
ArtifactHub and PyPI.")
+ raise storage.AccessError("Staging is currently supported only for
ArtifactHub, PyPI and Maven Central.")
template_url = dd.platform.value.template_staging_url
if template_url is None:
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]