Files
rdgen/rdgenerator/build_backends/github.py
T
naeeo 4fd930aa0b feat: 同步构建真实状态并支持在列表停止在途任务
- Gitea 运行未结束时探测作业级结论,修复前置作业失败后可复用工作流父作业/等待标签作业长期挂起导致状态永远构建中

- 打开我的构建/管理仪表盘时主动向构建平台同步在途任务状态;创建超 6 小时仍在途的任务判超时

- 构建中任务列表增加停止按钮(Gitea 需 1.28+ 取消 API,旧版本提示去运行页手动 Cancel),终态任务保留删除

- GitHub/Gitea 后端统一 cancel_run 接口
2026-09-29 23:45:25 +08:00

126 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""GitHub Actions 构建后端(迁自 views.py / web_views.py 的原有逻辑,行为保持一致)。"""
from .. import app_settings, ghnet
from .base import BackendError, DispatchResult, RunInfo, WORKFLOW_FILES, BuildBackend
API = 'https://api.github.com'
# 未进入 completed 的状态一律视为进行中
_GITHUB_ACTIVE = {'queued', 'in_progress', 'waiting', 'requested', 'pending'}
class GitHubBackend(BuildBackend):
name = 'github'
label = 'GitHub'
# ---- 配置 ----
def configured(self):
return bool(app_settings.get_value('GHUSER').strip()) \
and bool(app_settings.get_value('GHBEARER').strip())
def warnings(self):
warnings = []
if not app_settings.get_value('GHUSER').strip():
warnings.append('GitHub 用户名未配置,无法触发构建。')
if not app_settings.get_value('GHBEARER').strip():
warnings.append('GitHub 访问令牌未配置,无法触发构建。')
warnings.extend(self._shared_warnings(include_sh_secret=True))
return warnings
# ---- 工作流选择(windows selfhosted 走 sh-generator-windows.yml) ----
def workflow_file_for(self, platform, selfhosted=False):
if selfhosted and platform in ('windows',):
return 'sh-generator-windows.yml'
return WORKFLOW_FILES.get(platform, 'generator-windows.yml')
# ---- 触发 ----
def dispatch(self, workflow_file, inputs, *, timeout=20):
user = app_settings.get_value('GHUSER')
repo = app_settings.get_value('REPONAME')
url = f'{API}/repos/{user}/{repo}/actions/workflows/{workflow_file}/dispatches'
payload = {
'ref': app_settings.get_value('GHBRANCH'),
'inputs': inputs,
'return_run_details': True,
}
token = app_settings.get_value('GHBEARER')
try:
resp = ghnet.post(url, json=payload, token=token, timeout=timeout)
except Exception as e:
raise BackendError(f'无法连接 GitHub 构建服务:{e}', kind='network')
if resp.status_code not in (200, 204):
raise BackendError('GitHub 构建服务拒绝了启动请求,请检查服务配置或稍后重试。', kind='rejected')
data = {}
try:
data = resp.json()
except ValueError:
pass
return DispatchResult(run_id=data.get('workflow_run_id'), html_url=data.get('html_url') or '')
# ---- 状态查询 ----
def get_run(self, run_id, *, timeout=12):
user = app_settings.get_value('GHUSER')
repo = app_settings.get_value('REPONAME')
url = f'{API}/repos/{user}/{repo}/actions/runs/{run_id}'
try:
resp = ghnet.get(url, token=app_settings.get_value('GHBEARER'), timeout=timeout)
except Exception as e:
print(f'查询 GitHub 状态出错:{e}')
return None
if resp.status_code != 200:
return None
data = resp.json()
if data.get('status') == 'completed':
return RunInfo(finished=True, status=data.get('conclusion') or 'failure',
html_url=data.get('html_url') or self.log_url(run_id))
return RunInfo(finished=False, status=data.get('status') or 'in_progress',
html_url=data.get('html_url') or self.log_url(run_id))
# ---- 取消运行 ----
def cancel_run(self, run_id, *, timeout=15):
user = app_settings.get_value('GHUSER')
repo = app_settings.get_value('REPONAME')
url = f'{API}/repos/{user}/{repo}/actions/runs/{run_id}/cancel'
try:
resp = ghnet.post(url, token=app_settings.get_value('GHBEARER'), timeout=timeout)
except Exception as e:
raise BackendError(f'无法连接 GitHub 构建服务:{e}', kind='network')
if 200 <= resp.status_code < 300:
return
if resp.status_code == 409:
raise BackendError('该构建运行已经结束,无法取消。', kind='finished')
raise BackendError(f'GitHub 拒绝了取消请求(HTTP {resp.status_code})。')
# ---- 日志链接 ----
def log_url(self, run_id):
if not run_id:
return ''
return (
f"https://github.com/{app_settings.get_value('GHUSER')}"
f"/{app_settings.get_value('REPONAME')}/actions/runs/{run_id}"
)
# ---- 连接测试 ----
def test_connection(self):
user = app_settings.get_value('GHUSER').strip()
repo = app_settings.get_value('REPONAME').strip()
token = app_settings.get_value('GHBEARER').strip()
if not user or not token:
return {'ok': False, 'message': 'GitHub 用户名或访问令牌为空,请先保存配置。'}
try:
resp = ghnet.get(f'{API}/repos/{user}/{repo}', token=token, timeout=10)
if resp.status_code == 401:
return {'ok': False, 'message': '令牌无效或已过期(GitHub 返回 401)。'}
if resp.status_code == 404:
return {'ok': False,
'message': f'找不到仓库 {user}/{repo},或令牌无权访问该仓库(请确认仓库名与令牌的 Repository access)。'}
if resp.status_code != 200:
return {'ok': False, 'message': f'GitHub 返回异常状态码:{resp.status_code}。'}
act = ghnet.get(f'{API}/repos/{user}/{repo}/actions/workflows', token=token, timeout=10)
if act.status_code == 200:
return {'ok': True, 'message': f'连接正常:令牌可访问仓库 {user}/{repo},且具备 Actions 权限。'}
return {'ok': False,
'message': '可以访问仓库,但令牌缺少 Actions 权限;请编辑令牌,授予 Actions 与 Workflows 的读写权限。'}
except Exception as e:
return {'ok': False,
'message': f'无法连接 GitHub API:{e}(如服务器在国内网络,请在下方配置代理后重试)'}