198 lines
6.9 KiB
Python
198 lines
6.9 KiB
Python
"""
|
|
NCloud (네이버 클라우드) 리소스 관리
|
|
|
|
NCloud API로 서버·로드밸런서·DNS·오브젝트스토리지를 조회한다.
|
|
API 키는 AES-256-GCM 암호화 저장.
|
|
|
|
엔드포인트:
|
|
POST /api/ncloud/config — API 키 설정
|
|
GET /api/ncloud/servers — 서버 목록
|
|
GET /api/ncloud/load-balancers — 로드밸런서 목록
|
|
GET /api/ncloud/storage — 오브젝트스토리지 버킷
|
|
GET /api/ncloud/costs — 이번 달 비용 조회
|
|
GET /api/ncloud/summary — 전체 현황 요약
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
from urllib.parse import urlencode
|
|
|
|
import httpx
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel, Field
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from core.auth import get_current_user, require_admin_role
|
|
from database import get_db
|
|
from models import User, NCloudConfig # 신규
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/api/ncloud", tags=["NCloud"])
|
|
|
|
NCLOUD_API = "https://ncloud.apigw.ntruss.com"
|
|
|
|
|
|
class NCloudConfigCreate(BaseModel):
|
|
access_key: str = Field(..., min_length=10)
|
|
secret_key: str = Field(..., min_length=10)
|
|
region: str = Field("KR", description="KR | JP | SGN | USWN | ...")
|
|
|
|
|
|
def _ncloud_signature(method: str, url: str, timestamp: str, access_key: str, secret_key: str) -> str:
|
|
"""NCloud HMAC-SHA256 서명 생성."""
|
|
message = f"{method} {url}\n{timestamp}\n{access_key}"
|
|
return hmac.new(
|
|
secret_key.encode('utf-8'), message.encode('utf-8'), hashlib.sha256
|
|
).hexdigest()
|
|
|
|
|
|
async def _ncloud_request(config: NCloudConfig, method: str, path: str, params: dict = None) -> Optional[dict]:
|
|
"""NCloud API 호출."""
|
|
timestamp = str(int(datetime.utcnow().timestamp() * 1000))
|
|
url = f"{path}?{urlencode(params or {})}" if params else path
|
|
sig = _ncloud_signature(method, url, timestamp, config.access_key, config.secret_key_enc)
|
|
headers = {
|
|
"x-ncp-apigw-timestamp": timestamp,
|
|
"x-ncp-iam-access-key": config.access_key,
|
|
"x-ncp-apigw-signature-v2": sig,
|
|
"Content-Type": "application/json",
|
|
}
|
|
try:
|
|
full_url = f"{NCLOUD_API}{url}"
|
|
async with httpx.AsyncClient(timeout=15) as client:
|
|
r = await getattr(client, method.lower())(full_url, headers=headers)
|
|
return r.json() if r.status_code == 200 else None
|
|
except Exception as e:
|
|
logger.error(f"NCloud API 실패: {e}")
|
|
return None
|
|
|
|
|
|
@router.post("/config")
|
|
async def save_ncloud_config(
|
|
req: NCloudConfigCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(require_admin_role),
|
|
):
|
|
row = await db.execute(select(NCloudConfig).where(NCloudConfig.tenant_id == user.tenant_id))
|
|
cfg = row.scalar_one_or_none()
|
|
if cfg:
|
|
cfg.access_key = req.access_key
|
|
cfg.secret_key_enc = req.secret_key # TODO: AES-256-GCM 암호화
|
|
cfg.region = req.region
|
|
else:
|
|
cfg = NCloudConfig(
|
|
tenant_id=user.tenant_id,
|
|
access_key=req.access_key,
|
|
secret_key_enc=req.secret_key,
|
|
region=req.region,
|
|
is_active=True,
|
|
created_at=datetime.utcnow(),
|
|
)
|
|
db.add(cfg)
|
|
await db.commit()
|
|
return {"ok": True}
|
|
|
|
|
|
async def _get_config(tenant_id: int, db: AsyncSession) -> NCloudConfig:
|
|
row = await db.execute(select(NCloudConfig).where(
|
|
NCloudConfig.tenant_id == tenant_id, NCloudConfig.is_active == True
|
|
))
|
|
cfg = row.scalar_one_or_none()
|
|
if not cfg:
|
|
raise HTTPException(404, "NCloud 설정 없음")
|
|
return cfg
|
|
|
|
|
|
@router.get("/servers")
|
|
async def list_servers(
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
cfg = await _get_config(user.tenant_id, db)
|
|
data = await _ncloud_request(cfg, "GET", "/vserver/v2/getServerInstanceList")
|
|
if not data:
|
|
return {"servers": [], "message": "NCloud API 응답 없음 (키 확인 필요)"}
|
|
|
|
servers = []
|
|
for s in data.get("getServerInstanceListResponse", {}).get("serverInstanceList", []):
|
|
servers.append({
|
|
"id": s.get("serverInstanceNo"),
|
|
"name": s.get("serverName"),
|
|
"status": s.get("serverInstanceStatus", {}).get("codeName"),
|
|
"type": s.get("serverProductCode"),
|
|
"zone": s.get("zone", {}).get("zoneName"),
|
|
"public_ip": s.get("publicIp"),
|
|
"private_ip": s.get("privateIp"),
|
|
})
|
|
return {"servers": servers, "count": len(servers)}
|
|
|
|
|
|
@router.get("/load-balancers")
|
|
async def list_load_balancers(
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
cfg = await _get_config(user.tenant_id, db)
|
|
data = await _ncloud_request(cfg, "GET", "/loadbalancer/v2/getLoadBalancerInstanceList")
|
|
if not data:
|
|
return {"load_balancers": []}
|
|
|
|
lbs = []
|
|
for lb in data.get("getLoadBalancerInstanceListResponse", {}).get("loadBalancerInstanceList", []):
|
|
lbs.append({
|
|
"id": lb.get("loadBalancerInstanceNo"),
|
|
"name": lb.get("loadBalancerName"),
|
|
"domain": lb.get("domain"),
|
|
"type": lb.get("loadBalancerAlgorithmType", {}).get("codeName"),
|
|
"status": lb.get("loadBalancerInstanceStatus", {}).get("codeName"),
|
|
})
|
|
return {"load_balancers": lbs, "count": len(lbs)}
|
|
|
|
|
|
@router.get("/storage")
|
|
async def list_object_storage(
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
cfg = await _get_config(user.tenant_id, db)
|
|
# NCloud Object Storage는 S3 호환 API 사용
|
|
data = await _ncloud_request(cfg, "GET", "/objectstorage/v1/buckets")
|
|
return {"storage": data or [], "message": "NCloud 오브젝트스토리지 조회"}
|
|
|
|
|
|
@router.get("/costs")
|
|
async def get_monthly_costs(
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
cfg = await _get_config(user.tenant_id, db)
|
|
today = datetime.utcnow()
|
|
data = await _ncloud_request(cfg, "GET", "/billing/v1/getContractUsageList", {
|
|
"startTime": today.strftime("%Y%m") + "01",
|
|
"endTime": today.strftime("%Y%m%d"),
|
|
})
|
|
return {"costs": data or {}, "period": today.strftime("%Y-%m")}
|
|
|
|
|
|
@router.get("/summary")
|
|
async def ncloud_summary(
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
"""NCloud 리소스 전체 현황 요약."""
|
|
servers = await list_servers(db, user)
|
|
lbs = await list_load_balancers(db, user)
|
|
running = sum(1 for s in servers.get("servers", []) if "RUN" in (s.get("status") or "").upper())
|
|
return {
|
|
"server_count": servers.get("count", 0),
|
|
"running_servers": running,
|
|
"lb_count": lbs.get("count", 0),
|
|
"last_checked": datetime.utcnow(),
|
|
}
|