Implement bounded SBOM Nexus catch-up
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a028f0-a42f-7582-89a8-ebaad7343834
This commit is contained in:
parent
192f74f678
commit
8e8c74bd4c
3 changed files with 221 additions and 20 deletions
|
|
@ -36,9 +36,10 @@ Until CUST-WP-0062-T03 lands there is no live endpoint — the query is exercise
|
|||
against a test double (``tests/test_sbom_nexus_context_resolver.py``) and the
|
||||
daily definition stays ``enabled: false``.
|
||||
|
||||
This adapter is read-only. Ingest of the selected repos is a declared bounded
|
||||
side-effect owned by ACTIVITY-WP-0030-T02 and is deliberately not implemented
|
||||
here.
|
||||
With ``params.apply: true`` the adapter performs the declared T02 side-effect:
|
||||
each selected repository receives exactly one terminal ingest or skip outcome.
|
||||
The ranked response is truncated before any write, so the number of processed
|
||||
repositories can never exceed ``limit``. The default remains read-only.
|
||||
|
||||
Config: SBOM_NEXUS_URL env var (default: http://127.0.0.1:8010).
|
||||
"""
|
||||
|
|
@ -47,6 +48,7 @@ from __future__ import annotations
|
|||
|
||||
import os
|
||||
from typing import Any
|
||||
from urllib.parse import quote
|
||||
|
||||
import httpx
|
||||
|
||||
|
|
@ -84,6 +86,14 @@ def _fetch_json(path: str, params: dict[str, Any] | None = None) -> Any:
|
|||
return response.json()
|
||||
|
||||
|
||||
def _post_json(path: str, payload: dict[str, Any] | None = None) -> Any:
|
||||
url = f"{_base_url()}{path}"
|
||||
with httpx.Client(timeout=_TIMEOUT_SECONDS) as client:
|
||||
response = client.post(url, json=payload)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def _int_or(value: Any, default: int) -> int:
|
||||
try:
|
||||
return int(value)
|
||||
|
|
@ -147,7 +157,7 @@ def _catch_up(params: dict[str, Any]) -> dict[str, Any]:
|
|||
never_count = _int_or(payload.get("never_count"), 0)
|
||||
stale_count = _int_or(payload.get("stale_count"), len(repos))
|
||||
|
||||
return {
|
||||
result = {
|
||||
"repos": repos,
|
||||
"selected_count": len(repos),
|
||||
"stale_count": stale_count,
|
||||
|
|
@ -155,6 +165,68 @@ def _catch_up(params: dict[str, Any]) -> dict[str, Any]:
|
|||
"total_count": total_count,
|
||||
"limit": limit,
|
||||
}
|
||||
if params.get("apply") is True:
|
||||
result.update(_apply_bounded_ingest(repos))
|
||||
return result
|
||||
|
||||
|
||||
def _skip(repo_slug: str, reason: str, detail: str | None = None) -> dict[str, Any]:
|
||||
payload: dict[str, Any] = {"reason": reason}
|
||||
if detail:
|
||||
payload["detail"] = detail[:300]
|
||||
raw = _post_json(f"/sbom/{quote(repo_slug, safe='')}/skip", payload)
|
||||
if not isinstance(raw, dict) or raw.get("status") != "skipped":
|
||||
raise RuntimeError(f"sbom-nexus skip returned an invalid outcome for {repo_slug}")
|
||||
return raw
|
||||
|
||||
|
||||
def _ingest(repo_slug: str) -> dict[str, Any]:
|
||||
raw = _post_json(f"/sbom/{quote(repo_slug, safe='')}/ingest")
|
||||
if not isinstance(raw, dict) or raw.get("status") not in {"ingested", "skipped"}:
|
||||
return _skip(repo_slug, "ingest-error", "invalid ingest outcome")
|
||||
return raw
|
||||
|
||||
|
||||
def _apply_bounded_ingest(repos: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
updated: list[dict[str, Any]] = []
|
||||
skipped: list[dict[str, Any]] = []
|
||||
|
||||
for repo in repos:
|
||||
repo_slug = str(repo["repo_slug"])
|
||||
try:
|
||||
if repo.get("checkout_available") is False:
|
||||
outcome = _skip(repo_slug, "no-checkout")
|
||||
else:
|
||||
outcome = _ingest(repo_slug)
|
||||
except Exception as exc:
|
||||
# A transport or contract failure still needs a terminal Nexus
|
||||
# outcome so the same impossible repository cannot pin the queue.
|
||||
outcome = _skip(repo_slug, "ingest-error", type(exc).__name__)
|
||||
|
||||
compact = {
|
||||
key: outcome.get(key)
|
||||
for key in (
|
||||
"repo_slug",
|
||||
"status",
|
||||
"reason",
|
||||
"snapshot_id",
|
||||
"entry_count",
|
||||
"snapshot_at",
|
||||
"source_revision",
|
||||
)
|
||||
if outcome.get(key) is not None
|
||||
}
|
||||
compact.setdefault("repo_slug", repo_slug)
|
||||
if outcome.get("status") == "ingested":
|
||||
updated.append(compact)
|
||||
else:
|
||||
skipped.append(compact)
|
||||
|
||||
return {
|
||||
"attempted_count": len(repos),
|
||||
"updated": updated,
|
||||
"skipped": skipped,
|
||||
}
|
||||
|
||||
|
||||
class SbomNexusContextResolver(ContextResolver):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue