101 lines
3.0 KiB
Python
101 lines
3.0 KiB
Python
#!/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())
|