import json import logging import os from yuxi.channel.extensions.generic_webhook.types import ( AuthConfig, AuthType, EndpointConfig, MappingRule, OutboundConfig, ) logger = logging.getLogger("yuxi.channel.generic_webhook.config") class EndpointConfigManager: ENV_ENDPOINTS = "GENERIC_WEBHOOK_ENDPOINTS" ENV_PREFIX = "GENERIC_WEBHOOK_" def __init__(self): self._endpoints: dict[str, EndpointConfig] = {} self._load_from_env() def list_endpoint_ids(self) -> list[str]: return list(self._endpoints.keys()) def get_endpoint(self, endpoint_id: str) -> EndpointConfig | None: return self._endpoints.get(endpoint_id) def get_all_endpoints(self) -> list[EndpointConfig]: return list(self._endpoints.values()) def add_endpoint(self, config: EndpointConfig): if config.endpoint_id in self._endpoints: logger.warning("端点 %s 已存在,覆盖", config.endpoint_id) self._endpoints[config.endpoint_id] = config logger.info("端点 %s 已注册", config.endpoint_id) def remove_endpoint(self, endpoint_id: str) -> bool: if endpoint_id in self._endpoints: del self._endpoints[endpoint_id] return True return False def update_endpoint(self, endpoint_id: str, **kwargs) -> EndpointConfig | None: config = self._endpoints.get(endpoint_id) if config is None: return None for key, value in kwargs.items(): if hasattr(config, key): setattr(config, key, value) return config def _load_from_env(self): endpoints_json = os.getenv(self.ENV_ENDPOINTS, "") if endpoints_json: try: configs = json.loads(endpoints_json) for cfg in configs: endpoint = self._parse_endpoint(cfg) self._endpoints[endpoint.endpoint_id] = endpoint logger.info("从环境变量加载了 %d 个端点", len(configs)) except (json.JSONDecodeError, TypeError) as e: logger.error("解析 GENERIC_WEBHOOK_ENDPOINTS 失败: %s", e) return endpoint_id = os.getenv(f"{self.ENV_PREFIX}ENDPOINT_ID", "") if endpoint_id: cfg = { "endpoint_id": endpoint_id, "label": os.getenv(f"{self.ENV_PREFIX}LABEL", endpoint_id), "auth_type": os.getenv(f"{self.ENV_PREFIX}AUTH_TYPE", "none"), "api_key": os.getenv(f"{self.ENV_PREFIX}API_KEY", ""), "hmac_secret": os.getenv(f"{self.ENV_PREFIX}HMAC_SECRET", ""), "outbound_url": os.getenv(f"{self.ENV_PREFIX}OUTBOUND_URL", ""), "outbound_template": os.getenv(f"{self.ENV_PREFIX}OUTBOUND_TEMPLATE", ""), } endpoint = self._parse_endpoint(cfg) self._endpoints[endpoint.endpoint_id] = endpoint def _parse_endpoint(self, cfg: dict) -> EndpointConfig: auth_cfg = AuthConfig( type=AuthType(cfg.get("auth_type", "none")), api_key=cfg.get("api_key", ""), api_key_header=cfg.get("api_key_header", "X-API-Key"), hmac_secret=cfg.get("hmac_secret", ""), hmac_header=cfg.get("hmac_header", "X-Signature-256"), custom_header_name=cfg.get("custom_header_name", ""), custom_header_value=cfg.get("custom_header_value", ""), ) mapping = MappingRule( msg_id=cfg.get("msg_id_jsonpath", "$.id"), sender_id=cfg.get("sender_id_jsonpath", "$.sender.id"), sender_name=cfg.get("sender_name_jsonpath", "$.sender.name"), content=cfg.get("content_jsonpath", "$.content"), message_type=cfg.get("message_type", "text"), defaults=cfg.get("defaults", {}), ) outbound_cfg = None if cfg.get("outbound_url"): outbound_cfg = OutboundConfig( url=cfg.get("outbound_url", ""), method=cfg.get("outbound_method", "POST"), content_type=cfg.get("outbound_content_type", "application/json"), auth_type=cfg.get("outbound_auth_type", "none"), auth_token=cfg.get("outbound_auth_token", ""), payload_template=cfg.get("outbound_template", ""), ) return EndpointConfig( endpoint_id=cfg["endpoint_id"], label=cfg.get("label", cfg["endpoint_id"]), mapping=mapping, auth=auth_cfg, outbound=outbound_cfg, dm_policy=cfg.get("dm_policy", "open"), allow_from=cfg.get("allow_from", []), ) def config_schema(self) -> dict: return { "type": "object", "title": "Generic Webhook 端点配置", "description": "通用 Webhook 管道端点配置(JSONPath 映射规则 + 认证 + 出站)", "properties": { "endpoint_id": { "type": "string", "title": "端点 ID", "description": "唯一标识,URL 路径参数,如 ci-jenkins、monitor-grafana", "pattern": "^[a-zA-Z0-9_-]+$", }, "label": { "type": "string", "title": "显示名称", "description": "前端展示的友好名称,如 'Jenkins CI Pipeline'", }, "auth_type": { "type": "string", "title": "认证方式", "enum": ["none", "api_key", "hmac_sha256", "custom_header"], "default": "none", }, "api_key": { "type": "string", "title": "API Key", "format": "password", "description": "当 auth_type=api_key 时的预共享密钥", }, "hmac_secret": { "type": "string", "title": "HMAC Secret", "format": "password", "description": "当 auth_type=hmac_sha256 时的预共享密钥", }, "msg_id_jsonpath": { "type": "string", "title": "消息 ID JSONPath", "default": "$.id", "description": "JSONPath 表达式,提取消息唯一 ID(用于去重)", }, "sender_id_jsonpath": { "type": "string", "title": "发送者 ID JSONPath", "default": "$.sender.id", }, "content_jsonpath": { "type": "string", "title": "内容 JSONPath", "default": "$.content", }, "dm_policy": { "type": "string", "title": "DM 安全策略", "enum": ["open", "pairing", "allowlist", "disabled"], "default": "open", }, }, "required": ["endpoint_id"], }