142 lines
5.6 KiB
Python
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": "커스텀 템플릿 미리보기"}
|