release build workflow
This commit is contained in:
1 parent
998d9f603d
commit
8084c39e37
4 files changed
+140
-1
No files matched your search
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
# Creates or updates the Gitea release for a tag and attaches files to it. Its
|
||||
# notes are that version's section of CHANGELOG.md. Run again for the same tag,
|
||||
# it updates the notes and replaces files of the same name.
|
||||
#
|
||||
# gitea-release.py <tag> <file>...
|
||||
#
|
||||
# Needs GITEA_URL (https://gitea.example.com), GITEA_REPO (owner/name) and
|
||||
# GITEA_TOKEN. Used by .gitea/workflows/release.yaml.
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import uuid
|
||||
|
||||
|
||||
def api(method, path, body=None, content_type="application/json"):
|
||||
url = f"{os.environ['GITEA_URL'].rstrip('/')}/api/v1/repos/{os.environ['GITEA_REPO']}{path}"
|
||||
if isinstance(body, dict):
|
||||
body = json.dumps(body).encode()
|
||||
headers = {"Authorization": f"token {os.environ['GITEA_TOKEN']}", "Accept": "application/json"}
|
||||
if body is not None:
|
||||
headers["Content-Type"] = content_type
|
||||
request = urllib.request.Request(url, data=body, method=method, headers=headers)
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=300) as response:
|
||||
data = response.read()
|
||||
return json.loads(data) if data else None
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code == 404 and method == "GET":
|
||||
return None
|
||||
sys.exit(f"{method} {url}: HTTP {e.code}: {e.read().decode(errors='replace')}")
|
||||
|
||||
|
||||
def changelog_section(version):
|
||||
"""The lines under '## <version>' in CHANGELOG.md, up to the next '## '."""
|
||||
lines, found = [], False
|
||||
with open("CHANGELOG.md") as f:
|
||||
for line in f:
|
||||
if line.startswith("## "):
|
||||
if found:
|
||||
break
|
||||
found = line[3:].split()[0] == version
|
||||
continue
|
||||
if found:
|
||||
lines.append(line)
|
||||
if not found:
|
||||
sys.exit(f"CHANGELOG.md has no '## {version}' section")
|
||||
return "".join(lines).strip() + "\n"
|
||||
|
||||
|
||||
def upload(release_id, path):
|
||||
name = os.path.basename(path)
|
||||
boundary = uuid.uuid4().hex
|
||||
with open(path, "rb") as f:
|
||||
content = f.read()
|
||||
body = (f"--{boundary}\r\nContent-Disposition: form-data; name=\"attachment\"; filename=\"{name}\"\r\n"
|
||||
f"Content-Type: application/octet-stream\r\n\r\n").encode() + content + f"\r\n--{boundary}--\r\n".encode()
|
||||
api("POST", f"/releases/{release_id}/assets?name={urllib.parse.quote(name)}", body,
|
||||
f"multipart/form-data; boundary={boundary}")
|
||||
print(f"attached {name}")
|
||||
|
||||
|
||||
def main(tag, files):
|
||||
notes = changelog_section(tag[1:] if tag.startswith("v") else tag)
|
||||
release = api("GET", f"/releases/tags/{urllib.parse.quote(tag)}")
|
||||
if release is None:
|
||||
release = api("POST", "/releases", {"tag_name": tag, "name": tag, "body": notes})
|
||||
print(f"created release {tag}")
|
||||
else:
|
||||
release = api("PATCH", f"/releases/{release['id']}", {"body": notes})
|
||||
print(f"updated release {tag}")
|
||||
names = {os.path.basename(p) for p in files}
|
||||
for asset in release.get("assets") or []:
|
||||
if asset["name"] in names:
|
||||
api("DELETE", f"/releases/{release['id']}/assets/{asset['id']}")
|
||||
for path in files:
|
||||
upload(release["id"], path)
|
||||
print(release.get("html_url", ""))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 3:
|
||||
sys.exit("usage: gitea-release.py <tag> <file>...")
|
||||
for var in ("GITEA_URL", "GITEA_REPO", "GITEA_TOKEN"):
|
||||
if not os.environ.get(var):
|
||||
sys.exit(f"{var} is not set")
|
||||
main(sys.argv[1], sys.argv[2:])
|
||||
Reference in new issue
Block a user