feat: add repository projection synchronization
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 1m9s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a028f0-a42f-7582-89a8-ebaad7343834
This commit is contained in:
tegwick 2026-08-22 16:52:47 +02:00
parent d3258a6ba0
commit 280da08455
7 changed files with 317 additions and 2 deletions

View file

@ -0,0 +1,102 @@
"""Synchronize the minimum repository projection used by SBOM Nexus."""
from __future__ import annotations
import socket
import urllib.parse
from collections import Counter
from typing import Any
from sbom_nexus.importer import request_json
def _checkout_path(repo: dict[str, Any], host_id: str | None) -> str | None:
host_paths = repo.get("host_paths") or {}
selected_host = host_id or socket.gethostname()
return host_paths.get(selected_host) or repo.get("local_path")
def sync_repository_projections(
source_url: str,
target_url: str,
*,
dry_run: bool,
host_id: str | None = None,
) -> dict[str, Any]:
"""Upsert repository identity/path projections without scanning or ingesting."""
repositories = request_json(source_url, "/repos/")
projections: dict[str, dict[str, Any]] = {}
statuses = Counter()
for repo in repositories:
slug = repo["slug"]
if slug in projections:
return {
"ok": False,
"dry_run": dry_run,
"source_repo_count": len(repositories),
"error": f"duplicate repository slug: {slug}",
}
active = repo.get("status", "active") == "active"
checkout_path = _checkout_path(repo, host_id)
projections[slug] = {
"slug": slug,
"active": active,
"checkout_path": checkout_path,
}
statuses["active" if active else "inactive"] += 1
statuses["with_checkout_path" if checkout_path else "without_checkout_path"] += 1
if dry_run:
return {
"ok": True,
"dry_run": True,
"host_id": host_id or socket.gethostname(),
"source_repo_count": len(projections),
"counts": dict(statuses),
"would_upsert": len(projections),
"reconciliation": None,
}
for slug, projection in sorted(projections.items()):
request_json(
target_url,
f"/repositories/{urllib.parse.quote(slug, safe='')}",
method="PUT",
body={
"active": projection["active"],
"checkout_path": projection["checkout_path"],
},
)
target_repositories = {
repo["slug"]: repo for repo in request_json(target_url, "/repositories/")
}
missing = sorted(set(projections) - set(target_repositories))
mismatched = []
for slug in sorted(set(projections) & set(target_repositories)):
expected = projections[slug]
actual = target_repositories[slug]
differences = {
field: {"source": expected[field], "target": actual.get(field)}
for field in ("active", "checkout_path")
if expected[field] != actual.get(field)
}
if differences:
mismatched.append({"repo_slug": slug, "differences": differences})
reconciliation = {
"ok": not missing and not mismatched,
"matched_repo_count": len(projections) - len(missing) - len(mismatched),
"missing_repo_slugs": missing,
"mismatched_repositories": mismatched,
"extra_target_repo_slugs": sorted(set(target_repositories) - set(projections)),
}
return {
"ok": reconciliation["ok"],
"dry_run": False,
"host_id": host_id or socket.gethostname(),
"source_repo_count": len(projections),
"counts": dict(statuses),
"upserted": len(projections),
"reconciliation": reconciliation,
}