"""GitHub Actions 构建后端(迁自 views.py / web_views.py 的原有逻辑,行为保持一致)。 注意:本平台不主动取消 GitHub 上的运行,停止/删除只作用于本平台本地记录。 构建状态以工作流结束时的回调(update_github_run)为第一来源;get_run 仅做 只读终态核对,兜底回调丢失(脚本未跑到收尾或 GENURL 不可达)的情况。 """ from .. import app_settings, ghnet from .base import BackendError, DispatchResult, RunInfo, WORKFLOW_FILES, BuildBackend API = 'https://api.github.com' # GitHub conclusion -> rdgen 内部终态;无人值守构建不会有人处理审批, # action_required/neutral/stale/startup_failure 等异常结论一律按失败呈现 _GH_CONCLUSION_MAP = { 'success': 'success', 'failure': 'failure', 'cancelled': 'cancelled', 'skipped': 'skipped', 'timed_out': 'timed_out', } 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=8): 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 try: data = resp.json() except ValueError: return None # 只关心终态:在途(queued/in_progress/waiting)返回 None,交给回调与下次核对 if data.get('status') != 'completed': return None conclusion = (data.get('conclusion') or 'failure').lower() return RunInfo( finished=True, status=_GH_CONCLUSION_MAP.get(conclusion, 'failure'), html_url=data.get('html_url') or self.log_url(run_id), ) # ---- 日志链接 ---- 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}(如服务器在国内网络,请在下方配置代理后重试)'}