111 lines
3.8 KiB
Python
111 lines
3.8 KiB
Python
"""
|
|
Jenkins CI/CD 상태 API (모바일 기능 #99, #100).
|
|
|
|
GET /api/cicd/builds — 빌드 목록 (최근 20건)
|
|
GET /api/cicd/builds/{id} — 빌드 상세
|
|
POST /api/cicd/builds/trigger — 빌드 트리거
|
|
GET /api/cicd/status — 전체 파이프라인 상태
|
|
WS /ws/cicd-status — 실시간 빌드 상태 스트림
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import json
|
|
import logging
|
|
import os
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect
|
|
from pydantic import BaseModel
|
|
|
|
from core.auth import get_current_user
|
|
from models import User
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(tags=["CI/CD"])
|
|
|
|
JENKINS_URL = os.getenv("JENKINS_URL", "http://localhost:8080")
|
|
|
|
_MOCK_BUILDS = [
|
|
{"id": 1, "project": "guardia-itsm", "branch": "main", "status": "SUCCESS", "started_at": "2026-06-06T10:00:00", "duration_sec": 125, "triggered_by": "admin"},
|
|
{"id": 2, "project": "guardia-messenger", "branch": "feature/100feat", "status": "SUCCESS", "started_at": "2026-06-06T09:30:00", "duration_sec": 340, "triggered_by": "ythong"},
|
|
{"id": 3, "project": "guardia-manager", "branch": "main", "status": "FAILURE", "started_at": "2026-06-06T08:00:00", "duration_sec": 60, "triggered_by": "admin"},
|
|
{"id": 4, "project": "zioinfo-web", "branch": "main", "status": "SUCCESS", "started_at": "2026-06-05T17:00:00", "duration_sec": 90, "triggered_by": "admin"},
|
|
{"id": 5, "project": "guardia-itsm", "branch": "develop", "status": "RUNNING", "started_at": "2026-06-06T10:30:00", "duration_sec": None, "triggered_by": "ythong"},
|
|
]
|
|
|
|
|
|
class TriggerIn(BaseModel):
|
|
project: str
|
|
branch: Optional[str] = "main"
|
|
|
|
|
|
@router.get("/api/cicd/builds")
|
|
async def list_builds(
|
|
limit: int = 20,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
return {"jenkins_url": JENKINS_URL, "items": _MOCK_BUILDS[:limit]}
|
|
|
|
|
|
@router.get("/api/cicd/builds/{build_id}")
|
|
async def get_build(
|
|
build_id: int,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
b = next((b for b in _MOCK_BUILDS if b["id"] == build_id), None)
|
|
if not b:
|
|
return {"error": "빌드를 찾을 수 없습니다."}
|
|
return b
|
|
|
|
|
|
@router.post("/api/cicd/builds/trigger", status_code=202)
|
|
async def trigger_build(
|
|
payload: TriggerIn,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
return {
|
|
"queued": True,
|
|
"project": payload.project,
|
|
"branch": payload.branch,
|
|
"triggered_by": current_user.username,
|
|
"message": f"{payload.project}@{payload.branch} 빌드가 대기열에 추가됐습니다.",
|
|
}
|
|
|
|
|
|
@router.get("/api/cicd/status")
|
|
async def pipeline_status(current_user: User = Depends(get_current_user)):
|
|
running = [b for b in _MOCK_BUILDS if b["status"] == "RUNNING"]
|
|
return {
|
|
"jenkins_connected": False,
|
|
"jenkins_url": JENKINS_URL,
|
|
"running_builds": len(running),
|
|
"latest_apk_url": "/api/cicd/apk/latest",
|
|
"apk_qr_data": "https://zioinfo.co.kr:8443/static/apk/guardia-latest.apk",
|
|
"builds": _MOCK_BUILDS[:5],
|
|
}
|
|
|
|
|
|
_cicd_clients: set[WebSocket] = set()
|
|
|
|
|
|
@router.websocket("/ws/cicd-status")
|
|
async def cicd_ws(websocket: WebSocket):
|
|
await websocket.accept()
|
|
_cicd_clients.add(websocket)
|
|
try:
|
|
while True:
|
|
await asyncio.sleep(10)
|
|
running = [b for b in _MOCK_BUILDS if b["status"] == "RUNNING"]
|
|
await websocket.send_text(json.dumps({
|
|
"type": "status",
|
|
"running": len(running),
|
|
"ts": datetime.now().isoformat(),
|
|
}))
|
|
except WebSocketDisconnect:
|
|
_cicd_clients.discard(websocket)
|
|
except Exception as e:
|
|
logger.warning("cicd ws error: %s", e)
|
|
_cicd_clients.discard(websocket)
|