82 lines
2.3 KiB
Python
82 lines
2.3 KiB
Python
"""Record each verified R2 upload for the GitHub job summary.
|
|
|
|
The record is a log. A missing summary file or a public-URL failure must not
|
|
fail an upload that already landed. The process exit writes the table, so a
|
|
step that uploads and then exits still reports its objects.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import atexit
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
|
|
_KEYS: list[str] = []
|
|
_REGISTERED = False
|
|
|
|
|
|
def note(key: str) -> None:
|
|
"""Remember one object key that a verified upload just wrote."""
|
|
global _REGISTERED
|
|
if not isinstance(key, str) or not key or key in _KEYS:
|
|
return
|
|
_KEYS.append(key)
|
|
if not _REGISTERED and os.environ.get("GITHUB_STEP_SUMMARY"):
|
|
atexit.register(flush)
|
|
_REGISTERED = True
|
|
|
|
|
|
def rows(keys: list[str]) -> list[tuple[str, str]]:
|
|
"""Public URL for each key, in upload order. A bad key is named, not dropped."""
|
|
from scripts.releases import r2
|
|
|
|
base = r2.public_base_url()
|
|
listed = []
|
|
for key in keys:
|
|
try:
|
|
listed.append((key, r2.public_url_for(base, key)))
|
|
except ValueError:
|
|
listed.append((key, ""))
|
|
return listed
|
|
|
|
|
|
def render(keys: list[str]) -> str:
|
|
"""One markdown table. Empty when the step uploaded nothing."""
|
|
if not keys:
|
|
return ""
|
|
lines = ["", "### R2 uploads", "", "| Object | URL |", "|---|---|"]
|
|
for key, url in rows(keys):
|
|
cell = f"[download]({url})" if url else "—"
|
|
lines.append(f"| `{key}` | {cell} |")
|
|
return "\n".join(lines) + "\n"
|
|
|
|
|
|
def flush() -> None:
|
|
"""Append the table once. A second call, or a missing summary, writes nothing."""
|
|
summary = os.environ.get("GITHUB_STEP_SUMMARY", "")
|
|
if not summary or not _KEYS:
|
|
return
|
|
text = render(list(_KEYS))
|
|
_KEYS.clear()
|
|
if not text:
|
|
return
|
|
try:
|
|
with Path(summary).open("a", encoding="utf-8") as stream:
|
|
stream.write(text)
|
|
except OSError:
|
|
return
|
|
|
|
|
|
def main() -> None:
|
|
"""Print the table for keys a caller already recorded."""
|
|
raw = os.environ.get("R2_UPLOAD_KEYS", "")
|
|
keys = json.loads(raw) if raw else []
|
|
if not isinstance(keys, list) or not all(isinstance(key, str) for key in keys):
|
|
raise SystemExit("R2_UPLOAD_KEYS must be a JSON list of object keys")
|
|
print(render(keys), end="")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|