ForcePilot/backend/package/yuxi/channel/extensions/generic_webhook/config.py

182 lines
7.1 KiB
Python
Raw Normal View History

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"],
}