feat(adapters): 接入 CloudCone/Zeabur/Dynadot/新网/Agnes 五个平台

线上平台列表里 cloudcone、zeabur、xinwang、dynadot、agnes 的 sdk_type 仍为空,
平台卡片只能显示「未对接」,用户无法同步。按各自真实 API 能力补齐:

- CloudCone:实例清单 + 开机/关机/重启;无公开账单接口,不声明余额与续费
- Zeabur:服务器清单 + 重启;毫核/MB 归一化为核/GB,未采集时不伪造规格
- Dynadot:域名清单(含到期日)+ 账户余额;set_dns2 位置参数无法安全 CRUD
- 新网:代理 API 按域名逐个查到期日;无列表接口,需在账号里填 domains 清单
- Agnes:/models 校验 Key;无余额接口,如实标记不支持

配套改动:
- _align_preset_providers 为库中为空的 sdk_type 回填种子值(启动即生效),
  避免新适配器上线后线上仍停留在「未对接」
- 适配器可声明 plain_config,代理号/域名等非密钥字段前端按明文输入
- PRESET_PROVIDERS 五条记录填上对应 sdk_type

测试:445 passed(新增 tests/test_platform_adapters.py),vitest 62 passed。
This commit is contained in:
gouki
2026-10-26 09:16:45 +00:00
parent 9aa1ca431e
commit 103f48e38b
14 changed files with 892 additions and 32 deletions
+30 -12
View File
@@ -26,8 +26,9 @@ from app.core.seed import PRESET_PROVIDERS, seed_providers
from app.database import _align_preset_providers
from app.models.provider import Provider
#: 线上 GET /api/providers 抓下来的真实状态(2026-10-11),
#: 用它做 fixture 才能保证迁移验证的是生产形态而不是理想形态
#: 线上 GET /api/providers 抓下来的真实状态(2026-10-26),
#: 用它做 fixture 才能保证迁移验证的是生产形态而不是理想形态。
#: cloudcone/zeabur/dynadot/xinwang/agnes 上线前都是 None,靠启动迁移补 sdk_type。
LIVE_PROVIDERS = [
("agnes", None, "ai_agent"),
("deepseek", "deepseek-api", "ai_agent"),
@@ -35,19 +36,19 @@ LIVE_PROVIDERS = [
("minimax", "minimax-api", "ai_agent"),
("openai", "openai-api", "ai_agent"),
("cloudflare", "cloudflare-api", "cloudflare,dns,ssl_cert,cdn"),
("dynadot", "dynadot-api", "domain,dns"),
("dynadot", None, "domain,dns"),
("namesilo", "namesilo-api", "domain,dns"),
("xinwang", None, "domain,dns"),
("aliyun", "aliyun-sdk", "vps,domain,ssl_cert,cdn,dns"),
("aliyun-intl", "alibabacloud-sdk", "vps,domain,ssl_cert,cdn,dns,ai_agent,other"),
("cloudcone", "cloudcone-api", "vps"),
("cloudcone", None, "vps"),
("digitalocean", "do-api", "vps,domain"),
("linode", "linode-api", "vps"),
("tencent", "tencent-sdk", "vps,domain,ssl_cert,cdn,dns"),
("tencent-intl", "tencent-intl-sdk", "vps,domain,ssl_cert,cdn,dns"),
("tothost", None, "vps"),
("tothost", "whmcs-panel", "vps"),
("vultr", "vultr-api", "vps"),
("zeabur", "zeabur-api", "vps,domain"),
("zeabur", None, "vps,domain"),
]
@@ -118,11 +119,25 @@ def test_seed_providers_is_idempotent(live_db):
# ----------------------------- 迁移行为 ----------------------------- #
def test_migration_clears_bogus_sdk_type(live_db):
"""cloudcone/zeabur/dynadot 声明的适配器从未实现,必须清空"""
"""库里声明了注册表没有的 sdk_type(旧版本残留)必须清空"""
with live_db.begin() as conn:
conn.execute(sa.text("UPDATE providers SET sdk_type = 'vultr-v1-removed' WHERE slug = 'vultr'"))
_run_migration(live_db)
assert _snapshot(live_db)["vultr"][0] is None
def test_migration_backfills_missing_sdk_type(live_db):
"""新增适配器上线后,库里 sdk_type 为空的预设平台必须补上,否则卡片永远不可同步"""
_run_migration(live_db)
snap = _snapshot(live_db)
for slug in ("cloudcone", "zeabur", "dynadot"):
assert snap[slug][0] is None, f"{slug}.sdk_type 未清空:{snap[slug][0]!r}"
for slug, expected in (
("cloudcone", "cloudcone-api"),
("zeabur", "zeabur-api"),
("dynadot", "dynadot-api"),
("xinwang", "xinnet-api"),
("agnes", "agnes-api"),
):
assert snap[slug][0] == expected, f"{slug}.sdk_type 未回填:{snap[slug][0]!r}"
def test_migration_keeps_valid_sdk_type(live_db):
@@ -155,10 +170,13 @@ def test_migration_adds_missing_service_tags(live_db):
def test_migration_ignores_user_created_providers(live_db):
"""tothost 不在种子里,一个字都不能动"""
before = _snapshot(live_db)["tothost"]
"""用户自建平台不在种子里,一个字都不能动"""
with Session(live_db) as s:
s.add(Provider(slug="myhost", name="MyHost", category="vps", services="vps"))
s.commit()
before = _snapshot(live_db)["myhost"]
_run_migration(live_db)
assert _snapshot(live_db)["tothost"] == before
assert _snapshot(live_db)["myhost"] == before
def test_migration_is_idempotent(live_db):