diff --git a/tools/_release_api.py b/tools/_release_api.py new file mode 100644 index 0000000..3bd7a84 --- /dev/null +++ b/tools/_release_api.py @@ -0,0 +1,89 @@ +# Gitea リリース更新の API 部分(release-update.sh から呼ばれる)。 +# 1) タグ名からリリースを特定 +# 2) 同名の旧添付資産を削除し、最新の zip をアップロード +# 3) 本文の「ビルド元コミット: `xxxx`」を現在の短縮ハッシュへ置換 +# 認証は環境変数 GITEA_USER/GITEA_PASS(Basic)。Gitea が自己署名証明書のため TLS 検証は無効化。 +# 引数: +import os +import re +import ssl +import sys +import json +import base64 +from urllib.request import Request, urlopen +from urllib.error import HTTPError + +host, owner, repo, tag, asset, commit = sys.argv[1:7] +user = os.environ["GITEA_USER"] +pw = os.environ["GITEA_PASS"] + +base = f"https://{host}/api/v1/repos/{owner}/{repo}" +ctx = ssl.create_default_context() +ctx.check_hostname = False +ctx.verify_mode = ssl.CERT_NONE +auth = "Basic " + base64.b64encode(f"{user}:{pw}".encode()).decode() + + +def api(path, method="GET", data=None, headers=None): + h = {"Authorization": auth} + if headers: + h.update(headers) + req = Request(base + path, data=data, method=method, headers=h) + try: + r = urlopen(req, context=ctx) + except HTTPError as e: + msg = e.read().decode("utf-8", "replace") + sys.exit(f"ERROR: {method} {path} -> HTTP {e.code}: {msg}") + body = r.read() + return json.loads(body) if body else None + + +# 1) タグ名でリリースを特定(無ければ 404 で停止) +rel = api(f"/releases/tags/{tag}") +rid = rel["id"] +print(f"release id={rid} (tag {tag})") + +# 2) 同名の旧資産を削除 +name = os.path.basename(asset) +for a in api(f"/releases/{rid}/assets") or []: + if a["name"] == name: + api(f"/releases/{rid}/assets/{a['id']}", method="DELETE") + print(f"deleted old asset id={a['id']} ({a['size']} bytes)") + +# 3) 最新の zip をアップロード(multipart/form-data, field=attachment) +with open(asset, "rb") as f: + filedata = f.read() +boundary = "----photochkReleaseBoundary7e1f" +mp = b"".join([ + f"--{boundary}\r\n".encode(), + f'Content-Disposition: form-data; name="attachment"; filename="{name}"\r\n'.encode(), + b"Content-Type: application/octet-stream\r\n\r\n", + filedata, + b"\r\n", + f"--{boundary}--\r\n".encode(), +]) +new = api( + f"/releases/{rid}/assets?name={name}", + method="POST", + data=mp, + headers={"Content-Type": f"multipart/form-data; boundary={boundary}"}, +) +print(f"uploaded asset id={new['id']} ({new['size']} bytes)") + +# 4) 本文の「ビルド元コミット: `xxxx`」を現在の短縮ハッシュへ置換 +body = rel.get("body") or "" +new_body, n = re.subn( + r"(ビルド元コミット: `)[0-9a-fA-F]{7,40}(`)", + r"\g<1>" + commit + r"\g<2>", + body, +) +if n == 0: + # まだ行が無いリリースには追記する。次回以降は置換で更新される。 + new_body = body.rstrip() + f"\n\nビルド元コミット: `{commit}`\n" +api( + f"/releases/{rid}", + method="PATCH", + data=json.dumps({"body": new_body}).encode("utf-8"), + headers={"Content-Type": "application/json"}, +) +print(f"body {'updated' if n else 'appended'}: ビルド元コミット -> {commit}")