# GUARDiA ITSM — Priority 5: 외부 시스템 연동 고도화 **문서 버전**: 1.0 | **작성일**: 2026-05-25 --- ## 1. Jenkins 운영 배포 승인 연동 ### 목적 Jenkins Declarative Pipeline의 `input()` 단계와 ITSM 승인 API를 양방향으로 연동한다. 운영(prd) 배포 시 ITSM에서 PM이 승인해야만 Jenkins 파이프라인이 진행된다. ### 흐름 ``` GUARDiA Vibe 세션 → "배포(prd)" 버튼 클릭 │ ▼ POST /api/vibe/{id}/deploy { environment: "prd" } │ ▼ Jenkins Job 트리거 (core/cicd.py) │ Jenkins Pipeline: stage('Request ITSM Approval') { steps { sh """ curl -X POST ${ITSM_URL}/api/vibe/${SESSION_ID}/request-approval \ -H "Authorization: Bearer ${ITSM_TOKEN}" \ -d '{"environment": "prd", "build_number": ${BUILD_NUMBER}}' """ } } stage('Wait for ITSM Approval') { steps { timeout(time: 60, unit: 'MINUTES') { waitUntil { def result = sh( script: "curl -s ${ITSM_URL}/api/vibe/${SESSION_ID}/approval-status", returnStdout: true ).trim() return result == '"APPROVED"' } } } } stage('Deploy to Production') { ... } ``` ### ITSM API 신규 엔드포인트 ``` POST /api/vibe/{id}/request-approval → VibeDeploy 승인 요청 생성 (SRApproval 또는 AgentApproval 재사용) → PM에게 메신저 알림 GET /api/vibe/{id}/approval-status → "PENDING" | "APPROVED" | "REJECTED" PATCH /api/vibe/{id}/approve → PM이 ITSM UI에서 승인 처리 → Jenkins 폴링 응답에 "APPROVED" 반환 ``` --- ## 2. SonarQube Quality Gate 연동 ### 목적 Jenkins 빌드 완료 후 SonarQube 분석 결과를 ITSM에 자동 등록한다. Quality Gate 실패 시 SR을 생성하여 개발팀에 통보한다. ### core/cicd.py 추가 함수 ```python async def get_sonarqube_result(project_key: str) -> SonarResult: """SonarQube Quality Gate 결과 조회""" url = f"{SONARQUBE_URL}/api/qualitygates/project_status?projectKey={project_key}" async with httpx.AsyncClient() as client: r = await client.get(url, headers={"Authorization": f"Bearer {SONARQUBE_TOKEN}"}) data = r.json() status = data["projectStatus"]["status"] # OK | WARN | ERROR metrics = { c["metricKey"]: c["value"] for c in data["projectStatus"]["conditions"] } return SonarResult( status=status, coverage=metrics.get("coverage", "N/A"), bugs=metrics.get("bugs", "0"), vulnerabilities=metrics.get("vulnerabilities", "0"), code_smells=metrics.get("code_smells", "0"), ) async def handle_sonar_gate_failure(session_id: int, result: SonarResult, db: AsyncSession): """Quality Gate 실패 시 SR 자동 생성""" if result.status == "ERROR": sr = SRRequest( title=f"SonarQube Quality Gate 실패 — 세션 {session_id}", description=f"취약점: {result.vulnerabilities}, 버그: {result.bugs}, 커버리지: {result.coverage}%", sr_type="DEPLOY", priority="HIGH", ) db.add(sr) await db.commit() ``` ### Jenkins 연동 (Jenkinsfile에 추가) ```groovy stage('SonarQube Analysis') { steps { withSonarQubeEnv('SonarQube') { sh 'mvn sonar:sonar -Dsonar.projectKey=${PROJECT_KEY}' } } } stage('Quality Gate') { steps { timeout(time: 5, unit: 'MINUTES') { waitForQualityGate abortPipeline: false } // ITSM에 결과 전송 sh """ curl -X POST ${ITSM_URL}/api/vibe/sonar-result \ -d '{"session_id": ${SESSION_ID}, "project_key": "${PROJECT_KEY}"}' """ } } ``` ### 환경 변수 ```bash SONARQUBE_URL=http://sonar.agency.go.kr:9000 SONARQUBE_TOKEN= ``` --- ## 3. SSL 자동 갱신 (Let's Encrypt) ### 목적 certbot과 연동하여 SSL 인증서를 자동으로 갱신하고, 결과를 ITSM에 기록한다. ### scripts/sm/ssl/ssl_auto_renew.sh ```bash #!/bin/bash # SSL 자동 갱신 스크립트 (서버에서 실행) set -euo pipefail ITSM_URL="${ITSM_URL:-http://localhost:8001}" SERVER_ID="${SERVER_ID:-}" ITSM_TOKEN="${ITSM_TOKEN:-}" # certbot 갱신 if certbot renew --quiet --non-interactive \ --deploy-hook "systemctl reload nginx 2>/dev/null || systemctl reload apache2 2>/dev/null"; then # 새 만료일 조회 NEW_EXPIRE=$(openssl x509 -in /etc/letsencrypt/live/*/cert.pem -noout -enddate \ | cut -d= -f2) NEW_EXPIRE_ISO=$(date -d "$NEW_EXPIRE" +"%Y-%m-%d") # ITSM에 갱신 기록 curl -s -X POST "${ITSM_URL}/api/ssl/renew/${SERVER_ID}" \ -H "Authorization: Bearer ${ITSM_TOKEN}" \ -H "Content-Type: application/json" \ -d "{\"new_expire_date\": \"${NEW_EXPIRE_ISO}\", \"renewed_by\": \"certbot-auto\", \"notes\": \"Let's Encrypt 자동 갱신\"}" echo "[OK] SSL 갱신 완료: ${NEW_EXPIRE_ISO}" else echo "[WARN] SSL 갱신 불필요 (아직 유효기간 충분)" fi ``` ### 배치 작업 등록 `tb_batch_job`에 자동 갱신 배치 잡 등록: - `cron_expr`: `0 3 * * *` (매일 03:00) - `command`: `SSL_WATCHER=true bash /opt/guardia/scripts/ssl/ssl_auto_renew.sh` - `alert_on_fail`: true --- ## 4. SSE 스트리밍 확장 ### 목적 배치 실행 로그와 빌드 로그를 SSE로 실시간 스트리밍한다. ### core/events.py 확장 ```python # 배치 실행 로그 스트리밍 async def stream_batch_log(run_id: int) -> AsyncGenerator[str, None]: """tb_batch_run.stdout 청크 단위 SSE 전송""" last_len = 0 for _ in range(300): # 최대 5분 (1초 간격) async with SessionLocal() as db: run = await db.get(BatchRun, run_id) if run.stdout and len(run.stdout) > last_len: new_data = run.stdout[last_len:] last_len = len(run.stdout) yield f"data: {json.dumps({'chunk': new_data})}\n\n" if run.status not in ("RUNNING",): yield f"data: {json.dumps({'done': True, 'status': run.status})}\n\n" break await asyncio.sleep(1) # Jenkins 빌드 로그 스트리밍 async def stream_build_log(session_id: int) -> AsyncGenerator[str, None]: """Jenkins Build Log API 폴링 → SSE 전송""" # GET {JENKINS_URL}/job/{name}/{number}/logText/progressiveText?start=0 offset = 0 while True: log_chunk, next_offset = await cicd.get_progressive_log(session_id, offset) if log_chunk: yield f"data: {json.dumps({'chunk': log_chunk, 'offset': next_offset})}\n\n" offset = next_offset done = await cicd.is_build_complete(session_id) if done: yield f"data: {json.dumps({'done': True})}\n\n" break await asyncio.sleep(2) ``` ### 신규 엔드포인트 ``` GET /api/batch/runs/{run_id}/stream 배치 로그 실시간 스트리밍 (SSE) GET /api/vibe/{id}/build/stream 빌드 로그 실시간 스트리밍 (SSE) ``` ### 프론트엔드 연결 ```javascript // batch.html — 실행 로그 스트리밍 const es = new EventSource(`/api/batch/runs/${runId}/stream?token=${token}`); es.onmessage = (e) => { const data = JSON.parse(e.data); if (data.chunk) logBox.textContent += data.chunk; if (data.done) { es.close(); updateRunStatus(data.status); } }; ```