实现了 GitLab 全功能集成,包含 Webhook 处理、API 客户端、消息收发、会话管理、安全校验、限流器等完整模块,支持 Issues、Merge Requests、流水线等事件监听与交互。
196 lines
7.9 KiB
Python
196 lines
7.9 KiB
Python
from __future__ import annotations
|
||
|
||
import hashlib
|
||
import logging
|
||
import os
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class GitLabConfigAdapter:
|
||
def __init__(self):
|
||
self._config: dict = {}
|
||
|
||
@property
|
||
def _gl_cfg(self) -> dict:
|
||
return self._config.get("channels", {}).get("gitlab", {})
|
||
|
||
def list_account_ids(self, config: dict) -> list[str]:
|
||
self._config = config
|
||
accounts = self._gl_cfg.get("accounts", {})
|
||
if accounts:
|
||
return list(accounts.keys())
|
||
if os.environ.get("GITLAB_PRIVATE_TOKEN"):
|
||
return ["default"]
|
||
return []
|
||
|
||
async def resolve_account(self, account_id: str) -> dict:
|
||
return self._build_account(account_id)
|
||
|
||
def is_configured(self, account: dict) -> bool:
|
||
return bool(
|
||
account.get("base_url")
|
||
and account.get("private_token")
|
||
and account.get("webhook_token")
|
||
and account.get("project_id")
|
||
)
|
||
|
||
def is_enabled(self, account: dict) -> bool:
|
||
return account.get("enabled", True)
|
||
|
||
def disabled_reason(self, account: dict) -> str:
|
||
if not self.is_configured(account):
|
||
return "GitLab 账户未完整配置: base_url, private_token, webhook_token, project_id 均为必填"
|
||
return ""
|
||
|
||
async def resolve_allow_from(self, config: dict, account_id: str) -> list[str] | None:
|
||
self._config = config
|
||
account = self._build_account(account_id)
|
||
return account.get("allow_from", [])
|
||
|
||
def describe_account(self, account: dict) -> dict:
|
||
return {
|
||
"account_id": account.get("account_id", ""),
|
||
"name": account.get("name", ""),
|
||
"base_url": account.get("base_url", ""),
|
||
"project_id": account.get("project_id", 0),
|
||
"project_path": account.get("project_path", ""),
|
||
"bot_username": account.get("bot_username", ""),
|
||
"dm_policy": account.get("dm_policy", "open"),
|
||
"configured": self.is_configured(account),
|
||
}
|
||
|
||
def default_account_id(self) -> str:
|
||
return self._gl_cfg.get("defaultAccount", "default")
|
||
|
||
@staticmethod
|
||
def credential_fingerprint(base_url: str, token: str) -> str:
|
||
return hashlib.sha256(f"{base_url}:{token}".encode()).hexdigest()[:8]
|
||
|
||
def _build_account(self, account_id: str) -> dict:
|
||
gl_cfg = self._gl_cfg
|
||
accounts = gl_cfg.get("accounts", {})
|
||
raw = accounts.get(account_id, {}) if account_id != "default" else gl_cfg
|
||
|
||
suffix = f"_{account_id.upper()}" if account_id != "default" else ""
|
||
|
||
def _env_or(name: str, default=None):
|
||
return os.environ.get(f"GITLAB_{name}{suffix}", default)
|
||
|
||
def _get(key: str, env_key: str | None = None, default=None):
|
||
val = raw.get(key, gl_cfg.get(key))
|
||
if not val and env_key:
|
||
val = _env_or(env_key)
|
||
return val if val else default
|
||
|
||
return {
|
||
"account_id": account_id,
|
||
"base_url": _get("baseUrl", "BASE_URL", "https://gitlab.com"),
|
||
"private_token": _get("privateToken", "PRIVATE_TOKEN", ""),
|
||
"webhook_token": _get("webhookToken", "WEBHOOK_TOKEN", ""),
|
||
"webhook_signing_secret": _get("webhookSigningSecret", "WEBHOOK_SIGNING_SECRET", ""),
|
||
"webhook_path": _get("webhookPath", None, "/api/webhook/gitlab"),
|
||
"bot_username": _get("botUsername", "BOT_USERNAME", ""),
|
||
"project_id": int(_get("projectId", "PROJECT_ID", 0) or 0),
|
||
"project_path": _get("projectPath", "PROJECT_PATH", ""),
|
||
"listen_issues": _get("listenIssues", None, True),
|
||
"listen_merge_requests": _get("listenMergeRequests", None, True),
|
||
"listen_notes": _get("listenNotes", None, True),
|
||
"listen_pipelines": _get("listenPipelines", None, True),
|
||
"listen_deployments": _get("listenDeployments", None, True),
|
||
"ssl_verify": _get("sslVerify", None, True),
|
||
"ca_bundle_path": _get("caBundlePath", None, ""),
|
||
"dm_policy": _get("dmPolicy", None, "open"),
|
||
"allow_from": raw.get("allowFrom", gl_cfg.get("allowFrom", [])),
|
||
"text_chunk_limit": int(_get("textChunkLimit", None, 65000) or 65000),
|
||
"streaming_mode": _get("streamingMode", None, "block"),
|
||
"name": raw.get("name", f"GitLab-{account_id}"),
|
||
"enabled": raw.get("enabled", gl_cfg.get("enabled", True)),
|
||
}
|
||
|
||
def config_schema(self) -> dict:
|
||
return {
|
||
"$schema": "https://json-schema.org/draft-07/schema#",
|
||
"type": "object",
|
||
"title": "GitLab 渠道配置",
|
||
"properties": {
|
||
"baseUrl": {
|
||
"type": "string",
|
||
"title": "GitLab 实例地址",
|
||
"description": "GitLab 实例的 Base URL,SaaS 模式为 https://gitlab.com,自托管为 https://your-gitlab.example.com",
|
||
},
|
||
"privateToken": {
|
||
"type": "string",
|
||
"title": "Access Token (PAT / Project Access Token)",
|
||
"description": "GitLab Personal Access Token 或 Project Access Token,需要 api scope",
|
||
"x-ui-password": True,
|
||
},
|
||
"webhookToken": {
|
||
"type": "string",
|
||
"title": "Webhook Secret Token",
|
||
"description": "用于验证 X-Gitlab-Token Header 的密钥",
|
||
"x-ui-password": True,
|
||
},
|
||
"botUsername": {
|
||
"type": "string",
|
||
"title": "Bot GitLab 用户名",
|
||
"description": "用于过滤 Bot 自身消息,防止无限循环",
|
||
},
|
||
"projectId": {
|
||
"type": "integer",
|
||
"title": "项目 ID",
|
||
"description": "GitLab 项目的全局 ID(非 iid),在项目首页可见",
|
||
},
|
||
"projectPath": {
|
||
"type": "string",
|
||
"title": "项目路径",
|
||
"description": "如 group/project",
|
||
},
|
||
"listenIssues": {
|
||
"type": "boolean",
|
||
"title": "监听 Issue 事件",
|
||
"default": True,
|
||
},
|
||
"listenMergeRequests": {
|
||
"type": "boolean",
|
||
"title": "监听 MR 事件",
|
||
"default": True,
|
||
},
|
||
"listenNotes": {
|
||
"type": "boolean",
|
||
"title": "监听评论事件",
|
||
"default": True,
|
||
},
|
||
"listenPipelines": {
|
||
"type": "boolean",
|
||
"title": "监听 Pipeline 事件",
|
||
"default": True,
|
||
},
|
||
"listenDeployments": {
|
||
"type": "boolean",
|
||
"title": "监听 Deployment 事件",
|
||
"default": True,
|
||
},
|
||
"sslVerify": {
|
||
"type": "boolean",
|
||
"title": "SSL 证书验证",
|
||
"description": "自托管实例可能使用自签名证书,内网可关闭",
|
||
"default": True,
|
||
},
|
||
"dmPolicy": {
|
||
"type": "string",
|
||
"title": "消息安全策略",
|
||
"enum": ["open", "pairing", "allowlist", "disabled"],
|
||
"default": "open",
|
||
},
|
||
"textChunkLimit": {
|
||
"type": "integer",
|
||
"title": "文本分块上限",
|
||
"default": 65000,
|
||
"minimum": 100,
|
||
"maximum": 950000,
|
||
},
|
||
},
|
||
"required": ["baseUrl", "privateToken", "webhookToken", "projectId"],
|
||
}
|