generated from coulomb/repo-seed
66 lines
2.0 KiB
Python
66 lines
2.0 KiB
Python
"""Artifact manifest helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import mimetypes
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from guide_board.schema import assert_valid
|
|
|
|
|
|
def build_artifact_manifest(
|
|
run_dir: Path,
|
|
run_id: str,
|
|
evidence: list[dict[str, Any]],
|
|
) -> list[dict[str, Any]]:
|
|
artifacts: list[dict[str, Any]] = []
|
|
seen: set[str] = set()
|
|
for item in evidence:
|
|
producer = item["check_id"]
|
|
for artifact_ref in item.get("artifact_refs", []):
|
|
if not isinstance(artifact_ref, str) or artifact_ref in seen:
|
|
continue
|
|
seen.add(artifact_ref)
|
|
path = (run_dir / artifact_ref).resolve()
|
|
try:
|
|
path.relative_to(run_dir.resolve())
|
|
except ValueError:
|
|
continue
|
|
if not path.exists() or not path.is_file():
|
|
continue
|
|
artifact = {
|
|
"id": f"artifact:{_safe_id(artifact_ref)}",
|
|
"run_id": run_id,
|
|
"path": artifact_ref,
|
|
"media_type": _media_type(path),
|
|
"producer": producer,
|
|
"checksum": f"sha256:{_sha256(path)}",
|
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
"retention_class": "raw",
|
|
}
|
|
assert_valid(artifact, "raw-artifact")
|
|
artifacts.append(artifact)
|
|
return artifacts
|
|
|
|
|
|
def _sha256(path: Path) -> str:
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as handle:
|
|
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
digest.update(chunk)
|
|
return digest.hexdigest()
|
|
|
|
|
|
def _media_type(path: Path) -> str:
|
|
guessed, _ = mimetypes.guess_type(path.name)
|
|
if guessed:
|
|
return guessed
|
|
return "application/octet-stream"
|
|
|
|
|
|
def _safe_id(value: str) -> str:
|
|
return "".join(char if char.isalnum() or char in {"-", "_"} else "_" for char in value)
|