Files
Jyotisha/scripts/vedastro_reproducible_build_probe.py
2026-07-19 08:59:33 +08:00

102 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Create a secret-free identity contract for a pinned VedAstro container build."""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import subprocess
from pathlib import Path
from typing import Any
def _sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def _git_commit(root: Path) -> str:
completed = subprocess.run(
["git", "rev-parse", "HEAD"], cwd=root, text=True, capture_output=True, check=False
)
return completed.stdout.strip() if completed.returncode == 0 else ""
def _inspect_image(tag: str) -> dict[str, Any] | None:
completed = subprocess.run(
["docker", "image", "inspect", tag], text=True, capture_output=True, check=False
)
if completed.returncode != 0:
return None
payload = json.loads(completed.stdout)
return payload[0] if isinstance(payload, list) and payload else None
def build_identity(
source_root: Path,
*,
source_commit: str | None = None,
image_inspect: dict[str, Any] | None = None,
) -> dict[str, Any]:
source_root = source_root.resolve()
dockerfile = source_root / "API/Dockerfile"
if not dockerfile.is_file():
raise FileNotFoundError(dockerfile)
docker_text = dockerfile.read_text(encoding="utf-8")
base_images: list[str] = []
stage_names: set[str] = set()
for match in re.finditer(
r"^FROM\s+([^\s]+)(?:\s+AS\s+([^\s]+))?",
docker_text,
flags=re.MULTILINE | re.IGNORECASE,
):
image, stage = match.group(1), match.group(2)
if image not in stage_names:
base_images.append(image)
if stage:
stage_names.add(stage)
project_files = sorted(source_root.glob("**/*.csproj"))
project_hashes = {
str(path.relative_to(source_root)): _sha256(path)
for path in project_files
if "/bin/" not in path.as_posix() and "/obj/" not in path.as_posix()
}
image_id = (image_inspect or {}).get("Id", "")
repo_digests = (image_inspect or {}).get("RepoDigests") or []
return {
"scope": "vedastro_reproducible_build_identity",
"source_root": str(source_root),
"source_commit": source_commit or _git_commit(source_root),
"dockerfile_path": "API/Dockerfile",
"dockerfile_sha256": _sha256(dockerfile),
"base_images": base_images,
"project_file_hashes": project_hashes,
"image_id": image_id,
"repo_digests": repo_digests,
"status": "reproducible_candidate_built" if image_id else "source_pinned_image_not_built",
"boundary": "Identifies the pinned local candidate only; it does not identify api.vedastro.org.",
}
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("source_root", type=Path)
parser.add_argument("--image-tag", default="")
parser.add_argument("--output", type=Path)
args = parser.parse_args()
report = build_identity(
args.source_root,
image_inspect=_inspect_image(args.image_tag) if args.image_tag else None,
)
text = json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
if args.output:
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(text, encoding="utf-8")
print(text, end="")
return 0
if __name__ == "__main__":
raise SystemExit(main())