feat: route SBOM writes to Nexus behind flag
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 24s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a028f0-a42f-7582-89a8-ebaad7343834
This commit is contained in:
tegwick 2026-08-22 19:55:08 +02:00
parent d01ae3971d
commit b75234a533
7 changed files with 111 additions and 4 deletions

View file

@ -21,6 +21,7 @@ class Settings(BaseSettings):
ops_run_sla_hours: float = 1.0
sbom_nexus_url: str | None = None
sbom_nexus_read_mode: Literal["legacy", "nexus"] = "legacy"
sbom_nexus_write_mode: Literal["legacy", "nexus"] = "legacy"
sbom_nexus_timeout_seconds: float = 5.0

View file

@ -20,7 +20,13 @@ from api.schemas.sbom import (
SBOMSnapshotRead,
)
from api.services.legacy_meter import identity_from_request, record_legacy_usage
from api.services.sbom_nexus import SBOMNexusError, get_json, reads_from_nexus
from api.services.sbom_nexus import (
SBOMNexusError,
get_json,
post_json,
reads_from_nexus,
writes_to_nexus,
)
router = APIRouter(prefix="/sbom", tags=["sbom"])
logger = logging.getLogger(__name__)
@ -63,6 +69,29 @@ async def ingest_sbom(
) -> dict:
"""Create a new SBOM snapshot for a repo. Previous snapshots are retained."""
repo = await _get_repo_by_slug(body.repo_slug, session)
if writes_to_nexus():
payload = await _nexus_post("/sbom/ingest/", body=body.model_dump(mode="json"))
try:
snapshot_at = datetime.fromisoformat(
payload["snapshot_at"].replace("Z", "+00:00")
)
result = {
"repo_slug": payload["repo_slug"],
"snapshot_id": payload["snapshot_id"],
"ingested": payload["ingested"],
"snapshot_at": payload["snapshot_at"],
}
except (AttributeError, KeyError, TypeError, ValueError) as exc:
raise HTTPException(
status_code=502,
detail="SBOM Nexus returned an invalid ingest response",
) from exc
repo.last_sbom_at = snapshot_at
repo.sbom_source = "sbom-nexus"
await session.commit()
await _meter_compat(session, request, "POST", "/sbom/ingest/")
return result
now = datetime.now(tz=timezone.utc)
snap = SBOMSnapshot(
@ -315,6 +344,13 @@ async def _nexus_get(path: str, *, params: dict | None = None):
raise HTTPException(status_code=exc.status_code, detail=exc.detail) from exc
async def _nexus_post(path: str, *, body: dict):
try:
return await post_json(path, body=body)
except SBOMNexusError as exc:
raise HTTPException(status_code=exc.status_code, detail=exc.detail) from exc
async def _local_repo_ids(items: list[dict], session: AsyncSession) -> dict[str, uuid.UUID]:
slugs = {item.get("repo_slug") for item in items}
if None in slugs:

View file

@ -21,12 +21,18 @@ def reads_from_nexus() -> bool:
return settings.sbom_nexus_read_mode == "nexus"
async def get_json(
def writes_to_nexus() -> bool:
return settings.sbom_nexus_write_mode == "nexus"
async def request_json(
method: str,
path: str,
*,
params: dict[str, Any] | None = None,
body: dict[str, Any] | None = None,
) -> Any:
"""Fetch one Nexus resource; never fall back silently to legacy storage."""
"""Call Nexus; never fall back silently to legacy storage."""
if not settings.sbom_nexus_url:
raise SBOMNexusError(503, "SBOM Nexus read mode is enabled without SBOM_NEXUS_URL")
@ -35,7 +41,7 @@ async def get_json(
base_url=settings.sbom_nexus_url.rstrip("/"),
timeout=settings.sbom_nexus_timeout_seconds,
) as client:
response = await client.get(path, params=params)
response = await client.request(method, path, params=params, json=body)
except httpx.RequestError as exc:
raise SBOMNexusError(502, f"SBOM Nexus is unavailable: {exc.__class__.__name__}") from exc
@ -54,3 +60,11 @@ async def get_json(
return response.json()
except ValueError as exc:
raise SBOMNexusError(502, "SBOM Nexus returned invalid JSON") from exc
async def get_json(path: str, *, params: dict[str, Any] | None = None) -> Any:
return await request_json("GET", path, params=params)
async def post_json(path: str, *, body: dict[str, Any]) -> Any:
return await request_json("POST", path, body=body)