GuestOps/deploy/release_record.py

77 lines
2.4 KiB
Python

#!/usr/bin/env python3
"""Create deterministic evidence for a reviewed GuestOps image archive."""
from __future__ import annotations
import argparse
import hashlib
import json
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def project_versions() -> tuple[str, str]:
props = (ROOT / "Directory.Build.props").read_text(encoding="utf-8")
match = re.search(r"<Version>([^<]+)</Version>", props)
if match is None:
raise ValueError("Directory.Build.props does not contain a Version element")
package = json.loads((ROOT / "web" / "package.json").read_text(encoding="utf-8"))
return match.group(1), str(package["version"])
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--artifact", required=True, type=Path)
parser.add_argument("--commit", required=True)
parser.add_argument("--api-image", required=True)
parser.add_argument("--api-id", required=True)
parser.add_argument("--worker-image", required=True)
parser.add_argument("--worker-id", required=True)
parser.add_argument("--output", required=True, type=Path)
args = parser.parse_args()
commit = args.commit.lower()
if re.fullmatch(r"[0-9a-f]{40}", commit) is None:
parser.error("--commit must be a full 40-character Git SHA")
if not args.artifact.is_file():
parser.error("--artifact must name an existing file")
dotnet_version, web_version = project_versions()
if dotnet_version != web_version:
parser.error(
f"release versions differ: .NET={dotnet_version}, web={web_version}"
)
record = {
"artifact": {
"name": args.artifact.name,
"sha256": sha256(args.artifact),
"size": args.artifact.stat().st_size,
},
"commit": commit,
"images": {
"api": {"id": args.api_id, "reference": args.api_image},
"worker": {"id": args.worker_id, "reference": args.worker_image},
},
"schemaVersion": 1,
"version": dotnet_version,
}
args.output.write_text(
json.dumps(record, indent=2, sort_keys=True) + "\n", encoding="utf-8"
)
if __name__ == "__main__":
main()