182 lines
7.1 KiB
Python
182 lines
7.1 KiB
Python
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"],
|
||
}
|