diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 00000000..07676401 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -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 "이제 리뷰어 승인을 받아 병합할 수 있습니다." diff --git a/itsm/cicd/config/jenkins.yaml b/itsm/cicd/config/jenkins.yaml index c8446dbe..09996204 100644 --- a/itsm/cicd/config/jenkins.yaml +++ b/itsm/cicd/config/jenkins.yaml @@ -79,6 +79,17 @@ jenkins: value: "/opt/apps" - key: "SCRIPTS_ROOT" 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 @@ -162,12 +173,27 @@ credentials: description: "SonarQube 분석 토큰" 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: scope: GLOBAL id: "git-credentials" - description: "Git 저장소 자격증명" - username: "${GIT_USERNAME}" + description: "Git 저장소 자격증명 (Gitea 사용 권장)" + username: "${GIT_USERNAME:-gitadmin}" password: "${GIT_PASSWORD}" # ── SonarQube 서버 설정 ─────────────────────────────────────────────────────── diff --git a/itsm/cicd/jenkins/Jenkinsfile.java-maven b/itsm/cicd/jenkins/Jenkinsfile.java-maven index ede40219..3280eca4 100644 --- a/itsm/cicd/jenkins/Jenkinsfile.java-maven +++ b/itsm/cicd/jenkins/Jenkinsfile.java-maven @@ -34,8 +34,25 @@ pipeline { stages { stage('Checkout') { steps { - checkout scm - echo "✅ 체크아웃 완료: ${env.GIT_COMMIT?.take(8) ?: 'N/A'}" + // 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 + } + } } } diff --git a/itsm/cicd/scripts/install/jenkins_install.sh b/itsm/cicd/scripts/install/jenkins_install.sh index 60dc10b7..3bbaf984 100644 --- a/itsm/cicd/scripts/install/jenkins_install.sh +++ b/itsm/cicd/scripts/install/jenkins_install.sh @@ -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() { log "=== Jenkins 설치 시작 ===" detect_os @@ -178,6 +200,7 @@ main() { configure_firewall configure_selinux prepare_directories + configure_gitea_webhook print_summary log "=== Jenkins 설치 완료 ===" } diff --git a/itsm/cicd/scripts/install/jenkins_plugins.sh b/itsm/cicd/scripts/install/jenkins_plugins.sh index 12dbc7a9..c69779c3 100644 --- a/itsm/cicd/scripts/install/jenkins_plugins.sh +++ b/itsm/cicd/scripts/install/jenkins_plugins.sh @@ -111,6 +111,10 @@ PLUGINS=( "audit-trail" # 감사 로그 "matrix-auth" # Matrix Authorization + # ── Gitea 연동 (웹훅 트리거) ───────────────────────── + "generic-webhook-trigger" # Gitea Push/PR 웹훅 수신 + "gitea" # Gitea 전용 SCM 플러그인 + # ── 설정 관리 ───────────────────────────────────── "configuration-as-code" # JCasC "configuration-as-code-support" # JCasC 지원 diff --git a/itsm/cicd/scripts/notify/gitea_webhook.sh b/itsm/cicd/scripts/notify/gitea_webhook.sh new file mode 100644 index 00000000..81c42be8 --- /dev/null +++ b/itsm/cicd/scripts/notify/gitea_webhook.sh @@ -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 "=================================================="