# api/ecs.py from typing import List, Dict from fastapi import APIRouter, HTTPException, Query from pydantic import BaseModel, Field from services.ecs_service import ( create_ecs_instances, get_account_credit, list_custom_and_shared_images, list_vpcs_and_vswitches, ) from utils.validators import ( is_valid_customer_name, is_valid_resource_name, is_valid_region, ) router = APIRouter() class ImageItem(BaseModel): ImageId: str = Field(..., description="镜像 ID") ImageName: str = Field(..., description="镜像名称") class CreateInstanceRequest(BaseModel): customer: str resource_name: str region: str access_key: str secret_key: str count: int = 1 account_uid: str | None = None image_id: str zone_id: str | None = None vswitch_id: str | None = None class InstanceResponse(BaseModel): ip: str name: str region: str instance_id: str user: str password: str created: str email: str | None = None ram_user: str ram_password: str image_id: str class CreateInstancesResponse(BaseModel): instances: list[InstanceResponse] @router.get( "/images", response_model=List[ImageItem], summary="查询自建镜像和共享给您的镜像列表", ) def get_images( access_key: str = Query(..., description="用户的 AccessKey"), secret_key: str = Query(..., description="用户的 SecretKey"), ecs_region: str = Query("ap-southeast-1", description="ECS 实例所在区域,仅做展示"), ) -> List[ImageItem]: """ 返回两类镜像: - owner_alias='self' 用户自建镜像 - owner_alias='others' 共享给用户的镜像 """ try: images = list_custom_and_shared_images( access_key, secret_key, ecs_region) return images except Exception as e: raise HTTPException(status_code=500, detail=f"拉取镜像失败:{e}") @router.get("/balance") def balance( access_key: str = Query(..., description="用户的 AccessKey"), secret_key: str = Query(..., description="用户的 SecretKey"), ecs_region: str = Query("ap-southeast-1", description="ECS 实例所在区域,仅做展示"), ) -> Dict[str, float]: """ 查询账户可用余额(AvailableAmount),接口名不变仍叫 /balance """ try: # 调用 get_account_credit 方法查询可用余额 available_amount = get_account_credit( access_key, secret_key, ecs_region) # 返回查询结果,确保返回 float 类型 return {"available_amount": float(available_amount)} except RuntimeError as re: # 捕获具体的运行时错误并返回 400 状态码 raise HTTPException(status_code=400, detail=f"请求失败:{str(re)}") except Exception as e: # 捕获所有其他异常并返回 500 状态码 raise HTTPException(status_code=500, detail=f"查询可用余额失败:{str(e)}") @router.post("/create", response_model=CreateInstancesResponse) def create_instances(data: CreateInstanceRequest): if not is_valid_customer_name(data.customer): raise HTTPException(status_code=400, detail="客户名称不能为空") if not is_valid_resource_name(data.resource_name): raise HTTPException(status_code=400, detail="资源账号名称非法") if not is_valid_region(data.region): raise HTTPException(status_code=400, detail="区域 ID 格式不合法") try: results = create_ecs_instances( customer=data.customer, resource_name=data.resource_name, region=data.region, access_key=data.access_key, secret_key=data.secret_key, count=data.count, account_uid=data.account_uid, image_id=data.image_id, zone_id=data.zone_id, vswitch_id=data.vswitch_id, ) return CreateInstancesResponse(instances=results) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/vswitches") def list_vswitches( access_key: str = Query(..., description="AccessKey"), secret_key: str = Query(..., description="SecretKey"), ecs_region: str = Query(..., description="地域 ID"), ): """ 拉取指定地域下的 VPC 与 VSwitch 列表,供前端选择。 """ try: data = list_vpcs_and_vswitches(access_key, secret_key, ecs_region) return data except Exception as e: raise HTTPException(status_code=500, detail=f"拉取 VPC/VSwitch 失败:{e}")