G-1: 메신저 Webhook Relay + _send_to_room 실제 httpx 호출 구현 G-2: POST /api/tasks/bulk SR 대량작업 엔드포인트 (최대 100건) G-3: 라이선스 만료 알림 스케줄러 (매일 09:00 KST) G-4: 체험판 upgrade_banner 필드 + license.py 배너 로직 G-5: core/auto_rca.py + incidents/problem auto-rca 엔드포인트 G-6: core/deploy_impact.py + vibe impact-analysis 엔드포인트 G-7: core/ticket_classifier.py + SR 생성 시 AI 분류 + ai-suggestion API G-8: VulnPatchRecord 모델 + vuln_scan 패치추적 4개 엔드포인트 G-9: core/jira_sync.py + gateway Jira/Confluence 연동 엔드포인트 G-10: core/push_notify.py + routers/push.py + PushSubscription 모델 G-11: approvals 다중승인 (위임/서명/기한초과/마감연장) G-12: alembic.ini + migrations/ + cicd/migrate_to_postgres.sh 하네스: guardia-orchestrator 확장기능 Phase 반영 봇명령어: /sr /status /license /bulk 슬래시 명령어 추가 설치스크립트: setup/ (Ubuntu, CentOS, RHEL, Windows) --test 옵션 포함 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.4 KiB
7.4 KiB
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 추가 함수
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에 추가)
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}"}'
"""
}
}
환경 변수
SONARQUBE_URL=http://sonar.agency.go.kr:9000
SONARQUBE_TOKEN=<SonarQube API Token>
3. SSL 자동 갱신 (Let's Encrypt)
목적
certbot과 연동하여 SSL 인증서를 자동으로 갱신하고, 결과를 ITSM에 기록한다.
scripts/sm/ssl/ssl_auto_renew.sh
#!/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.shalert_on_fail: true
4. SSE 스트리밍 확장
목적
배치 실행 로그와 빌드 로그를 SSE로 실시간 스트리밍한다.
core/events.py 확장
# 배치 실행 로그 스트리밍
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)
프론트엔드 연결
// 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); }
};