135 lines
6.1 KiB
Python
135 lines
6.1 KiB
Python
"""Manager Gen6 — 운영 자동화: 노코드 자동화·정책UI·런북 관리·작업 스케줄러"""
|
|
import uuid
|
|
from datetime import datetime
|
|
from typing import Any, Dict, List, Optional
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
from pydantic import BaseModel
|
|
|
|
router = APIRouter(prefix="/api/ops-auto", tags=["Operations Automation"])
|
|
|
|
_automations: Dict[str, Dict] = {}
|
|
_runbooks: Dict[str, Dict] = {}
|
|
_schedules: Dict[str, Dict] = {}
|
|
_executions: List[Dict] = []
|
|
|
|
class AutomationCreate(BaseModel):
|
|
name: str; trigger: str # schedule|event|threshold|manual
|
|
conditions: List[Dict[str, Any]] = []
|
|
actions: List[Dict[str, Any]] = []
|
|
enabled: bool = True
|
|
|
|
class RunbookCreate(BaseModel):
|
|
name: str; description: str = ""; category: str = "incident"
|
|
steps: List[Dict[str, Any]] = []; owner: str = ""
|
|
|
|
class ScheduleCreate(BaseModel):
|
|
name: str; cron: str; automation_id: str; timezone: str = "Asia/Seoul"
|
|
|
|
# ── 노코드 자동화 빌더 ───────────────────────────────────────────────────────
|
|
@router.post("/automations")
|
|
async def create_automation(auto: AutomationCreate):
|
|
aid = f"AUTO-{uuid.uuid4().hex[:8].upper()}"
|
|
_automations[aid] = {**auto.model_dump(), "id": aid,
|
|
"run_count": 0, "last_run": None,
|
|
"created_at": datetime.utcnow().isoformat()}
|
|
return _automations[aid]
|
|
|
|
@router.get("/automations")
|
|
async def list_automations(enabled: Optional[bool] = None):
|
|
autos = list(_automations.values())
|
|
if enabled is not None: autos = [a for a in autos if a["enabled"] == enabled]
|
|
default = [
|
|
{"id": "AUTO-SYS01", "name": "디스크 임계 → 알림", "trigger": "threshold", "enabled": True, "run_count": 42},
|
|
{"id": "AUTO-SYS02", "name": "야간 백업 실행", "trigger": "schedule", "enabled": True, "run_count": 186},
|
|
]
|
|
return {"automations": default + autos, "total": len(default) + len(autos)}
|
|
|
|
@router.get("/automations/{aid}")
|
|
async def get_automation(aid: str):
|
|
a = _automations.get(aid)
|
|
if not a: raise HTTPException(404)
|
|
return a
|
|
|
|
@router.patch("/automations/{aid}/toggle")
|
|
async def toggle_automation(aid: str):
|
|
a = _automations.get(aid)
|
|
if not a: raise HTTPException(404)
|
|
a["enabled"] = not a["enabled"]
|
|
return {"id": aid, "enabled": a["enabled"]}
|
|
|
|
@router.post("/automations/{aid}/run")
|
|
async def run_automation(aid: str, params: Dict[str, Any] = {}):
|
|
a = _automations.get(aid)
|
|
if not a: raise HTTPException(404)
|
|
exec_id = str(uuid.uuid4())
|
|
result = {"exec_id": exec_id, "automation_id": aid, "params": params,
|
|
"status": "completed", "duration_ms": 234,
|
|
"ts": datetime.utcnow().isoformat()}
|
|
_executions.append(result)
|
|
a["run_count"] = a.get("run_count", 0) + 1
|
|
a["last_run"] = datetime.utcnow().isoformat()
|
|
return result
|
|
|
|
@router.get("/automations/executions/history")
|
|
async def execution_history(limit: int = 50):
|
|
return {"executions": _executions[-limit:], "total": len(_executions)}
|
|
|
|
# ── 런북 관리 ────────────────────────────────────────────────────────────────
|
|
@router.post("/runbooks")
|
|
async def create_runbook(rb: RunbookCreate):
|
|
rid = f"RB-{uuid.uuid4().hex[:8].upper()}"
|
|
_runbooks[rid] = {**rb.model_dump(), "id": rid, "version": 1,
|
|
"created_at": datetime.utcnow().isoformat()}
|
|
return _runbooks[rid]
|
|
|
|
@router.get("/runbooks")
|
|
async def list_runbooks(category: Optional[str] = None):
|
|
rbs = list(_runbooks.values())
|
|
if category: rbs = [r for r in rbs if r["category"] == category]
|
|
default = [
|
|
{"id": "RB-001", "name": "서버 재시작 런북", "category": "maintenance", "version": 3, "steps": 5},
|
|
{"id": "RB-002", "name": "장애 대응 런북", "category": "incident", "version": 5, "steps": 8},
|
|
{"id": "RB-003", "name": "배포 롤백 런북", "category": "deploy", "version": 2, "steps": 6},
|
|
]
|
|
if category: default = [r for r in default if r["category"] == category]
|
|
return {"runbooks": default + rbs, "total": len(default) + len(rbs)}
|
|
|
|
@router.post("/runbooks/{rid}/execute")
|
|
async def execute_runbook(rid: str, target: str = "", params: Dict[str, Any] = {}):
|
|
rb = _runbooks.get(rid)
|
|
exec_id = str(uuid.uuid4())
|
|
return {"exec_id": exec_id, "runbook_id": rid, "target": target, "params": params,
|
|
"status": "completed", "steps_executed": 5,
|
|
"ts": datetime.utcnow().isoformat()}
|
|
|
|
# ── 작업 스케줄러 ────────────────────────────────────────────────────────────
|
|
@router.post("/schedules")
|
|
async def create_schedule(sch: ScheduleCreate):
|
|
sid = f"SCH-{uuid.uuid4().hex[:8].upper()}"
|
|
_schedules[sid] = {**sch.model_dump(), "id": sid, "status": "active",
|
|
"next_run": datetime.utcnow().isoformat(),
|
|
"created_at": datetime.utcnow().isoformat()}
|
|
return _schedules[sid]
|
|
|
|
@router.get("/schedules")
|
|
async def list_schedules():
|
|
return {"schedules": list(_schedules.values()), "total": len(_schedules)}
|
|
|
|
@router.delete("/schedules/{sid}")
|
|
async def delete_schedule(sid: str):
|
|
_schedules.pop(sid, None)
|
|
return {"deleted": sid}
|
|
|
|
# ── 정책 UI ────────────────────────────────────────────────────────────────
|
|
@router.get("/policies")
|
|
async def list_policies():
|
|
return {"policies": [
|
|
{"id": "POL-001", "name": "디스크 80% 경보", "type": "threshold", "active": True},
|
|
{"id": "POL-002", "name": "SLA 위반 에스컬레이션", "type": "sla", "active": True},
|
|
{"id": "POL-003", "name": "야간 배포 차단", "type": "access", "active": True},
|
|
]}
|
|
|
|
@router.get("/health")
|
|
async def health():
|
|
return {"status": "ok", "automations": len(_automations), "runbooks": len(_runbooks), "schedules": len(_schedules)}
|