guardia-itsm/deploy_server_webhook.py
2026-06-02 20:52:19 +09:00

214 lines
9.7 KiB
Python

#!/usr/bin/env python3
"""GUARDiA CI/CD Webhook 서버 (포트 9999) — 6개 repo 지원"""
import http.server, subprocess, threading, json, hmac, hashlib, logging
import os, urllib.request, base64
SECRET = b"zioinfo-deploy-2026"
LOG = "/var/log/zioinfo/deploy.log"
JENKINS_URL = "http://127.0.0.1:9080"
JENKINS_USER = "admin"
JENKINS_TOKEN = "Admin@2026!"
ITSM_NOTIFY = "http://127.0.0.1:9001/api/messenger/webhook"
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
handlers=[logging.FileHandler(LOG), logging.StreamHandler()])
def notify_itsm(success: bool, msg: str):
try:
body = json.dumps({"event": "build_result", "room": "ops",
"success": success, "result_summary": msg}).encode()
req = urllib.request.Request(
ITSM_NOTIFY, data=body,
headers={"Content-Type": "application/json"})
urllib.request.urlopen(req, timeout=5)
except Exception:
pass
def trigger_jenkins(job: str):
try:
cred = base64.b64encode(f"{JENKINS_USER}:{JENKINS_TOKEN}".encode()).decode()
headers = {"Authorization": f"Basic {cred}"}
crumb_req = urllib.request.Request(
f"{JENKINS_URL}/crumbIssuer/api/json", headers=headers)
crumb_data = json.loads(urllib.request.urlopen(crumb_req, timeout=5).read())
headers[crumb_data["crumbRequestField"]] = crumb_data["crumb"]
build_req = urllib.request.Request(
f"{JENKINS_URL}/job/{job}/build?token=gitea-build-2026",
data=b"", headers=headers)
urllib.request.urlopen(build_req, timeout=5)
except Exception:
pass
def run_steps(repo: str, steps: list) -> bool:
for name, cmd in steps:
logging.info(f"[{repo}:{name}] 실행 중...")
try:
result = subprocess.run(
cmd, capture_output=True, text=True, timeout=300)
if result.stdout.strip():
logging.info(f"[{repo}:{name}] 완료")
if result.returncode != 0:
logging.error(f"[{repo}:{name}] 실패: {(result.stdout + result.stderr)[:200]}")
return False
else:
logging.info(f"[{repo}:{name}] 완료")
except Exception as e:
logging.error(f"[{repo}:{name}] 예외: {e}")
return False
return True
class WebhookHandler(http.server.BaseHTTPRequestHandler):
def do_POST(self):
try:
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length) if length else b""
sig = self.headers.get("X-Gitea-Signature", "")
if sig:
expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
self.send_response(403); self.end_headers(); return
except Exception:
body = b""
self.send_response(202)
self.end_headers()
self.wfile.write(b"Deploy queued")
try:
payload = json.loads(body) if body else {}
except Exception:
payload = {}
repo = payload.get("repository", {}).get("name", "")
branch = payload.get("ref", "").replace("refs/heads/", "")
logging.info(f"Webhook 수신: repo={repo} branch={branch}")
if not repo:
return
threading.Thread(target=self._deploy, args=(repo,), daemon=True).start()
def _deploy(self, repo: str):
logging.info(f"=== {repo} 배포 시작 ===")
if repo == "zioinfo-web":
SRC = "/opt/zioinfo/src"
ok = run_steps(repo, [
("git pull", ["bash", "-c",
f"[ -d {SRC}/.git ] && git -C {SRC} fetch origin main && git -C {SRC} reset --hard origin/main"
f" || git clone 'http://zio:Zio%40Admin2026%21@127.0.0.1:9003/zio/zioinfo-web.git' {SRC}"]),
("npm build", ["bash", "-c",
f"cd {SRC}/frontend && npm ci --legacy-peer-deps 2>/dev/null || npm install --legacy-peer-deps && npm run build"]),
("copy to www", ["bash", "-c",
f"cp -r {SRC}/backend/src/main/resources/static/. /var/www/zioinfo/ && echo 'copied'"]),
("mvn package", ["bash", "-c",
f"cd {SRC}/backend && /usr/bin/mvn clean package -DskipTests -q"]),
("deploy jar", ["bash", "-c",
f"cp {SRC}/backend/target/zioinfo-web-*.jar /opt/zioinfo/app/app.jar"]),
("restart", ["systemctl", "restart", "zioinfo"]),
("health check", ["bash", "-c", "sleep 5 && systemctl is-active zioinfo"]),
])
if ok:
notify_itsm(True, "✅ zioinfo-web 배포 완료")
trigger_jenkins("zioinfo-web")
else:
notify_itsm(False, "❌ zioinfo-web 빌드 실패")
elif repo == "guardia-itsm":
SRC = "/opt/guardia/src"
ok = run_steps(repo, [
("git pull", ["bash", "-c",
f"[ -d {SRC}/.git ] && git -C {SRC} fetch origin main && git -C {SRC} reset --hard origin/main"
f" || git clone 'http://zio:Zio%40Admin2026%21@127.0.0.1:9003/zio/guardia-itsm.git' {SRC}"]),
("rsync", ["bash", "-c",
f"rsync -a --exclude=__pycache__ --exclude=.git "
f"--exclude=rpa_rules.json --exclude='*.pyc' "
f"{SRC}/ /opt/guardia/app/"]),
("pip install", ["bash", "-c",
"/opt/guardia/venv/bin/pip install -r /opt/guardia/app/requirements.txt -q"]),
("restart", ["systemctl", "restart", "guardia"]),
("health check", ["bash", "-c", "sleep 4 && systemctl is-active guardia"]),
])
if ok:
notify_itsm(True, "✅ guardia-itsm 배포 완료")
trigger_jenkins("guardia-itsm")
else:
notify_itsm(False, "❌ guardia-itsm 빌드 실패")
elif repo == "guardia-manager":
SRC = "/opt/manager/src"
ok = run_steps(repo, [
("git pull", ["bash", "-c",
f"[ -d {SRC}/.git ] && git -C {SRC} fetch origin main && git -C {SRC} reset --hard origin/main"
f" || git clone 'http://zio:Zio%40Admin2026%21@127.0.0.1:9003/zio/guardia-manager.git' {SRC}"]),
("npm build", ["bash", "-c",
f"cd {SRC}/frontend && npm ci 2>/dev/null || npm install && npm run build"]),
("copy dist", ["bash", "-c",
f"cp -r {SRC}/frontend/dist/. /var/www/manager/ 2>/dev/null || "
f"cp -r {SRC}/dist/. /var/www/manager/"]),
("restart", ["systemctl", "restart", "guardia-manager", "2>/dev/null", "||", "true"]),
])
if ok:
notify_itsm(True, "✅ guardia-manager 배포 완료")
trigger_jenkins("guardia-manager")
else:
notify_itsm(False, "❌ guardia-manager 빌드 실패")
elif repo == "guardia-docs":
SRC = "/opt/guardia-docs/src"
ok = run_steps(repo, [
("git pull", ["bash", "-c",
f"[ -d {SRC}/.git ] && git -C {SRC} fetch origin main && git -C {SRC} reset --hard origin/main"
f" || git clone 'http://zio:Zio%40Admin2026%21@127.0.0.1:9003/zio/guardia-docs.git' {SRC}"]),
("copy docs", ["bash", "-c",
f"mkdir -p /var/www/docs && rsync -a --delete {SRC}/ /var/www/docs/"]),
])
if ok:
notify_itsm(True, "✅ guardia-docs 배포 완료")
else:
notify_itsm(False, "❌ guardia-docs 빌드 실패")
elif repo == "guardia-messenger":
trigger_jenkins("guardia-messenger")
elif repo == "zioinfo-mail":
SRC = "/opt/mail"
ok = run_steps(repo, [
("git pull", ["bash", "-c",
f"[ -d {SRC}/src/.git ] && git -C {SRC}/src fetch origin main && git -C {SRC}/src reset --hard origin/main"
f" || git clone 'http://zio:Zio%40Admin2026%21@127.0.0.1:9003/zio/zioinfo-mail.git' {SRC}/src"]),
("npm build", ["bash", "-c",
f"cd {SRC}/src/frontend && npm ci --legacy-peer-deps 2>/dev/null || npm install --legacy-peer-deps && npm run build"]),
("copy dist", ["bash", "-c",
f"mkdir -p /var/www/mail && cp -r {SRC}/src/dist/. /var/www/mail/"]),
("pip install", ["bash", "-c",
f"{SRC}/venv/bin/pip install -r {SRC}/src/backend/requirements.txt -q"]),
("rsync", ["bash", "-c",
f"rsync -a --exclude=__pycache__ --exclude=.git --exclude='*.pyc' --exclude='.env' {SRC}/src/backend/ {SRC}/backend/"]),
("restart", ["systemctl", "restart", "zioinfo-mail"]),
("health check", ["bash", "-c", "sleep 4 && curl -sf http://localhost:8026/health"]),
])
if ok:
notify_itsm(True, "✅ zioinfo-mail 배포 완료")
trigger_jenkins("zioinfo-mail")
else:
notify_itsm(False, "❌ zioinfo-mail 빌드 실패")
logging.info(f"=== {repo} 배포 완료 ===")
def log_message(self, fmt, *args):
logging.info(fmt % args)
if __name__ == "__main__":
os.makedirs("/var/log/zioinfo", exist_ok=True)
logging.info("GUARDiA CI/CD Webhook 서버 시작 (포트 9999) — 6개 repo 지원")
server = http.server.HTTPServer(("0.0.0.0", 9999), WebhookHandler)
server.serve_forever()