#!/usr/bin/env python3 """Request an authenticated production refresh after a remote project update.""" from __future__ import annotations import argparse import json import os import sys from urllib.error import HTTPError, URLError from urllib.request import Request, urlopen DISPATCH_URL = ( "https://git.labyricorn.com/api/v1/repos/Labyricorn/labyricorn-site/" "actions/workflows/deploy.yml/dispatches" ) TOKEN_ENVIRONMENT_VARIABLE = "LABYRICORN_REFRESH_TOKEN" class RefreshError(RuntimeError): """Raised when Gitea does not accept a refresh request.""" def dispatch_refresh(token: str, *, timeout: float = 15.0) -> None: token = token.strip() if not token: raise RefreshError("the refresh token is empty") if "\r" in token or "\n" in token: raise RefreshError("the refresh token contains an invalid newline") request = Request( DISPATCH_URL, data=json.dumps({"ref": "main"}).encode("utf-8"), headers={ "Accept": "application/json", "Authorization": f"token {token}", "Content-Type": "application/json", "User-Agent": "labyricorn-project-refresh/1", }, method="POST", ) try: with urlopen(request, timeout=timeout) as response: status = response.getcode() except HTTPError as exc: raise RefreshError(f"Gitea rejected the refresh request (HTTP {exc.code})") from exc except URLError as exc: reason = getattr(exc, "reason", "connection failed") raise RefreshError(f"could not contact Gitea: {reason}") from exc except OSError as exc: raise RefreshError(f"could not send the refresh request: {exc}") from exc if not 200 <= status < 300: raise RefreshError(f"Gitea returned unexpected HTTP status {status}") def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--dry-run", action="store_true", help="show the target workflow without sending a request or reading a token", ) parser.add_argument( "--timeout", type=float, default=15.0, help="request timeout in seconds (default: 15)", ) args = parser.parse_args(argv) if args.timeout <= 0: parser.error("--timeout must be greater than zero") if args.dry_run: print(f"Would dispatch deploy.yml for main via {DISPATCH_URL}") return 0 token = os.environ.get(TOKEN_ENVIRONMENT_VARIABLE) if token is None: print( f"project-refresh: ERROR: {TOKEN_ENVIRONMENT_VARIABLE} is not set", file=sys.stderr, ) return 2 try: dispatch_refresh(token, timeout=args.timeout) except RefreshError as exc: print(f"project-refresh: ERROR: {exc}", file=sys.stderr) return 1 print("Production refresh requested for site main.") print("Verify the Deploy production Action and the published project revision.") return 0 if __name__ == "__main__": raise SystemExit(main())