You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by ar...@apache.org on 2026/06/10 14:47:30 UTC
(tooling-trusted-releases) branch arm updated: #1298 - add SBOM to artifact catalog and make e2e tests clean
This is an automated email from the ASF dual-hosted git repository.
alitheg pushed a commit to branch arm
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/arm by this push:
new a0ff9078 #1298 - add SBOM to artifact catalog and make e2e tests clean
a0ff9078 is described below
commit a0ff90785308c262b17d40ada954a8f0a2c88566
Author: Alastair McFarlane <ar...@apache.org>
AuthorDate: Wed Jun 10 15:47:08 2026 +0100
#1298 - add SBOM to artifact catalog and make e2e tests clean
---
atr/models/api.py | 6 ++++--
atr/models/sql.py | 2 ++
atr/shared/catalog.py | 5 +++++
atr/storage/writers/announce.py | 7 +++++++
atr/templates/_catalog_version.html | 1 +
migrations/versions/0094_2026.06.10_ee041532.py | 27 +++++++++++++++++++++++++
tests/run-e2e.sh | 18 ++++++++---------
tests/unit/test_catalog.py | 14 +++++++++++++
8 files changed, 68 insertions(+), 12 deletions(-)
diff --git a/atr/models/api.py b/atr/models/api.py
index c8b62bfa..fadcba80 100644
--- a/atr/models/api.py
+++ b/atr/models/api.py
@@ -39,14 +39,16 @@ class CatalogArtifact(schema.Strict):
classification: str | None
signature_path: str | None
checksum_path: str | None
+ sbom_path: str | None
key_fingerprint: str | None
svn_revision: int | None
downloadable: bool
- # Public download URLs: the artifact via the mirror network, its signature and
- # checksum from downloads.apache.org. None when the version is not downloadable.
+ # Public download URLs: the artifact via the mirror network, its signature,
+ # checksum and SBOM from downloads.apache.org. None when the version is not downloadable.
artifact_url: str | None
signature_url: str | None
checksum_url: str | None
+ sbom_url: str | None
class CatalogVersion(schema.Strict):
diff --git a/atr/models/sql.py b/atr/models/sql.py
index 37aa8fa3..d3eaca2e 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -1880,6 +1880,8 @@ class Artifact(sqlmodel.SQLModel, table=True):
signature_path: str | None = sqlmodel.Field(default=None, **example("apache-example-0.0.1.tar.gz.asc"))
# Path to the strongest available checksum file (SHA-512 preferred over SHA-256 over MD5)
checksum_path: str | None = sqlmodel.Field(default=None, **example("apache-example-0.0.1.tar.gz.sha512"))
+ # Path to the paired CycloneDX SBOM, if one rides alongside the artifact (.cdx.json preferred over .cdx.xml)
+ sbom_path: str | None = sqlmodel.Field(default=None, **example("apache-example-0.0.1.tar.gz.cdx.json"))
classification: str | None = sqlmodel.Field(default=None, **example("source"))
# Per-artifact SVN revision - not all artifacts in a release are added in the same commit
svn_revision: int | None = sqlmodel.Field(default=None, index=True, **example(12345))
diff --git a/atr/shared/catalog.py b/atr/shared/catalog.py
index 68647c6f..0d504a63 100644
--- a/atr/shared/catalog.py
+++ b/atr/shared/catalog.py
@@ -71,6 +71,7 @@ def _artifact(row: sql.Artifact, committee: sql.Committee | None, downloadable:
artifact_url: str | None = None
signature_url: str | None = None
checksum_url: str | None = None
+ sbom_url: str | None = None
if downloadable and (committee is not None):
artifact_url = util.public_download_url(
committee, suffix, util.DownloadFile.ARTIFACT, filename=row.artifact_path
@@ -83,17 +84,21 @@ def _artifact(row: sql.Artifact, committee: sql.Committee | None, downloadable:
checksum_url = util.public_download_url(
committee, suffix, util.DownloadFile.METADATA, filename=row.checksum_path
)
+ if row.sbom_path:
+ sbom_url = util.public_download_url(committee, suffix, util.DownloadFile.METADATA, filename=row.sbom_path)
return models.api.CatalogArtifact(
artifact_path=row.artifact_path,
classification=row.classification,
signature_path=row.signature_path,
checksum_path=row.checksum_path,
+ sbom_path=row.sbom_path,
key_fingerprint=row.key_fingerprint,
svn_revision=row.svn_revision,
downloadable=downloadable,
artifact_url=artifact_url,
signature_url=signature_url,
checksum_url=checksum_url,
+ sbom_url=sbom_url,
)
diff --git a/atr/storage/writers/announce.py b/atr/storage/writers/announce.py
index a7bab189..277168ce 100644
--- a/atr/storage/writers/announce.py
+++ b/atr/storage/writers/announce.py
@@ -491,6 +491,12 @@ class CommitteeMember(CommitteeParticipant):
if candidate in rel_paths:
checksum_path = candidate
break
+ sbom_path: str | None = None
+ for suffix in (".cdx.json", ".cdx.xml"):
+ candidate = f"{rel}{suffix}"
+ if candidate in rel_paths:
+ sbom_path = candidate
+ break
fingerprint = (
await self.__signature_fingerprint(release, parent_revision_number, signature_path)
if signature_path is not None
@@ -505,6 +511,7 @@ class CommitteeMember(CommitteeParticipant):
key_fingerprint=fingerprint,
signature_path=signature_path,
checksum_path=checksum_path,
+ sbom_path=sbom_path,
classification=classifications.get(rel),
svn_revision=svn_revision,
download_path_suffix=str(download_path_suffix) if download_path_suffix is not None else None,
diff --git a/atr/templates/_catalog_version.html b/atr/templates/_catalog_version.html
index c3e479cd..cb72b2c5 100644
--- a/atr/templates/_catalog_version.html
+++ b/atr/templates/_catalog_version.html
@@ -26,6 +26,7 @@
<a href="{{ artifact.artifact_url }}">Download</a>
{% if artifact.signature_url %}<a class="ms-2" href="{{ artifact.signature_url }}">.asc</a>{% endif %}
{% if artifact.checksum_url %}<a class="ms-2" href="{{ artifact.checksum_url }}">checksum</a>{% endif %}
+ {% if artifact.sbom_url %}<a class="ms-2" href="{{ artifact.sbom_url }}">SBOM</a>{% endif %}
{% else %}
<span class="text-secondary">Metadata only</span>
{% endif %}
diff --git a/migrations/versions/0094_2026.06.10_ee041532.py b/migrations/versions/0094_2026.06.10_ee041532.py
new file mode 100644
index 00000000..2cbf7c04
--- /dev/null
+++ b/migrations/versions/0094_2026.06.10_ee041532.py
@@ -0,0 +1,27 @@
+"""Add sbom_path to artifacts
+
+Revision ID: 0094_2026.06.10_ee041532
+Revises: 0093_2026.06.04_40cbca79
+Create Date: 2026-06-10 14:29:28.394235+00:00
+"""
+
+from collections.abc import Sequence
+
+import sqlalchemy as sa
+from alembic import op
+
+# Revision identifiers, used by Alembic
+revision: str = "0094_2026.06.10_ee041532"
+down_revision: str | None = "0093_2026.06.04_40cbca79"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+ with op.batch_alter_table("artifact", schema=None) as batch_op:
+ batch_op.add_column(sa.Column("sbom_path", sa.String(), nullable=True))
+
+
+def downgrade() -> None:
+ with op.batch_alter_table("artifact", schema=None) as batch_op:
+ batch_op.drop_column("sbom_path")
diff --git a/tests/run-e2e.sh b/tests/run-e2e.sh
index 048f302f..9524f425 100755
--- a/tests/run-e2e.sh
+++ b/tests/run-e2e.sh
@@ -5,18 +5,16 @@ cd "$(dirname "$0")"
echo "Running ATR e2e tests..."
+echo "Resetting the ATR dev container so each run starts from a clean database..."
+docker compose down atr-dev --volumes >/dev/null 2>&1 || true
+docker compose up atr-dev -d --build --wait
+
if ! docker compose ps atr-dev --status running -q 2>/dev/null | grep -q .
then
- echo "Starting ATR dev container..."
- docker compose up atr-dev -d --build --wait
-
- if ! docker compose ps atr-dev --status running -q 2>/dev/null | grep -q .
- then
- echo "ERROR: the atr-dev container failed to start or crashed during startup"
- echo "Container logs:"
- docker compose logs atr-dev --tail 100
- exit 1
- fi
+ echo "ERROR: the atr-dev container failed to start or crashed during startup"
+ echo "Container logs:"
+ docker compose logs atr-dev --tail 100
+ exit 1
fi
docker compose build e2e-dev
diff --git a/tests/unit/test_catalog.py b/tests/unit/test_catalog.py
index 3d73717f..4bcfb8d6 100644
--- a/tests/unit/test_catalog.py
+++ b/tests/unit/test_catalog.py
@@ -56,6 +56,7 @@ def _artifact(
*,
release: sql.Release | None = None,
svn_revision: int | None = None,
+ sbom_path: str | None = None,
) -> sql.Artifact:
return sql.Artifact(
project_key=project.key,
@@ -64,6 +65,7 @@ def _artifact(
release_key=(release.key if release is not None else None),
release=release,
svn_revision=svn_revision,
+ sbom_path=sbom_path,
)
@@ -131,6 +133,18 @@ def test_only_released_versions_are_downloadable() -> None:
assert versions["4.1.7"].svn_revision == 28114
+def test_paired_sbom_shown_on_catalog_artifact() -> None:
+ project = _project()
+ released = _release(project, "5.0.2", cycle_key="cassandra-default", released=_NOW)
+ artifacts = [
+ _artifact(project, "5.0.2", "a-5.0.2.tar.gz", release=released, sbom_path="a-5.0.2.tar.gz.cdx.json"),
+ ]
+
+ versions = {str(v.version): v for v in catalog._versions(artifacts, {}, project.committee_key)}
+
+ assert versions["5.0.2"].artifacts[0].sbom_path == "a-5.0.2.tar.gz.cdx.json"
+
+
def test_versions_are_newest_first() -> None:
project = _project()
older = _release(
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org