feat(cicd): Gitea 기반 CI/CD 파이프라인 통합

[Jenkins - Gitea 연동]
- config/jenkins.yaml: gitea-credentials + gitea-api-token 자격증명 추가
- config/jenkins.yaml: GITEA_BASE_URL/ORG/REPO 전역 환경변수 추가
- Jenkinsfile.java-maven: Gitea SCM checkout 우선 (폴백: scm 기본값)
- jenkins_plugins.sh: generic-webhook-trigger + gitea 플러그인 추가
- jenkins_install.sh: 설치 후 Gitea 웹훅 자동 등록 호출

[Gitea 웹훅 자동화]
- scripts/notify/gitea_webhook.sh: Jenkins Generic Webhook Trigger 등록
  - push, pull_request, pull_request_review 이벤트 트리거
  - PR 빌드 전용 웹훅 별도 등록

[Gitea Actions CI (온프레미스 CI/CD)]
- .gitea/workflows/ci.yml:
  - Python Lint (flake8 E9/F4/F8 계열)
  - 모듈 임포트 테스트 (21개 모듈)
  - FastAPI 앱 로드 테스트
  - bash 구문 검사 (setup/*.sh + cicd/**/*.sh)
  - Docker Compose YAML 검증
  - PR 검증 요약 job

[브랜치 전략 적용]
  push: main, develop, feature/**
  pull_request: main, develop

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
DESKTOP-TKLFCPR\ython 2026-05-29 19:37:07 +09:00
parent 4b7904d14a
commit 779ea18ea9
6 changed files with 349 additions and 5 deletions

153
.gitea/workflows/ci.yml Normal file
View File

@ -0,0 +1,153 @@
# GUARDiA ITSM — Gitea Actions CI/CD
# 트리거: PR (feature → develop), Push (develop, main)
# 목적: Python 문법 검사 + 임포트 테스트 + 설치 스크립트 검증
name: GUARDiA CI
on:
push:
branches:
- main
- develop
- 'feature/**'
pull_request:
branches:
- main
- develop
env:
PYTHON_VERSION: "3.11"
jobs:
# ── 1. Python 코드 품질 검사 ─────────────────────────────────
lint-and-test:
name: Python Lint & Import Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install dependencies
run: |
cd itsm
pip install --upgrade pip -q
pip install -r requirements.txt -q
pip install flake8 -q
- name: Flake8 (문법/스타일 검사)
run: |
cd itsm
# 오류만 체크 (경고는 무시) — E9xx, F4xx, F8xx만
flake8 . --count --select=E9,F401,F811,F821,F841 \
--exclude=__pycache__,.git,static \
--max-line-length=200 \
--statistics
continue-on-error: false
- name: 모듈 임포트 테스트
run: |
cd itsm
python -c "
import sys, os
sys.path.insert(0, '.')
os.chdir('.')
modules = [
'models', 'database',
'core.auth', 'core.license', 'core.oauth',
'core.auto_rca', 'core.deploy_impact',
'core.ticket_classifier', 'core.jira_sync',
'core.push_notify', 'core.scheduler',
'routers.tasks', 'routers.approvals',
'routers.messenger', 'routers.vuln_scan',
'routers.gateway', 'routers.license',
'routers.push', 'routers.incidents',
'routers.problem', 'routers.vibe',
]
errors = []
for m in modules:
try:
__import__(m)
print(f'[OK] {m}')
except Exception as e:
print(f'[ERR] {m}: {e}')
errors.append(m)
if errors:
print(f'\\nFailed: {errors}')
sys.exit(1)
print('\\n모든 모듈 임포트 성공')
"
- name: FastAPI 앱 로드 테스트
run: |
cd itsm
python -c "
import sys, os
sys.path.insert(0, '.')
os.chdir('.')
from main import app
routes = [r.path for r in app.routes if hasattr(r, 'path')]
assert len(routes) > 100, f'라우트 수 부족: {len(routes)}'
print(f'FastAPI 앱 로드 성공 — {len(routes)}개 라우트')
"
# ── 2. 설치 스크립트 검증 ─────────────────────────────────────
validate-scripts:
name: Validate Install Scripts
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Bash 스크립트 구문 검사
run: |
echo "=== Bash 스크립트 구문 검사 ==="
FAIL=0
for f in setup/*.sh setup/lib/*.sh; do
bash -n "$f" && echo "[OK] $f" || { echo "[ERR] $f"; FAIL=1; }
done
for f in itsm/cicd/scripts/**/*.sh; do
[ -f "$f" ] && { bash -n "$f" && echo "[OK] $f" || { echo "[ERR] $f"; FAIL=1; }; }
done
[ $FAIL -eq 0 ] || exit 1
- name: Docker Compose YAML 검증
run: |
python3 -c "
import yaml
for f in ['docker-compose.yml', 'docker-compose.prod.yml']:
with open(f, encoding='utf-8') as fp:
yaml.safe_load(fp)
print(f'[OK] {f}')
"
- name: db_init.py 검증
run: |
python3 -c "
import ast
with open('itsm/tools/db_init.py', encoding='utf-8') as f:
ast.parse(f.read())
print('[OK] itsm/tools/db_init.py')
"
# ── 3. PR 검증 요약 ──────────────────────────────────────────
pr-summary:
name: PR Validation Summary
runs-on: ubuntu-latest
needs: [lint-and-test, validate-scripts]
if: github.event_name == 'pull_request'
steps:
- name: PR 통과
run: |
echo "✅ PR 검증 통과"
echo " - Python 코드 품질: 통과"
echo " - 모듈 임포트: 통과"
echo " - 설치 스크립트: 통과"
echo ""
echo "이제 리뷰어 승인을 받아 병합할 수 있습니다."

View File

@ -79,6 +79,17 @@ jenkins:
value: "/opt/apps" value: "/opt/apps"
- key: "SCRIPTS_ROOT" - key: "SCRIPTS_ROOT"
value: "/var/lib/jenkins/scripts" value: "/var/lib/jenkins/scripts"
# Gitea 설정 (온프레미스 형상관리)
- key: "GITEA_BASE_URL"
value: "${GITEA_BASE_URL:-http://localhost:3000}"
- key: "GITEA_ORG"
value: "${GITEA_ORG:-guardia}"
- key: "GITEA_REPO"
value: "${GITEA_REPO:-GUARDiA}"
- key: "SCM_BRANCH_PROTECT_MAIN"
value: "true"
- key: "DEFAULT_BRANCH"
value: "main"
# ── 빌드 실행기 설정 ───────────────────────────────────────────────────── # ── 빌드 실행기 설정 ─────────────────────────────────────────────────────
numExecutors: 4 numExecutors: 4
@ -162,12 +173,27 @@ credentials:
description: "SonarQube 분석 토큰" description: "SonarQube 분석 토큰"
secret: "${SONAR_TOKEN}" secret: "${SONAR_TOKEN}"
# Git 자격증명 (HTTPS) # Gitea 자격증명 (온프레미스 Git 서버)
- usernamePassword:
scope: GLOBAL
id: "gitea-credentials"
description: "Gitea 저장소 자격증명 (http://localhost:3000)"
username: "${GITEA_ADMIN:-gitadmin}"
password: "${GITEA_ADMIN_PW:-Gitea@guardia!}"
# Gitea API 토큰 (웹훅 등록 + PR 상태 업데이트)
- string:
scope: GLOBAL
id: "gitea-api-token"
description: "Gitea Personal Access Token"
secret: "${GITEA_API_TOKEN}"
# Git 자격증명 (HTTPS - 하위 호환)
- usernamePassword: - usernamePassword:
scope: GLOBAL scope: GLOBAL
id: "git-credentials" id: "git-credentials"
description: "Git 저장소 자격증명" description: "Git 저장소 자격증명 (Gitea 사용 권장)"
username: "${GIT_USERNAME}" username: "${GIT_USERNAME:-gitadmin}"
password: "${GIT_PASSWORD}" password: "${GIT_PASSWORD}"
# ── SonarQube 서버 설정 ─────────────────────────────────────────────────────── # ── SonarQube 서버 설정 ───────────────────────────────────────────────────────

View File

@ -34,8 +34,25 @@ pipeline {
stages { stages {
stage('Checkout') { stage('Checkout') {
steps { steps {
// Gitea(온프레미스) → GitHub 순서로 SCM 폴백
script {
def giteaUrl = "${env.GITEA_BASE_URL ?: 'http://localhost:3000'}/${env.GITEA_ORG ?: 'guardia'}/${env.GIT_REPO_NAME ?: 'GUARDiA'}.git"
try {
checkout([
$class: 'GitSCM',
branches: [[name: "*/${env.BRANCH_NAME ?: 'main'}"]],
userRemoteConfigs: [[
url: giteaUrl,
credentialsId: 'gitea-credentials'
]],
extensions: [[$class: 'CloneOption', depth: 1, noTags: false, shallow: true]]
])
echo "✅ Gitea checkout: ${env.GIT_COMMIT?.take(8) ?: 'N/A'}"
} catch (Exception e) {
echo "⚠️ Gitea checkout 실패 — SCM 기본값 사용: ${e.message}"
checkout scm checkout scm
echo "✅ 체크아웃 완료: ${env.GIT_COMMIT?.take(8) ?: 'N/A'}" }
}
} }
} }

View File

@ -169,6 +169,28 @@ print_summary() {
} }
# ── 메인 실행 ──────────────────────────────────────────────────────────────── # ── 메인 실행 ────────────────────────────────────────────────────────────────
configure_gitea_webhook() {
log "=== Gitea 웹훅 자동 등록 ==="
local script_dir
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local webhook_script="${script_dir}/../notify/gitea_webhook.sh"
if [[ -f "$webhook_script" ]]; then
# Gitea가 기동 중이면 웹훅 자동 등록
if curl -sf "${GITEA_BASE_URL:-http://localhost:3000}/api/v1/version" -o /dev/null 2>/dev/null; then
JENKINS_URL="${JENKINS_URL:-http://localhost:${JENKINS_PORT}}" \
bash "$webhook_script" \
&& log "Gitea 웹훅 등록 완료" \
|| warn "Gitea 웹훅 등록 실패 — 나중에 수동 실행: bash $webhook_script"
else
warn "Gitea 서비스 없음 — 웹훅 등록 건너뜀"
warn "Gitea 시작 후 실행: bash $webhook_script"
fi
else
warn "webhook 스크립트 없음: $webhook_script"
fi
}
main() { main() {
log "=== Jenkins 설치 시작 ===" log "=== Jenkins 설치 시작 ==="
detect_os detect_os
@ -178,6 +200,7 @@ main() {
configure_firewall configure_firewall
configure_selinux configure_selinux
prepare_directories prepare_directories
configure_gitea_webhook
print_summary print_summary
log "=== Jenkins 설치 완료 ===" log "=== Jenkins 설치 완료 ==="
} }

View File

@ -111,6 +111,10 @@ PLUGINS=(
"audit-trail" # 감사 로그 "audit-trail" # 감사 로그
"matrix-auth" # Matrix Authorization "matrix-auth" # Matrix Authorization
# ── Gitea 연동 (웹훅 트리거) ─────────────────────────
"generic-webhook-trigger" # Gitea Push/PR 웹훅 수신
"gitea" # Gitea 전용 SCM 플러그인
# ── 설정 관리 ───────────────────────────────────── # ── 설정 관리 ─────────────────────────────────────
"configuration-as-code" # JCasC "configuration-as-code" # JCasC
"configuration-as-code-support" # JCasC 지원 "configuration-as-code-support" # JCasC 지원

View File

@ -0,0 +1,121 @@
#!/bin/bash
# ==============================================================
# Gitea 웹훅 자동 등록 스크립트
# ==============================================================
# Gitea 저장소에 Jenkins 빌드 트리거 웹훅을 등록합니다.
# Gitea PR 이벤트 → Jenkins 파이프라인 자동 실행
#
# 사용법:
# bash cicd/scripts/notify/gitea_webhook.sh
# JENKINS_URL=http://jenkins:8080 bash gitea_webhook.sh
# ==============================================================
set -euo pipefail
GITEA_BASE="${GITEA_BASE_URL:-http://localhost:3000}"
GITEA_ORG="${GITEA_ORG:-guardia}"
GITEA_REPO="${GITEA_REPO:-GUARDiA}"
GITEA_ADMIN="${GITEA_ADMIN:-gitadmin}"
GITEA_ADMIN_PW="${GITEA_ADMIN_PW:-Gitea@guardia!}"
JENKINS_URL="${JENKINS_URL:-http://localhost:8080}"
JENKINS_USER="${JENKINS_USER:-admin}"
JENKINS_TOKEN="${JENKINS_TOKEN:-}"
API="${GITEA_BASE}/api/v1"
AUTH="Authorization: Basic $(echo -n "${GITEA_ADMIN}:${GITEA_ADMIN_PW}" | base64)"
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
info() { echo -e " $*"; }
echo "=================================================="
echo " Gitea 웹훅 등록"
echo " 저장소: ${GITEA_BASE}/${GITEA_ORG}/${GITEA_REPO}"
echo " Jenkins: ${JENKINS_URL}"
echo "=================================================="
# ── Jenkins Webhook URL ──────────────────────────────────────
# Jenkins Generic Webhook Trigger 플러그인 활용
WEBHOOK_TOKEN="${WEBHOOK_TOKEN:-guardia-jenkins-$(date +%s | md5sum | head -c 8)}"
WEBHOOK_URL="${JENKINS_URL}/generic-webhook-trigger/invoke?token=${WEBHOOK_TOKEN}"
# API 토큰이 있으면 Basic Auth 포함
if [[ -n "$JENKINS_TOKEN" ]]; then
WEBHOOK_URL="${JENKINS_URL%/}/generic-webhook-trigger/invoke?token=${WEBHOOK_TOKEN}"
fi
# ── 기존 웹훅 삭제 (중복 방지) ──────────────────────────────
info "기존 Jenkins 웹훅 정리..."
EXISTING=$(curl -sf "$API/repos/${GITEA_ORG}/${GITEA_REPO}/hooks" \
-H "$AUTH" 2>/dev/null | python3 -c "
import sys, json
hooks = json.load(sys.stdin)
ids = [str(h['id']) for h in hooks if 'jenkins' in str(h.get('config',{})).lower() or 'generic-webhook' in str(h.get('config',{})).lower()]
print(' '.join(ids))
" 2>/dev/null || echo "")
for hook_id in $EXISTING; do
curl -sf -X DELETE "$API/repos/${GITEA_ORG}/${GITEA_REPO}/hooks/${hook_id}" \
-H "$AUTH" -o /dev/null 2>/dev/null
info "기존 웹훅 삭제: ID=$hook_id"
done
# ── 웹훅 등록 ────────────────────────────────────────────────
info "Jenkins 웹훅 등록 중..."
RESP=$(curl -sf -X POST "$API/repos/${GITEA_ORG}/${GITEA_REPO}/hooks" \
-H "$AUTH" -H "Content-Type: application/json" \
-d "{
\"type\":\"gitea\",
\"config\":{
\"url\":\"${WEBHOOK_URL}\",
\"content_type\":\"json\",
\"secret\":\"${WEBHOOK_TOKEN}\"
},
\"events\":[\"push\",\"pull_request\",\"pull_request_review\"],
\"active\":true,
\"branch_filter\":\"*\"
}" 2>/dev/null)
HOOK_ID=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || echo "")
if [[ -n "$HOOK_ID" ]]; then
ok "웹훅 등록 완료 (ID: $HOOK_ID)"
else
warn "웹훅 등록 실패 — 수동 등록 필요"
info " Gitea → 저장소 → Settings → Webhooks → Add Webhook"
info " URL: $WEBHOOK_URL"
fi
# ── PR 이벤트 웹훅 설정 (PR 오픈 시 Jenkins 빌드) ────────────
info "PR 이벤트 웹훅 등록..."
PR_RESP=$(curl -sf -X POST "$API/repos/${GITEA_ORG}/${GITEA_REPO}/hooks" \
-H "$AUTH" -H "Content-Type: application/json" \
-d "{
\"type\":\"gitea\",
\"config\":{
\"url\":\"${JENKINS_URL}/job/guardia-pr/build?token=${WEBHOOK_TOKEN}\",
\"content_type\":\"json\"
},
\"events\":[\"pull_request\"],
\"active\":true
}" 2>/dev/null)
PR_HOOK_ID=$(echo "$PR_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || echo "")
[[ -n "$PR_HOOK_ID" ]] && ok "PR 트리거 웹훅 등록 (ID: $PR_HOOK_ID)" || warn "PR 웹훅 등록 실패"
# ── develop → main PR 자동화 설명 ────────────────────────────
echo ""
echo "=================================================="
ok "Gitea 웹훅 설정 완료!"
echo ""
info "=== CI/CD 트리거 흐름 ==="
info " 1. 개발자가 feature/이름/기능 브랜치에 push"
info " 2. Gitea 웹훅 → Jenkins Generic Webhook Trigger"
info " 3. Jenkins가 해당 브랜치 빌드·테스트"
info " 4. PR (feature → develop) 생성"
info " 5. 리뷰어 승인 후 develop 자동 병합"
info " 6. develop → main PR → 관리자 승인 → 운영 배포"
echo ""
info "웹훅 토큰: $WEBHOOK_TOKEN"
info "Jenkins 웹훅 URL: $WEBHOOK_URL"
echo "=================================================="