""" 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}