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