#!/usr/bin/env python3 import argparse import json import os import re import sys import urllib.error import urllib.request from subprocess import PIPE, run def get_env(name: str, default: str = "") -> str: value = os.getenv(name, default) return value.strip() if isinstance(value, str) else default def fail(message: str, code: int = 1) -> None: print(f"✗ {message}") sys.exit(code) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Dispatch Gitea Actions workflow") parser.add_argument("--workflow", default="wiki-sync.yml", help="Workflow file name (e.g. wiki-sync.yml)") parser.add_argument("--ref", default="master", help="Git ref for workflow dispatch") parser.add_argument( "--input", action="append", default=[], help="Workflow input in key=value format. Can be provided multiple times.", ) parser.add_argument( "--require-input", action="append", default=[], help="Required input key(s). Fails if key is missing or empty.", ) return parser.parse_args() def parse_inputs(items: list[str]) -> dict[str, str]: result: dict[str, str] = {} for item in items: if "=" not in item: fail(f"Неверный формат --input '{item}'. Используйте key=value") key, value = item.split("=", 1) key = key.strip() value = value.strip() if not key: fail(f"Пустой ключ в --input '{item}'") result[key] = value return result def get_repo_path() -> str: result = run(["git", "config", "--get", "remote.origin.url"], stdout=PIPE, stderr=PIPE, text=True) origin_url = (result.stdout or "").strip() if result.returncode != 0 or not origin_url: fail("remote.origin.url не найден. Проверьте, что команда запущена внутри git-репозитория.") repo_path = re.sub(r"^(https?://[^/]+/|git@[^:]+:)", "", origin_url) repo_path = re.sub(r"\.git$", "", repo_path) if not repo_path: fail(f"Не удалось вычислить owner/repo из remote.origin.url: {origin_url}") return repo_path def main() -> None: args = parse_args() token = get_env("DFGIT_TOKEN") or get_env("GIT_TOKEN") if not token: fail("не задан DFGIT_TOKEN (или GIT_TOKEN).") dfgit_url = get_env("DFGIT_URL", "http://dfgit").rstrip("/") workflow = args.workflow.strip() or "wiki-sync.yml" ref = args.ref.strip() or "master" inputs = parse_inputs(args.input) for required_key in args.require_input: required_key = required_key.strip() if required_key and not inputs.get(required_key, "").strip(): fail( f"Обязательный input '{required_key}' не задан. " f"Передайте --input {required_key}=..." ) repo_path = get_repo_path() dispatch_url = f"{dfgit_url}/api/v1/repos/{repo_path}/actions/workflows/{workflow}/dispatches" payload_data: dict[str, object] = {"ref": ref} if inputs: payload_data["inputs"] = inputs payload = json.dumps(payload_data).encode("utf-8") print(f"→ Репозиторий: {repo_path}") print(f"→ Workflow: {workflow}, ref: {ref}") if inputs: print(f"→ Inputs: {json.dumps(inputs, ensure_ascii=False)}") print(f"→ Запрос: {dispatch_url}") request = urllib.request.Request( dispatch_url, data=payload, method="POST", headers={ "Authorization": f"token {token}", "Content-Type": "application/json", "Accept": "application/json", }, ) try: with urllib.request.urlopen(request, timeout=30) as response: status = response.getcode() body = response.read().decode("utf-8", errors="replace").strip() except urllib.error.HTTPError as error: status = error.code body = error.read().decode("utf-8", errors="replace").strip() except urllib.error.URLError as error: fail(f"Ошибка сети при обращении к dfgit: {error}") if status in (200, 201, 204): print("✓ Workflow успешно запущен") print("Проверьте статус в Actions на сервере dfgit.") return print(f"✗ Ошибка запуска workflow (HTTP {status})") if body: print("Ответ API:") print(body) sys.exit(1) if __name__ == "__main__": main()