guardia-itsm/routers/idp_template.py
2026-06-03 08:04:03 +09:00

142 lines
5.6 KiB
Python

"""IDP Golden Path 템플릿 — 서비스 스캐폴딩 자동화"""
from __future__ import annotations
import json, logging
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from core.auth import get_current_user
from database import get_db
from models import User, IDPTemplate
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/idp/template", tags=["IDP 템플릿"])
BUILTIN_TEMPLATES = [
{
"name": "fastapi-service",
"description": "FastAPI Python 마이크로서비스 (Docker + Jenkinsfile + .env)",
"language": "python", "framework": "fastapi",
"variables": ["SERVICE_NAME", "PORT", "DB_URL"],
"files": ["main.py", "requirements.txt", "Dockerfile", "Jenkinsfile", ".env.example"],
},
{
"name": "spring-boot-service",
"description": "Spring Boot Java 서비스 (Maven + Jenkinsfile)",
"language": "java", "framework": "spring-boot",
"variables": ["SERVICE_NAME", "PORT", "DB_URL"],
"files": ["pom.xml", "src/", "Dockerfile", "Jenkinsfile"],
},
{
"name": "react-spa",
"description": "React 18 + TypeScript + Vite SPA",
"language": "typescript", "framework": "react",
"variables": ["APP_NAME", "API_BASE_URL"],
"files": ["package.json", "vite.config.ts", "src/App.tsx", "Jenkinsfile"],
},
{
"name": "csap-compliant",
"description": "공공기관 CSAP 준수 보안 설정 패키지",
"language": "config", "framework": "security",
"variables": ["SYSTEM_NAME", "ZONE"],
"files": ["nginx.conf", "ufw-rules.sh", "audit-policy.yaml"],
},
]
class TemplateCreate(BaseModel):
name: str; description: str = ""; language: str = ""; framework: str = ""
variables: list = []; files: list = []; content_json: str = "{}"
class ScaffoldRequest(BaseModel):
template_id: int; service_name: str
variables: dict = {}; create_gitea_repo: bool = False
@router.get("")
async def list_templates(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
builtins = [{"id": f"builtin-{i}", **t, "is_builtin": True}
for i, t in enumerate(BUILTIN_TEMPLATES)]
rows = await db.execute(select(IDPTemplate).order_by(IDPTemplate.name))
custom = [{"id": t.id, "name": t.name, "description": t.description,
"language": t.language, "is_builtin": False}
for t in rows.scalars().all()]
return builtins + custom
@router.post("", status_code=201)
async def create_template(body: TemplateCreate, db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user)):
t = IDPTemplate(
name=body.name, description=body.description,
language=body.language, framework=body.framework,
variables=json.dumps(body.variables), files=json.dumps(body.files),
content_json=body.content_json,
created_by=user.id, created_at=datetime.utcnow()
)
db.add(t); await db.commit(); await db.refresh(t)
return {"id": t.id}
@router.get("/{template_id}")
async def get_template(template_id, db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user)):
if str(template_id).startswith("builtin-"):
idx = int(str(template_id).replace("builtin-", ""))
if idx < len(BUILTIN_TEMPLATES):
return {**BUILTIN_TEMPLATES[idx], "id": template_id, "is_builtin": True}
raise HTTPException(404)
row = await db.execute(select(IDPTemplate).where(IDPTemplate.id == int(template_id)))
t = row.scalar_one_or_none()
if not t: raise HTTPException(404)
return {"id": t.id, "name": t.name, "description": t.description,
"language": t.language, "variables": json.loads(t.variables or "[]"),
"files": json.loads(t.files or "[]")}
@router.post("/{template_id}/scaffold")
async def scaffold(template_id, body: ScaffoldRequest, db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user)):
"""템플릿으로 새 서비스 스캐폴딩 — 변수 치환 + 선택적 Gitea repo 생성."""
scaffold_result = {
"service_name": body.service_name,
"template_id": str(template_id),
"generated_files": [],
"gitea_repo": None,
"jenkins_job": None,
}
# 내장 템플릿 기반 파일 목록 생성
tpl_data = None
if str(template_id).startswith("builtin-"):
idx = int(str(template_id).replace("builtin-", ""))
if idx < len(BUILTIN_TEMPLATES):
tpl_data = BUILTIN_TEMPLATES[idx]
if tpl_data:
scaffold_result["generated_files"] = [
f.replace("SERVICE_NAME", body.service_name)
for f in tpl_data.get("files", [])
]
if body.create_gitea_repo:
scaffold_result["gitea_repo"] = f"http://localhost:9003/zio/{body.service_name}"
scaffold_result["jenkins_job"] = body.service_name
return scaffold_result
@router.get("/{template_id}/preview")
async def preview(template_id, service_name: str = "my-service",
user: User = Depends(get_current_user)):
if str(template_id).startswith("builtin-"):
idx = int(str(template_id).replace("builtin-", ""))
if idx < len(BUILTIN_TEMPLATES):
tpl = BUILTIN_TEMPLATES[idx]
return {"template": tpl["name"], "service_name": service_name,
"files": tpl.get("files", []),
"required_vars": tpl.get("variables", [])}
return {"preview": "커스텀 템플릿 미리보기"}