feat: 云厂商SDK适配层(Vultr/DO/Cloudflare/阿里云/腾讯云)+ 同步服务 + 测试连接/同步接口与前端

This commit is contained in:
gouki
2026-08-02 15:56:48 +00:00
parent cc7b2c67fe
commit 1aacf51bea
13 changed files with 930 additions and 3 deletions
+84
View File
@@ -0,0 +1,84 @@
"""Vultr 适配器(REST API v2Bearer Token
API 文档:https://www.vultr.com/api/
所需配置:{"api_key": "..."}
"""
import httpx
from app.adapters.base import (
AccountInfo,
BaseAdapter,
NormalizedDomain,
NormalizedVPS,
)
from app.adapters.registry import register
@register("vultr-api")
class VultrAdapter(BaseAdapter):
required_config = ["api_key"]
BASE = "https://api.vultr.com/v2"
def _headers(self) -> dict:
return {"Authorization": f"Bearer {self.config.get('api_key', '')}"}
def _get(self, path: str) -> dict:
with httpx.Client(timeout=30) as client:
resp = client.get(self.BASE + path, headers=self._headers())
resp.raise_for_status()
return resp.json()
def test_connection(self) -> dict:
try:
data = self._get("/account")
balance = data.get("account", {}).get("balance")
return {"ok": True, "message": f"连接成功,账户余额 {balance} USD"}
except httpx.HTTPStatusError as e:
return {"ok": False, "message": f"HTTP {e.response.status_code}API Key 无效或权限不足"}
except Exception as e: # noqa: BLE001
return {"ok": False, "message": str(e)}
def get_account(self) -> AccountInfo:
acc = self._get("/account").get("account", {})
return AccountInfo(
balance=acc.get("balance"),
currency="USD",
pending_charges=acc.get("pending_charges"),
raw=acc,
)
def list_vps(self) -> list:
data = self._get("/instances")
result = []
for inst in data.get("instances", []):
ram_mb = inst.get("ram") or 0
result.append(
NormalizedVPS(
external_id=inst.get("id"),
name=inst.get("label") or inst.get("id"),
ip_address=inst.get("main_ip"),
region=inst.get("region"),
os=inst.get("os"),
cpu_cores=inst.get("vcpu_count"),
memory_gb=round(ram_mb / 1024, 1) if ram_mb else None,
disk_gb=inst.get("disk"),
status="active" if inst.get("status") == "active" else (inst.get("status") or "unknown"),
monthly_cost=inst.get("monthly_cost"),
currency="USD",
raw=inst,
)
)
return result
def list_domains(self) -> list:
data = self._get("/domains")
return [
NormalizedDomain(
external_id=d.get("domain"),
domain_name=d.get("domain"),
registrar="vultr",
raw=d,
)
for d in data.get("domains", [])
]