GRM-113: feat: upgrade all dependencies, add trigger-workflow command
Post-merge / detect-type (push) Successful in 55s
Post-merge / release (push) Successful in 1m19s
Post-merge / validate-commit-msg (push) Successful in 1m19s
Post-merge / badges (push) Successful in 1m33s
Post-merge / vikunja (push) Successful in 1m13s
Post-merge / configure-repo (push) Successful in 1m13s
Post-merge / sync-wiki (push) Successful in 1m54s
Post-merge / publish (push) Successful in 55s
Post-merge / detect-type (push) Successful in 55s
Post-merge / release (push) Successful in 1m19s
Post-merge / validate-commit-msg (push) Successful in 1m19s
Post-merge / badges (push) Successful in 1m33s
Post-merge / vikunja (push) Successful in 1m13s
Post-merge / configure-repo (push) Successful in 1m13s
Post-merge / sync-wiki (push) Successful in 1m54s
Post-merge / publish (push) Successful in 55s
This commit was merged in pull request #180.
This commit is contained in:
@@ -14,6 +14,7 @@ from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnk
|
||||
|
||||
from . import __version__
|
||||
from .exceptions import GRMError
|
||||
from .gitea_client import GiteaAPIError, GiteaWorkflowClient
|
||||
from .i18n import _
|
||||
from .runner_manager import RunnerManager
|
||||
|
||||
@@ -468,3 +469,82 @@ def list_runners(ask_become_pass: bool, no_status: bool) -> None:
|
||||
click.echo("-" * 90)
|
||||
for r in runners:
|
||||
click.echo(f"{r['name']:<18} {r['host']:<16} {r['user']:<10} {r['labels']:<30} {r['status']}")
|
||||
|
||||
|
||||
@cli.command(name="trigger-workflow", help=_("Trigger a Gitea Actions workflow via the API."))
|
||||
@click.argument("workflow_id", required=False)
|
||||
@click.option(
|
||||
"--repo",
|
||||
default=lambda: os.getenv("GRM_REPO", "oblachno-oss/grm"),
|
||||
help=_("Repository in owner/repo format (env: GRM_REPO, default: oblachno-oss/grm)"),
|
||||
)
|
||||
@click.option(
|
||||
"--ref",
|
||||
default="master",
|
||||
help=_("Git ref to run the workflow on (default: master)"),
|
||||
)
|
||||
@click.option(
|
||||
"--url",
|
||||
default=lambda: os.getenv("GITEA_URL", ""),
|
||||
help=_("Gitea URL (env: GITEA_URL)"),
|
||||
)
|
||||
@click.option(
|
||||
"--token",
|
||||
default=lambda: os.getenv("CI_GITEA_TOKEN"),
|
||||
help=_("Gitea API token (env: CI_GITEA_TOKEN)"),
|
||||
)
|
||||
@click.option(
|
||||
"--list",
|
||||
"list_only",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help=_("List available workflows instead of triggering one"),
|
||||
)
|
||||
def trigger_workflow(
|
||||
workflow_id: str,
|
||||
repo: str,
|
||||
ref: str,
|
||||
url: str,
|
||||
token: str | None,
|
||||
list_only: bool,
|
||||
) -> None:
|
||||
"""Trigger a Gitea Actions workflow dispatch event."""
|
||||
if not url:
|
||||
raise click.ClickException(_("GITEA_URL is required (set --url or GITEA_URL env var)"))
|
||||
if not token:
|
||||
raise click.ClickException(_("CI_GITEA_TOKEN is required (set --token or CI_GITEA_TOKEN env var)"))
|
||||
if not list_only and not workflow_id:
|
||||
raise click.ClickException(_("WORKFLOW_ID is required unless --list is used"))
|
||||
|
||||
client = GiteaWorkflowClient(url, token)
|
||||
owner, repo_name = repo.split("/", 1)
|
||||
|
||||
if list_only:
|
||||
workflows = client.list_workflows(owner, repo_name)
|
||||
if not workflows:
|
||||
click.echo(_("No workflows found in {repo}", repo=repo))
|
||||
return
|
||||
click.echo(f"{'ID':<30} {'NAME':<20} {'PATH':<25} {'STATE'}")
|
||||
click.echo("-" * 85)
|
||||
for wf in workflows:
|
||||
wf_id = str(wf.get("id", ""))
|
||||
wf_name = wf.get("name", "")
|
||||
wf_path = wf.get("path", "")
|
||||
wf_state = wf.get("state", "")
|
||||
click.echo(f"{wf_id:<30} {wf_name:<20} {wf_path:<25} {wf_state}")
|
||||
return
|
||||
|
||||
click.echo(_("Triggering workflow {wf} on {repo}@{ref}...", wf=workflow_id, repo=repo, ref=ref))
|
||||
try:
|
||||
result = client.dispatch_workflow(owner, repo_name, workflow_id, ref)
|
||||
except GiteaAPIError as e:
|
||||
raise click.ClickException(str(e)) from e
|
||||
|
||||
if result and result.get("id"):
|
||||
run_id: Any = result["id"]
|
||||
click.echo(_("Workflow triggered successfully. Run ID: {run_id}", run_id=run_id))
|
||||
if result.get("html_url"):
|
||||
html_url: Any = result["html_url"]
|
||||
click.echo(f" {html_url}")
|
||||
else:
|
||||
click.echo(_("Workflow triggered successfully."))
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
"""Minimal Gitea API client for workflow operations.
|
||||
|
||||
Uses urllib from the standard library to avoid adding requests as a
|
||||
runtime dependency. Only covers the Actions workflow dispatch endpoint.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request # noqa: PTH123 # nosec B404
|
||||
from contextlib import suppress
|
||||
from typing import Any
|
||||
|
||||
|
||||
class GiteaAPIError(Exception):
|
||||
"""Raised when a Gitea API call fails."""
|
||||
|
||||
def __init__(self, status: int, message: str) -> None:
|
||||
super().__init__(f"Gitea API error {status}: {message}")
|
||||
self.status = status
|
||||
self.message = message
|
||||
|
||||
|
||||
class GiteaWorkflowClient:
|
||||
"""Thin client for Gitea Actions workflow API endpoints."""
|
||||
|
||||
def __init__(self, base_url: str, token: str) -> None:
|
||||
self._base_url = base_url.rstrip("/")
|
||||
self._token = token
|
||||
|
||||
def _request(self, method: str, path: str, body: dict[str, Any] | None = None) -> dict[str, Any] | None:
|
||||
url = f"{self._base_url}/api/v1{path}"
|
||||
data = json.dumps(body).encode("utf-8") if body else None
|
||||
req = urllib.request.Request( # nosec B310
|
||||
url,
|
||||
data=data,
|
||||
method=method,
|
||||
)
|
||||
req.add_header("Authorization", f"token {self._token}")
|
||||
req.add_header("Content-Type", "application/json")
|
||||
req.add_header("Accept", "application/json")
|
||||
try:
|
||||
with urllib.request.urlopen(req) as resp: # noqa: PTH123 # nosec B310
|
||||
if resp.status == 204:
|
||||
return None
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else None
|
||||
except urllib.error.HTTPError as e:
|
||||
detail = e.read().decode("utf-8", errors="replace")
|
||||
with suppress(json.JSONDecodeError, ValueError):
|
||||
detail = json.loads(detail).get("message", detail)
|
||||
raise GiteaAPIError(e.code, detail) from e
|
||||
|
||||
def list_workflows(self, owner: str, repo: str) -> list[dict[str, Any]]:
|
||||
"""List all workflows in a repository."""
|
||||
result = self._request("GET", f"/repos/{owner}/{repo}/actions/workflows")
|
||||
if result is None:
|
||||
return []
|
||||
return result.get("workflows", [])
|
||||
|
||||
def dispatch_workflow(
|
||||
self,
|
||||
owner: str,
|
||||
repo: str,
|
||||
workflow_id: str,
|
||||
ref: str = "master",
|
||||
inputs: dict[str, str] | None = None,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Trigger a workflow dispatch event.
|
||||
|
||||
Args:
|
||||
owner: Repository owner.
|
||||
repo: Repository name.
|
||||
workflow_id: Workflow file name (e.g. "ci.yml") or numeric ID.
|
||||
ref: Git ref (branch/tag) to run on. Defaults to "master".
|
||||
inputs: Optional workflow inputs.
|
||||
|
||||
Returns:
|
||||
Run details dict if return_run_details is requested, else None.
|
||||
"""
|
||||
path = f"/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches?return_run_details=true"
|
||||
body: dict[str, Any] = {"ref": ref}
|
||||
if inputs:
|
||||
body["inputs"] = inputs
|
||||
return self._request("POST", path, body)
|
||||
@@ -630,5 +630,101 @@
|
||||
"pl": "nieznany",
|
||||
"ru": "неизвестно",
|
||||
"zh": "未知"
|
||||
},
|
||||
"CI_GITEA_TOKEN is required (set --token or CI_GITEA_TOKEN env var)": {
|
||||
"bg": "CI_GITEA_TOKEN е задължителен (задайте --token или CI_GITEA_TOKEN env var)",
|
||||
"de": "CI_GITEA_TOKEN ist erforderlich (setzen Sie --token oder CI_GITEA_TOKEN env var)",
|
||||
"en": "CI_GITEA_TOKEN is required (set --token or CI_GITEA_TOKEN env var)",
|
||||
"pl": "CI_GITEA_TOKEN jest wymagany (ustaw --token lub CI_GITEA_TOKEN env var)",
|
||||
"ru": "CI_GITEA_TOKEN обязателен (установите --token или CI_GITEA_TOKEN env var)",
|
||||
"zh": "需要 CI_GITEA_TOKEN(设置 --token 或 CI_GITEA_TOKEN 环境变量)"
|
||||
},
|
||||
"GITEA_URL is required (set --url or GITEA_URL env var)": {
|
||||
"bg": "GITEA_URL е задължителен (задайте --url или GITEA_URL env var)",
|
||||
"de": "GITEA_URL ist erforderlich (setzen Sie --url oder GITEA_URL env var)",
|
||||
"en": "GITEA_URL is required (set --url or GITEA_URL env var)",
|
||||
"pl": "GITEA_URL jest wymagany (ustaw --url lub GITEA_URL env var)",
|
||||
"ru": "GITEA_URL обязателен (установите --url или GITEA_URL env var)",
|
||||
"zh": "需要 GITEA_URL(设置 --url 或 GITEA_URL 环境变量)"
|
||||
},
|
||||
"Git ref to run the workflow on (default: master)": {
|
||||
"bg": "Git ref за изпълнение на работния процес (по подразбиране: master)",
|
||||
"de": "Git-Ref für die Workflow-Ausführung (Standard: master)",
|
||||
"en": "Git ref to run the workflow on (default: master)",
|
||||
"pl": "Git ref do uruchomienia workflow (domyślnie: master)",
|
||||
"ru": "Git ref для запуска workflow (по умолчанию: master)",
|
||||
"zh": "运行工作流的 Git ref(默认:master)"
|
||||
},
|
||||
"Gitea API token (env: CI_GITEA_TOKEN)": {
|
||||
"bg": "Gitea API токен (env: CI_GITEA_TOKEN)",
|
||||
"de": "Gitea API-Token (env: CI_GITEA_TOKEN)",
|
||||
"en": "Gitea API token (env: CI_GITEA_TOKEN)",
|
||||
"pl": "Token API Gitea (env: CI_GITEA_TOKEN)",
|
||||
"ru": "Токен API Gitea (env: CI_GITEA_TOKEN)",
|
||||
"zh": "Gitea API 令牌(环境变量:CI_GITEA_TOKEN)"
|
||||
},
|
||||
"List available workflows instead of triggering one": {
|
||||
"bg": "Списък на наличните работни процеси вместо изпълнение",
|
||||
"de": "Verfügbare Workflows auflisten statt auszuführen",
|
||||
"en": "List available workflows instead of triggering one",
|
||||
"pl": "Wyświetl dostępne workflow zamiast uruchamiać",
|
||||
"ru": "Список доступных workflow вместо запуска",
|
||||
"zh": "列出可用工作流而不是触发"
|
||||
},
|
||||
"No workflows found in {repo}": {
|
||||
"bg": "Няма намерени работни процеси в {repo}",
|
||||
"de": "Keine Workflows in {repo} gefunden",
|
||||
"en": "No workflows found in {repo}",
|
||||
"pl": "Nie znaleziono workflow w {repo}",
|
||||
"ru": "В {repo} не найдено workflow",
|
||||
"zh": "在 {repo} 中未找到工作流"
|
||||
},
|
||||
"Repository in owner/repo format (env: GRM_REPO, default: oblachno-oss/grm)": {
|
||||
"bg": "Хранилище във формат owner/repo (env: GRM_REPO, по подразбиране: oblachno-oss/grm)",
|
||||
"de": "Repository im owner/repo-Format (env: GRM_REPO, Standard: oblachno-oss/grm)",
|
||||
"en": "Repository in owner/repo format (env: GRM_REPO, default: oblachno-oss/grm)",
|
||||
"pl": "Repozytorium w formacie owner/repo (env: GRM_REPO, domyślnie: oblachno-oss/grm)",
|
||||
"ru": "Репозиторий в формате owner/repo (env: GRM_REPO, по умолчанию: oblachno-oss/grm)",
|
||||
"zh": "仓库格式为 owner/repo(环境变量:GRM_REPO,默认:oblachno-oss/grm)"
|
||||
},
|
||||
"Trigger a Gitea Actions workflow via the API.": {
|
||||
"bg": "Стартиране на Gitea Actions работен процес чрез API.",
|
||||
"de": "Einen Gitea Actions-Workflow über die API auslösen.",
|
||||
"en": "Trigger a Gitea Actions workflow via the API.",
|
||||
"pl": "Uruchom workflow Gitea Actions przez API.",
|
||||
"ru": "Запустить workflow Gitea Actions через API.",
|
||||
"zh": "通过 API 触发 Gitea Actions 工作流。"
|
||||
},
|
||||
"Triggering workflow {wf} on {repo}@{ref}...": {
|
||||
"bg": "Стартиране на работен процес {wf} в {repo}@{ref}...",
|
||||
"de": "Workflow {wf} auf {repo}@{ref} wird ausgelöst...",
|
||||
"en": "Triggering workflow {wf} on {repo}@{ref}...",
|
||||
"pl": "Uruchamianie workflow {wf} na {repo}@{ref}...",
|
||||
"ru": "Запуск workflow {wf} на {repo}@{ref}...",
|
||||
"zh": "正在触发工作流 {wf} 于 {repo}@{ref}..."
|
||||
},
|
||||
"WORKFLOW_ID is required unless --list is used": {
|
||||
"bg": "WORKFLOW_ID е задължителен, освен ако не се използва --list",
|
||||
"de": "WORKFLOW_ID ist erforderlich, es sei denn --list wird verwendet",
|
||||
"en": "WORKFLOW_ID is required unless --list is used",
|
||||
"pl": "WORKFLOW_ID jest wymagany, chyba że użyto --list",
|
||||
"ru": "WORKFLOW_ID обязателен, если не используется --list",
|
||||
"zh": "除非使用 --list,否则需要 WORKFLOW_ID"
|
||||
},
|
||||
"Workflow triggered successfully.": {
|
||||
"bg": "Работният процес е стартиран успешно.",
|
||||
"de": "Workflow erfolgreich ausgelöst.",
|
||||
"en": "Workflow triggered successfully.",
|
||||
"pl": "Workflow uruchomiony pomyślnie.",
|
||||
"ru": "Workflow успешно запущен.",
|
||||
"zh": "工作流触发成功。"
|
||||
},
|
||||
"Workflow triggered successfully. Run ID: {run_id}": {
|
||||
"bg": "Работният процес е стартиран успешно. ID на изпълнение: {run_id}",
|
||||
"de": "Workflow erfolgreich ausgelöst. Run-ID: {run_id}",
|
||||
"en": "Workflow triggered successfully. Run ID: {run_id}",
|
||||
"pl": "Workflow uruchomiony pomyślnie. ID uruchomienia: {run_id}",
|
||||
"ru": "Workflow успешно запущен. ID запуска: {run_id}",
|
||||
"zh": "工作流触发成功。运行 ID:{run_id}"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user