feat(release): require versioned locked publication

This commit is contained in:
Hermes Agent
2026-08-14 16:30:16 +09:00
parent 1a2b500722
commit cb8fcee226
6 changed files with 146 additions and 33 deletions
+16 -13
View File
@@ -20,6 +20,7 @@ CANARY = b"TREX_SECRET_CANARY_DO_NOT_SHIP"
COMMIT_RE = re.compile(r"^[0-9a-f]{40}$")
DIGEST_RE = re.compile(r"(?:^|\s)digest:\s*(sha256:[0-9a-f]{64})(?:\s|$)")
IMAGE_ID_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
VERSION_TAG_RE = re.compile(r"^refs/tags/v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$")
DEFAULT_MIGRATION_DIR = (
Path(__file__).resolve().parents[1] / "trex_core" / "adapters" / "persistence" / "sql"
)
@@ -100,19 +101,18 @@ def validate_commit(commit: str) -> str:
return commit
def release_tags(commit: str, ref: str) -> list[str]:
def validate_publication_trigger(event_name: str, ref: str) -> str:
if event_name != "push":
raise ValueError("publication event must be a push")
if not VERSION_TAG_RE.fullmatch(ref):
raise ValueError("publication ref must be a strict vX.Y.Z tag")
return ref.removeprefix("refs/tags/")
def release_tags(commit: str, event_name: str, ref: str) -> list[str]:
validate_commit(commit)
tags = [f"{IMAGE}:{commit[:12]}"]
if ref == "refs/heads/main":
return tags
prefix = "refs/tags/"
if ref.startswith(prefix) and ref.removeprefix(prefix).startswith("v"):
release = ref.removeprefix(prefix)
if not re.fullmatch(r"v[A-Za-z0-9._-]*", release):
raise ValueError("release ref contains unsupported tag characters")
tags.append(f"{IMAGE}:{release}")
return tags
raise ValueError("publication ref must be main or a v* tag")
release = validate_publication_trigger(event_name, ref)
return [f"{IMAGE}:{commit[:12]}", f"{IMAGE}:{release}"]
def sha256_file(path: Path) -> str:
@@ -359,6 +359,7 @@ def publish_image(
image_id: Path,
local_image: str,
commit: str,
event_name: str,
ref: str,
actor: str,
token: str,
@@ -366,7 +367,7 @@ def publish_image(
output_path: Path | None = None,
summary_path: Path | None = None,
) -> str:
tags = release_tags(commit, ref)
tags = release_tags(commit, event_name, ref)
verify_checksum(archive, checksum)
expected_image_id = read_image_id(image_id)
loaded = runner.run(["docker", "load", "--input", str(archive)])
@@ -450,6 +451,7 @@ def _parser() -> argparse.ArgumentParser:
publish.add_argument("--image-id", type=Path, required=True)
publish.add_argument("--local-image", required=True)
publish.add_argument("--commit", required=True)
publish.add_argument("--event-name", required=True)
publish.add_argument("--ref", required=True)
return parser
@@ -476,6 +478,7 @@ def main(argv: Sequence[str] | None = None) -> int:
image_id=args.image_id,
local_image=args.local_image,
commit=args.commit,
event_name=args.event_name,
ref=args.ref,
actor=os.environ.get("GITHUB_ACTOR", ""),
token=os.environ.get("REGISTRY_TOKEN", ""),