Fix read-only Web runtime publication
This commit is contained in:
@@ -9,6 +9,7 @@ import json
|
||||
import mimetypes
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import secrets
|
||||
import sys
|
||||
from typing import Any
|
||||
@@ -18,6 +19,7 @@ from urllib.request import Request, urlopen
|
||||
|
||||
|
||||
MAX_ASSET_BYTES = 256 * 1024 * 1024
|
||||
COMMIT_SHA = re.compile(r"^[0-9a-f]{40}$")
|
||||
|
||||
|
||||
class PublishError(RuntimeError):
|
||||
@@ -30,6 +32,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--owner", default="GovOPlaN")
|
||||
parser.add_argument("--repo", default="govoplan")
|
||||
parser.add_argument("--tag", required=True)
|
||||
parser.add_argument("--target-commit", required=True)
|
||||
parser.add_argument("--title", required=True)
|
||||
parser.add_argument("--body", default="Signed GovOPlaN runtime distribution.")
|
||||
parser.add_argument("--asset", type=Path, action="append", default=[], required=True)
|
||||
@@ -48,25 +51,49 @@ class GiteaReleasePublisher:
|
||||
self.repo = repo
|
||||
self.token = token
|
||||
|
||||
def release(self, *, tag: str, title: str, body: str) -> dict[str, Any]:
|
||||
def release(
|
||||
self,
|
||||
*,
|
||||
tag: str,
|
||||
target_commit: str,
|
||||
title: str,
|
||||
body: str,
|
||||
) -> dict[str, Any]:
|
||||
if COMMIT_SHA.fullmatch(target_commit) is None:
|
||||
raise PublishError("release target must be an exact lowercase commit SHA")
|
||||
resolved_target = self._resolve_commit(target_commit)
|
||||
if resolved_target != target_commit:
|
||||
raise PublishError("release target did not resolve to the requested commit")
|
||||
existing_tag = self._resolve_commit(tag, allow_missing=True)
|
||||
if existing_tag is not None and existing_tag != target_commit:
|
||||
raise PublishError(
|
||||
f"release tag {tag!r} already points to another commit"
|
||||
)
|
||||
|
||||
path = self._repo_path(f"/releases/tags/{quote(tag, safe='')}")
|
||||
try:
|
||||
return self._json("GET", path)
|
||||
release = self._json("GET", path)
|
||||
except HTTPError as exc:
|
||||
if exc.code != 404:
|
||||
raise
|
||||
return self._json(
|
||||
"POST",
|
||||
self._repo_path("/releases"),
|
||||
payload={
|
||||
"tag_name": tag,
|
||||
"name": title,
|
||||
"body": body,
|
||||
"draft": False,
|
||||
"prerelease": False,
|
||||
},
|
||||
expected=201,
|
||||
)
|
||||
release = self._json(
|
||||
"POST",
|
||||
self._repo_path("/releases"),
|
||||
payload={
|
||||
"tag_name": tag,
|
||||
"target_commitish": target_commit,
|
||||
"name": title,
|
||||
"body": body,
|
||||
"draft": False,
|
||||
"prerelease": False,
|
||||
},
|
||||
expected=201,
|
||||
)
|
||||
if self._resolve_commit(tag) != target_commit:
|
||||
raise PublishError(
|
||||
f"release tag {tag!r} does not resolve to the requested commit"
|
||||
)
|
||||
return release
|
||||
|
||||
def upload_assets(self, release: dict[str, Any], assets: tuple[Path, ...]) -> None:
|
||||
release_id = release.get("id")
|
||||
@@ -165,6 +192,21 @@ class GiteaReleasePublisher:
|
||||
def _headers(self) -> dict[str, str]:
|
||||
return {"Authorization": f"token {self.token}", "Accept": "application/json"}
|
||||
|
||||
def _resolve_commit(self, ref: str, *, allow_missing: bool = False) -> str | None:
|
||||
path = self._repo_path(f"/git/commits/{quote(ref, safe='')}")
|
||||
try:
|
||||
commit = self._json("GET", path)
|
||||
except HTTPError as exc:
|
||||
if allow_missing and exc.code == 404:
|
||||
return None
|
||||
raise
|
||||
if not isinstance(commit, dict):
|
||||
raise PublishError(f"Gitea returned an invalid commit for {ref!r}")
|
||||
sha = commit.get("sha")
|
||||
if not isinstance(sha, str) or COMMIT_SHA.fullmatch(sha) is None:
|
||||
raise PublishError(f"Gitea returned an invalid commit SHA for {ref!r}")
|
||||
return sha
|
||||
|
||||
def _repo_path(self, suffix: str) -> str:
|
||||
return (
|
||||
f"{self.base_url}/api/v1/repos/{quote(self.owner, safe='')}/"
|
||||
@@ -189,7 +231,12 @@ def main() -> int:
|
||||
repo=args.repo,
|
||||
token=os.environ.get(args.token_env, ""),
|
||||
)
|
||||
release = publisher.release(tag=args.tag, title=args.title, body=args.body)
|
||||
release = publisher.release(
|
||||
tag=args.tag,
|
||||
target_commit=args.target_commit,
|
||||
title=args.title,
|
||||
body=args.body,
|
||||
)
|
||||
publisher.upload_assets(release, tuple(args.asset))
|
||||
except (HTTPError, OSError, PublishError, ValueError) as exc:
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
|
||||
Reference in New Issue
Block a user