diff --git a/scripts/upload_artifacts.py b/scripts/upload_artifacts.py index 14ec080..3cabb82 100644 --- a/scripts/upload_artifacts.py +++ b/scripts/upload_artifacts.py @@ -1,98 +1,71 @@ #!/usr/bin/env python3 -""" -Upload build outputs as Gitea Actions artifacts using the Actions Runtime API. +"""Upload artifact to Gitea Actions runtime, falling back to local save.""" -This replaces GitHub-only actions/upload-artifact with a Gitea-native approach. -Uses ACTIONS_RUNTIME_URL and ACTIONS_RUNTIME_TOKEN provided by Gitea's -act_runner to upload artifacts directly to the pipeline service. - -Usage: - python3 scripts/upload_artifacts.py [path_glob2 ...] - -Environment (set by Gitea act_runner >= 1.21): - ACTIONS_RUNTIME_URL — Pipeline service base URL (e.g. http://localhost:35278/1) - ACTIONS_RUNTIME_TOKEN — Bearer token for the pipeline service - -When the runtime environment is not available (e.g. local testing), -artifacts are saved to ./artifacts/.zip as a fallback. -""" -import glob -import io +import json import os import sys -import urllib.error import urllib.request -import zipfile +import urllib.error -_RUNTIME_UPLOADED = False +HERE = os.path.dirname(os.path.abspath(__file__)) +ARTIFACTS_DIR = os.path.join(os.path.dirname(HERE), "artifacts") -def _upload_via_runtime(name, payload, runtime_url, runtime_token): - url = "{}/_apis/pipeline/artifacts/{}".format(runtime_url.rstrip("/"), name) +def _upload_via_runtime(artifact_name, payload, runtime_url, runtime_token): + """Upload via Gitea Actions runtime API.""" + url = f"{runtime_url}/_apis/artifact/build/{artifact_name}/{artifact_name}/upload" headers = { - "Authorization": "Bearer " + runtime_token, + "Authorization": f"Bearer {runtime_token}", "Content-Type": "application/octet-stream", - "Content-Range": "bytes 0-{}/{}".format(len(payload) - 1, len(payload)), - "x-tfs-filelength": str(len(payload)), + "Accept": "*/*", } req = urllib.request.Request(url, data=payload, headers=headers, method="PUT") - try: - with urllib.request.urlopen(req) as resp: - msg = "[upload-artifact] Uploaded '{}' ({} bytes) to Gitea runtime — HTTP {}" - print(msg.format(name, len(payload), resp.status)) - global _RUNTIME_UPLOADED - _RUNTIME_UPLOADED = True - return True - except urllib.error.HTTPError as e: - body = e.read().decode()[:300] if e.fp else "" - print("[upload-artifact] Runtime upload failed: HTTP {} — {}".format(e.code, body)) - return False + with urllib.request.urlopen(req) as resp: + return resp.status in (200, 201, 204) -def _save_locally(name, payload): - dest = os.path.join("artifacts", "{}.zip".format(name)) - os.makedirs(os.path.dirname(dest) or ".", exist_ok=True) +def _save_locally(artifact_name, payload): + """Fallback: save to local filesystem.""" + os.makedirs(ARTIFACTS_DIR, exist_ok=True) + dest = os.path.join(ARTIFACTS_DIR, f"{artifact_name}.zip") with open(dest, "wb") as f: f.write(payload) - print("[upload-artifact] Saved artifact to {} ({} bytes)".format(dest, len(payload))) + return dest def main(): if len(sys.argv) < 3: - print("Usage: {} [path_glob ...]".format(sys.argv[0])) + print(f"Usage: {sys.argv[0]} ") sys.exit(1) artifact_name = sys.argv[1] - patterns = sys.argv[2:] + file_path = sys.argv[2] - files = [] - for pattern in patterns: - matched = glob.glob(pattern, recursive=True) - files.extend(matched) - files = sorted(set(f for f in files if os.path.isfile(f))) + if not os.path.isfile(file_path): + print(f"[upload-artifact] ERROR: file not found: {file_path}") + sys.exit(1) - if not files: - print("[upload-artifact] No files found for pattern(s): {}".format(patterns)) - return + with open(file_path, "rb") as f: + payload = f.read() - buf = io.BytesIO() - with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf: - for f in files: - zf.write(f, os.path.relpath(f)) - payload = buf.getvalue() - - runtime_url = os.environ.get("ACTIONS_RUNTIME_URL", "").rstrip("/") + runtime_url = os.environ.get("ACTIONS_RUNTIME_URL", "") runtime_token = os.environ.get("ACTIONS_RUNTIME_TOKEN", "") if runtime_url and runtime_token: - ok = _upload_via_runtime(artifact_name, payload, runtime_url, runtime_token) - if not ok: - _save_locally(artifact_name, payload) - else: - print("[upload-artifact] ACTIONS_RUNTIME_URL/TOKEN not available — saving locally.") - _save_locally(artifact_name, payload) + try: + ok = _upload_via_runtime(artifact_name, payload, runtime_url, runtime_token) + if ok: + print(f"[upload-artifact] Uploaded {file_path} ({len(payload)} bytes)") + return + except urllib.error.URLError as e: + print(f"[upload-artifact] Runtime upload failed (URLError): {e.reason}") + except Exception as e: + print(f"[upload-artifact] Runtime upload failed: {e}") - print("[upload-artifact] Done — {} file(s) in '{}'.".format(len(files), artifact_name)) + # Fallback + dest = _save_locally(artifact_name, payload) + print(f"[upload-artifact] Saved artifact to {dest} ({len(payload)} bytes)") + print("[upload-artifact] Done — 1 file(s) in '{}'.".format(artifact_name)) if __name__ == "__main__":