guardia-itsm/routers/platform_eng.py

203 lines
10 KiB
Python

"""
GUARDiA 플랫폼 엔지니어링 (Platform Engineering) — Gen6
IDP 고도화·골든 패스 템플릿·소프트웨어 카탈로그 v2·셀프서비스 인프라
"""
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/platform", tags=["Platform Engineering"])
# ── 인메모리 스토어 ────────────────────────────────────────────────────────
_catalog: Dict[str, Dict] = {}
_templates: Dict[str, Dict] = {}
_environments: Dict[str, Dict] = {}
_service_levels: Dict[str, Dict] = {}
_requests: Dict[str, Dict] = {}
# ── 사전 로드 카탈로그 ────────────────────────────────────────────────────
def _init():
for svc in [
{"id": "svc-itsm", "name": "GUARDiA ITSM", "type": "backend", "language": "python", "version": "2.1.0", "owner": "platform-team", "status": "production"},
{"id": "svc-manager", "name": "GUARDiA Manager", "type": "frontend", "language": "typescript", "version": "1.5.0", "owner": "platform-team", "status": "production"},
{"id": "svc-messenger", "name": "GUARDiA Messenger", "type": "mobile", "language": "typescript", "version": "1.0.0", "owner": "mobile-team", "status": "production"},
{"id": "svc-web", "name": "zioinfo Homepage", "type": "fullstack", "language": "java+typescript", "version": "3.0.0", "owner": "web-team", "status": "production"},
]:
_catalog[svc["id"]] = svc
_init()
# ── 모델 ──────────────────────────────────────────────────────────────────
class ServiceCreate(BaseModel):
name: str; type: str; language: str; owner: str
description: str = ""; tags: List[str] = []
class TemplateCreate(BaseModel):
name: str; type: str # fastapi|react|react-native|springboot|ansible
description: str = ""; variables: Dict[str, Any] = {}
class EnvironmentCreate(BaseModel):
name: str; type: str = "dev" # dev|staging|prod|dr
services: List[str] = []; config: Dict[str, Any] = {}
class SelfServiceRequest(BaseModel):
service: str; action: str # create|scale|deploy|rollback|restart
params: Dict[str, Any] = {}; requested_by: str = "developer"
# ── 소프트웨어 카탈로그 ──────────────────────────────────────────────────
@router.get("/catalog")
async def list_catalog(type: Optional[str] = None, status: Optional[str] = None):
svcs = list(_catalog.values())
if type: svcs = [s for s in svcs if s.get("type") == type]
if status: svcs = [s for s in svcs if s.get("status") == status]
return {"services": svcs, "total": len(svcs)}
@router.post("/catalog")
async def add_service(svc: ServiceCreate):
sid = f"svc-{uuid.uuid4().hex[:8]}"
_catalog[sid] = {**svc.model_dump(), "id": sid, "status": "registered",
"version": "0.1.0", "created_at": datetime.utcnow().isoformat()}
return _catalog[sid]
@router.get("/catalog/{sid}")
async def get_service(sid: str):
s = _catalog.get(sid)
if not s: raise HTTPException(404)
return s
@router.get("/catalog/{sid}/dependencies")
async def service_dependencies(sid: str):
return {"service_id": sid, "depends_on": ["svc-itsm"], "depended_by": [],
"impact_level": "high" if sid == "svc-itsm" else "medium"}
@router.get("/catalog/{sid}/docs")
async def service_docs(sid: str):
s = _catalog.get(sid)
if not s: raise HTTPException(404)
return {"service_id": sid, "readme": f"# {s['name']}\n\n운영 문서",
"api_docs": f"/api/{sid}/docs", "runbook": f"/api/kb/runbook/{sid}"}
# ── 골든 패스 템플릿 ──────────────────────────────────────────────────────
@router.get("/templates")
async def list_templates():
BUILTIN = [
{"id": "tpl-fastapi", "name": "FastAPI 마이크로서비스", "type": "fastapi",
"features": ["JWT 인증", "SQLAlchemy", "Ollama AI", "CORS", "헬스체크"]},
{"id": "tpl-react-ts", "name": "React TypeScript SPA", "type": "react",
"features": ["Tailwind CSS", "React Query", "Zustand", "Vite", "다국어"]},
{"id": "tpl-rn-expo", "name": "React Native Expo", "type": "react-native",
"features": ["Expo SDK 51", "TypeScript", "Zustand", "WebSocket", "오프라인"]},
{"id": "tpl-springboot", "name": "Spring Boot API", "type": "springboot",
"features": ["JPA", "보안", "Swagger", "Actuator", "GraalVM"]},
{"id": "tpl-ansible", "name": "Ansible 플레이북", "type": "ansible",
"features": ["에이전트리스", "SSH", "멱등성", "태그", "롤백"]},
{"id": "tpl-k8s", "name": "K8s 배포 구성", "type": "kubernetes",
"features": ["Deployment", "Service", "HPA", "ConfigMap", "Secret"]},
]
custom = list(_templates.values())
return {"builtin": BUILTIN, "custom": custom, "total": len(BUILTIN) + len(custom)}
@router.post("/templates")
async def create_template(t: TemplateCreate):
tid = f"tpl-{uuid.uuid4().hex[:8]}"
_templates[tid] = {**t.model_dump(), "id": tid, "created_at": datetime.utcnow().isoformat()}
return _templates[tid]
@router.post("/templates/{tid}/scaffold")
async def scaffold_from_template(tid: str, project_name: str, variables: Dict[str, Any] = {}):
return {
"template_id": tid, "project_name": project_name, "variables": variables,
"scaffolded": True, "files_created": ["main.py", "models.py", "README.md", "Dockerfile", ".env.example"],
"next_steps": ["cd " + project_name, "pip install -r requirements.txt", "python main.py"],
"ts": datetime.utcnow().isoformat(),
}
# ── 환경 관리 ────────────────────────────────────────────────────────────
@router.get("/environments")
async def list_environments():
envs = list(_environments.values()) or [
{"id": "env-dev", "name": "개발", "type": "dev", "services": list(_catalog.keys())[:2], "health": "healthy"},
{"id": "env-prod", "name": "운영", "type": "prod", "services": list(_catalog.keys()), "health": "healthy"},
]
return {"environments": envs}
@router.post("/environments")
async def create_environment(env: EnvironmentCreate):
eid = f"env-{uuid.uuid4().hex[:8]}"
_environments[eid] = {**env.model_dump(), "id": eid, "health": "creating",
"created_at": datetime.utcnow().isoformat()}
return _environments[eid]
@router.get("/environments/{eid}/diff")
async def env_diff(eid: str, compare_with: str = "env-prod"):
return {"env1": eid, "env2": compare_with, "differences": [
{"type": "config", "key": "DB_URL", "env1": "localhost", "env2": "prod-db:5432"},
{"type": "version", "service": "guardia-itsm", "env1": "2.0.0", "env2": "2.1.0"},
]}
@router.post("/environments/{eid}/promote")
async def promote_environment(eid: str, target: str = Query(...)):
return {"from": eid, "to": target, "promoted": True, "ts": datetime.utcnow().isoformat()}
# ── 셀프서비스 인프라 ────────────────────────────────────────────────────
@router.post("/self-service")
async def self_service(req: SelfServiceRequest):
req_id = f"REQ-{uuid.uuid4().hex[:8].upper()}"
result = {
"request_id": req_id, "service": req.service, "action": req.action,
"params": req.params, "requested_by": req.requested_by,
"status": "approved", "auto_approved": True,
"ts": datetime.utcnow().isoformat(),
}
_requests[req_id] = result
return result
@router.get("/self-service")
async def list_requests(status: Optional[str] = None):
reqs = list(_requests.values())
if status: reqs = [r for r in reqs if r.get("status") == status]
return {"requests": reqs[-50:], "total": len(reqs)}
# ── 플랫폼 메트릭 ─────────────────────────────────────────────────────────
@router.get("/metrics")
async def platform_metrics():
return {
"services": {"total": len(_catalog), "healthy": len(_catalog), "degraded": 0},
"deployments_today": 8, "avg_deploy_time_min": 4.2,
"golden_path_adoption": 87.5, "self_service_requests_week": 34,
"developer_satisfaction": 4.7,
}
@router.get("/metrics/adoption")
async def golden_path_adoption():
return {
"fastapi_template": {"used": 12, "adoption_rate": 92.3},
"react_ts_template": {"used": 8, "adoption_rate": 88.1},
"ansible_template": {"used": 5, "adoption_rate": 76.4},
"overall": 87.5, "target": 95.0,
}
# ── 서비스 레벨 목표 ─────────────────────────────────────────────────────
@router.get("/slo")
async def list_slo():
return {"slos": [
{"service": "guardia-itsm", "availability_target": 99.9, "current": 99.95, "ok": True},
{"service": "guardia-manager", "availability_target": 99.5, "current": 99.8, "ok": True},
{"service": "guardia-messenger", "availability_target": 99.0, "current": 98.7, "ok": False},
]}
@router.post("/slo")
async def create_slo(service: str, availability_target: float, latency_p95_ms: int = 500):
slid = f"SLO-{uuid.uuid4().hex[:8].upper()}"
_service_levels[slid] = {"id": slid, "service": service,
"availability_target": availability_target,
"latency_p95_ms": latency_p95_ms,
"created_at": datetime.utcnow().isoformat()}
return _service_levels[slid]
@router.get("/health")
async def platform_health():
return {"status": "healthy", "services_monitored": len(_catalog),
"environments": len(_environments) or 2, "templates": 6}