feat: 云厂商SDK适配层(Vultr/DO/Cloudflare/阿里云/腾讯云)+ 同步服务 + 测试连接/同步接口与前端
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
"""阿里云适配器(含国际版,OpenAPI RPC 风格 + HMAC-SHA1 签名)
|
||||
|
||||
API 文档:https://help.aliyun.com/document_detail/25484.html
|
||||
所需配置:{"access_key_id": "...", "access_key_secret": "...", "region": "cn-hangzhou"}
|
||||
国际版(alibabacloud)只需将 region 设为海外区域(如 ap-southeast-1),端点自动按区域构造。
|
||||
说明:此处实现 RPC V1 签名(HMAC-SHA1),可直接调用 ECS OpenAPI;
|
||||
生产环境也可替换为官方 alibabacloud-ecs SDK,接口契约保持不变。
|
||||
"""
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import time
|
||||
import uuid
|
||||
from urllib.parse import quote
|
||||
|
||||
import httpx
|
||||
|
||||
from app.adapters.base import BaseAdapter, NormalizedVPS
|
||||
from app.adapters.registry import register
|
||||
|
||||
|
||||
@register("aliyun-sdk", "alibabacloud-sdk")
|
||||
class AliyunAdapter(BaseAdapter):
|
||||
required_config = ["access_key_id", "access_key_secret"]
|
||||
API_VERSION = "2014-05-26"
|
||||
|
||||
def _region(self) -> str:
|
||||
return self.config.get("region") or "cn-hangzhou"
|
||||
|
||||
def _endpoint(self) -> str:
|
||||
return f"https://ecs.{self._region()}.aliyuncs.com"
|
||||
|
||||
@staticmethod
|
||||
def _percent_encode(value) -> str:
|
||||
return quote(str(value), safe="~")
|
||||
|
||||
def _sign(self, params: dict, method: str = "GET") -> str:
|
||||
canonical = "&".join(
|
||||
f"{self._percent_encode(k)}={self._percent_encode(v)}"
|
||||
for k, v in sorted(params.items())
|
||||
)
|
||||
string_to_sign = f"{method}&{self._percent_encode('/')}&{self._percent_encode(canonical)}"
|
||||
key = (self.config.get("access_key_secret", "") + "&").encode()
|
||||
digest = hmac.new(key, string_to_sign.encode(), hashlib.sha1).digest()
|
||||
return base64.b64encode(digest).decode()
|
||||
|
||||
def _call(self, action: str, biz_params: dict = None) -> dict:
|
||||
params = {
|
||||
"Format": "JSON",
|
||||
"Version": self.API_VERSION,
|
||||
"AccessKeyId": self.config.get("access_key_id", ""),
|
||||
"SignatureMethod": "HMAC-SHA1",
|
||||
"Timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||
"SignatureVersion": "1.0",
|
||||
"SignatureNonce": str(uuid.uuid4()),
|
||||
"Action": action,
|
||||
"RegionId": self._region(),
|
||||
}
|
||||
if biz_params:
|
||||
params.update(biz_params)
|
||||
params["Signature"] = self._sign(params)
|
||||
with httpx.Client(timeout=30) as client:
|
||||
resp = client.get(self._endpoint(), params=params)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
if data.get("Code"):
|
||||
raise RuntimeError(f"{data.get('Code')}: {data.get('Message')}")
|
||||
return data
|
||||
|
||||
def test_connection(self) -> dict:
|
||||
try:
|
||||
data = self._call("DescribeRegions")
|
||||
count = len(data.get("Regions", {}).get("Region", []))
|
||||
return {"ok": True, "message": f"连接成功,可用区域 {count} 个"}
|
||||
except httpx.HTTPStatusError as e:
|
||||
return {"ok": False, "message": f"HTTP {e.response.status_code}:凭证无效"}
|
||||
except Exception as e: # noqa: BLE001
|
||||
return {"ok": False, "message": str(e)}
|
||||
|
||||
def list_vps(self) -> list:
|
||||
data = self._call("DescribeInstances", {"PageSize": "100"})
|
||||
result = []
|
||||
for inst in data.get("Instances", {}).get("Instance", []):
|
||||
public_ips = inst.get("PublicIpAddress", {}).get("IpAddress", [])
|
||||
eip = inst.get("EipAddress", {}).get("IpAddress")
|
||||
mem_mb = inst.get("Memory") or 0
|
||||
status_map = {"Running": "active", "Stopped": "stopped"}
|
||||
result.append(
|
||||
NormalizedVPS(
|
||||
external_id=inst.get("InstanceId"),
|
||||
name=inst.get("InstanceName") or inst.get("InstanceId"),
|
||||
ip_address=eip or (public_ips[0] if public_ips else None),
|
||||
region=inst.get("RegionId"),
|
||||
os=inst.get("OSName"),
|
||||
cpu_cores=inst.get("Cpu"),
|
||||
memory_gb=round(mem_mb / 1024, 1) if mem_mb else None,
|
||||
status=status_map.get(inst.get("Status"), inst.get("Status") or "unknown"),
|
||||
currency="CNY",
|
||||
raw=inst,
|
||||
)
|
||||
)
|
||||
return result
|
||||
Reference in New Issue
Block a user