Files
gerrit/install_webhook_ci.sh
2025-12-22 17:12:39 +08:00

485 lines
15 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# Webhook CI 安装脚本(轻量级,适合个人小团队)
# 支持 CentOS/RHEL 系统
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 配置变量
GERRIT_HOST="${GERRIT_HOST:-101.43.95.130}"
GERRIT_PORT="${GERRIT_PORT:-8080}"
WEBHOOK_PORT="${WEBHOOK_PORT:-9000}"
# 使用用户目录,避免需要 sudo
CI_SCRIPTS_DIR="${CI_SCRIPTS_DIR:-${HOME}/ci-scripts}"
WEBHOOK_USER="${USER}"
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}Webhook CI 自动化安装脚本${NC}"
echo -e "${GREEN}(轻量级,适合个人小团队)${NC}"
echo -e "${GREEN}========================================${NC}"
# 检查 Python3
check_python() {
echo -e "${GREEN}检查 Python3 环境...${NC}"
if ! command -v python3 &> /dev/null; then
echo -e "${RED}未检测到 Python3请先安装 Python3${NC}"
echo -e "${YELLOW}执行: sudo yum install -y python3${NC}"
exit 1
fi
echo -e "${GREEN}Python3 版本: $(python3 --version)${NC}"
}
# 创建工作目录
create_directories() {
echo -e "${GREEN}创建工作目录...${NC}"
mkdir -p ${CI_SCRIPTS_DIR}
cd ${CI_SCRIPTS_DIR}
echo -e "${GREEN}目录创建完成: ${CI_SCRIPTS_DIR}${NC}"
}
# 创建 Webhook 接收器
create_webhook_receiver() {
echo -e "${GREEN}创建 Webhook 接收器...${NC}"
cat > ${CI_SCRIPTS_DIR}/webhook_receiver.py << EOF
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Gerrit Webhook 接收器
接收 Gerrit 事件并触发构建脚本
"""
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import subprocess
import os
import logging
from datetime import datetime
# 获取脚本所在目录
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(os.path.join(SCRIPT_DIR, 'webhook.log')),
logging.StreamHandler()
]
)
class WebhookHandler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
"""重写日志方法,使用 logging"""
logging.info("%s - - [%s] %s" % (self.address_string(), self.log_date_time_string(), format % args))
def do_GET(self):
"""处理 GET 请求(健康检查)"""
self.send_response(200)
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(b'<h1>Webhook CI Receiver</h1><p>Status: OK</p>')
def do_POST(self):
"""处理 POST 请求Gerrit Webhook"""
try:
content_length = int(self.headers.get('Content-Length', 0))
post_data = self.rfile.read(content_length)
if not post_data:
self.send_response(400)
self.end_headers()
return
# 解析 JSON
try:
event = json.loads(post_data.decode('utf-8'))
except json.JSONDecodeError as e:
logging.error(f"JSON 解析错误: {e}")
self.send_response(400)
self.end_headers()
return
# 记录事件
event_type = event.get('type', 'unknown')
logging.info(f"收到事件: {event_type}")
# 处理不同类型的事件
if event_type == 'patchset-created':
self.handle_patchset_created(event)
elif event_type == 'change-merged':
self.handle_change_merged(event)
elif event_type == 'comment-added':
self.handle_comment_added(event)
else:
logging.info(f"未处理的事件类型: {event_type}")
self.send_response(200)
self.send_header('Content-type', 'application/json; charset=utf-8')
self.end_headers()
self.wfile.write(json.dumps({'status': 'ok'}).encode('utf-8'))
except Exception as e:
logging.error(f"处理请求时出错: {e}", exc_info=True)
self.send_response(500)
self.end_headers()
self.wfile.write(json.dumps({'error': str(e)}).encode('utf-8'))
def handle_patchset_created(self, event):
"""处理 patchset-created 事件"""
change = event.get('change', {})
project = change.get('project', '')
branch = change.get('branch', '')
change_id = change.get('id', '')
patchset = event.get('patchSet', {})
patchset_number = patchset.get('number', '')
logging.info(f"Patchset 创建: 项目={project}, 分支={branch}, Change={change_id}, Patchset={patchset_number}")
# 触发构建脚本(异步执行)
script_path = os.path.join(SCRIPT_DIR, 'build.sh')
if os.path.exists(script_path):
env = os.environ.copy()
env.update({
'GERRIT_PROJECT': project,
'GERRIT_BRANCH': branch,
'GERRIT_CHANGE_ID': change_id,
'GERRIT_PATCHSET_NUMBER': str(patchset_number),
'GERRIT_EVENT': 'patchset-created'
})
subprocess.Popen(['bash', script_path], env=env)
logging.info(f"已触发构建脚本: {script_path}")
else:
logging.warning(f"构建脚本不存在: {script_path}")
def handle_change_merged(self, event):
"""处理 change-merged 事件(代码合并后)"""
change = event.get('change', {})
project = change.get('project', '')
branch = change.get('branch', '')
logging.info(f"Change 合并: 项目={project}, 分支={branch}")
# 触发部署脚本(异步执行)
script_path = os.path.join(SCRIPT_DIR, 'deploy.sh')
if os.path.exists(script_path):
env = os.environ.copy()
env.update({
'GERRIT_PROJECT': project,
'GERRIT_BRANCH': branch,
'GERRIT_EVENT': 'change-merged'
})
subprocess.Popen(['bash', script_path], env=env)
logging.info(f"已触发部署脚本: {script_path}")
else:
logging.info(f"部署脚本不存在: {script_path}(跳过)")
def handle_comment_added(self, event):
"""处理 comment-added 事件(代码审查评论)"""
change = event.get('change', {})
comment = event.get('comment', '')
# 如果评论包含特定关键词,触发构建
if 'retest' in comment.lower() or 'test' in comment.lower():
logging.info("检测到测试关键词,触发构建")
self.handle_patchset_created(event)
if __name__ == '__main__':
port = int(os.environ.get('WEBHOOK_PORT', 9000))
server = HTTPServer(('0.0.0.0', port), WebhookHandler)
logging.info(f"Webhook 接收器启动在端口 {port}")
try:
server.serve_forever()
except KeyboardInterrupt:
logging.info("收到中断信号,正在关闭...")
server.shutdown()
EOF
chmod +x ${CI_SCRIPTS_DIR}/webhook_receiver.py
echo -e "${GREEN}Webhook 接收器创建完成${NC}"
}
# 创建构建脚本模板
create_build_script() {
echo -e "${GREEN}创建构建脚本模板...${NC}"
cat > ${CI_SCRIPTS_DIR}/build.sh << 'EOF'
#!/bin/bash
# 构建脚本模板
# 根据 Gerrit 事件自动触发
set -e
# 获取环境变量
PROJECT="${GERRIT_PROJECT}"
BRANCH="${GERRIT_BRANCH}"
CHANGE_ID="${GERRIT_CHANGE_ID}"
PATCHSET="${GERRIT_PATCHSET_NUMBER}"
EVENT="${GERRIT_EVENT}"
# 获取脚本所在目录
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
# 日志文件
LOG_FILE="\${SCRIPT_DIR}/build.log"
BUILD_DIR="/tmp/ci-builds/\${PROJECT}"
echo "=========================================" | tee -a ${LOG_FILE}
echo "构建开始: $(date)" | tee -a ${LOG_FILE}
echo "项目: ${PROJECT}" | tee -a ${LOG_FILE}
echo "分支: ${BRANCH}" | tee -a ${LOG_FILE}
echo "Change ID: ${CHANGE_ID}" | tee -a ${LOG_FILE}
echo "Patchset: ${PATCHSET}" | tee -a ${LOG_FILE}
echo "=========================================" | tee -a ${LOG_FILE}
# 创建构建目录
mkdir -p ${BUILD_DIR}
cd ${BUILD_DIR}
# 克隆或更新代码
if [ -d "${PROJECT}" ]; then
echo "更新代码..." | tee -a ${LOG_FILE}
cd ${PROJECT}
git fetch origin
git checkout ${BRANCH}
git pull origin ${BRANCH}
else
echo "克隆代码..." | tee -a ${LOG_FILE}
git clone ssh://admin@101.43.95.130:29418/${PROJECT}
cd ${PROJECT}
git checkout ${BRANCH}
fi
# 根据项目类型执行构建
if [ -f "pom.xml" ]; then
echo "检测到 Maven 项目,执行 Maven 构建..." | tee -a ${LOG_FILE}
mvn clean package -DskipTests || {
echo "Maven 构建失败" | tee -a ${LOG_FILE}
exit 1
}
elif [ -f "package.json" ]; then
echo "检测到 Node.js 项目,执行 npm 构建..." | tee -a ${LOG_FILE}
npm install && npm run build || {
echo "npm 构建失败" | tee -a ${LOG_FILE}
exit 1
}
elif [ -f "Makefile" ]; then
echo "检测到 Makefile执行 make..." | tee -a ${LOG_FILE}
make || {
echo "Make 构建失败" | tee -a ${LOG_FILE}
exit 1
}
else
echo "未检测到构建配置文件,跳过构建" | tee -a ${LOG_FILE}
fi
echo "构建完成: $(date)" | tee -a ${LOG_FILE}
echo "=========================================" | tee -a ${LOG_FILE}
EOF
chmod +x ${CI_SCRIPTS_DIR}/build.sh
echo -e "${GREEN}构建脚本模板创建完成${NC}"
}
# 创建部署脚本模板
create_deploy_script() {
echo -e "${GREEN}创建部署脚本模板...${NC}"
cat > ${CI_SCRIPTS_DIR}/deploy.sh << 'EOF'
#!/bin/bash
# 部署脚本模板
# 在代码合并后自动触发
set -e
PROJECT="${GERRIT_PROJECT}"
BRANCH="${GERRIT_BRANCH}"
# 获取脚本所在目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_FILE="${SCRIPT_DIR}/deploy.log"
echo "=========================================" | tee -a ${LOG_FILE}
echo "部署开始: $(date)" | tee -a ${LOG_FILE}
echo "项目: ${PROJECT}" | tee -a ${LOG_FILE}
echo "分支: ${BRANCH}" | tee -a ${LOG_FILE}
echo "=========================================" | tee -a ${LOG_FILE}
# 在这里添加您的部署逻辑
# 例如:
# - 复制构建产物到部署目录
# - 重启服务
# - 发送通知等
echo "部署完成: $(date)" | tee -a ${LOG_FILE}
echo "=========================================" | tee -a ${LOG_FILE}
EOF
chmod +x ${CI_SCRIPTS_DIR}/deploy.sh
echo -e "${GREEN}部署脚本模板创建完成${NC}"
}
# 创建启动脚本
create_start_script() {
echo -e "${GREEN}创建启动脚本...${NC}"
cat > ${CI_SCRIPTS_DIR}/start_webhook.sh << EOF
#!/bin/bash
# Webhook CI 启动脚本
cd ${CI_SCRIPTS_DIR}
export WEBHOOK_PORT=${WEBHOOK_PORT}
# 检查是否已在运行
if pgrep -f "webhook_receiver.py" > /dev/null; then
echo "Webhook CI 已在运行中"
exit 1
fi
# 启动服务(后台运行)
nohup python3 ${CI_SCRIPTS_DIR}/webhook_receiver.py > ${CI_SCRIPTS_DIR}/webhook_stdout.log 2>&1 &
echo "Webhook CI 已启动PID: \$!"
echo "日志文件: ${CI_SCRIPTS_DIR}/webhook.log"
echo "标准输出: ${CI_SCRIPTS_DIR}/webhook_stdout.log"
EOF
chmod +x ${CI_SCRIPTS_DIR}/start_webhook.sh
# 创建停止脚本
cat > ${CI_SCRIPTS_DIR}/stop_webhook.sh << EOF
#!/bin/bash
# Webhook CI 停止脚本
PID=\$(pgrep -f "webhook_receiver.py")
if [ -n "\$PID" ]; then
kill \$PID
echo "Webhook CI 已停止 (PID: \$PID)"
else
echo "Webhook CI 未运行"
fi
EOF
chmod +x ${CI_SCRIPTS_DIR}/stop_webhook.sh
# 创建状态检查脚本
cat > ${CI_SCRIPTS_DIR}/status_webhook.sh << EOF
#!/bin/bash
# Webhook CI 状态检查脚本
PID=\$(pgrep -f "webhook_receiver.py")
if [ -n "\$PID" ]; then
echo "Webhook CI 正在运行 (PID: \$PID)"
echo "端口: ${WEBHOOK_PORT}"
netstat -tlnp 2>/dev/null | grep ${WEBHOOK_PORT} || ss -tlnp 2>/dev/null | grep ${WEBHOOK_PORT} || echo "端口监听检查失败"
else
echo "Webhook CI 未运行"
fi
EOF
chmod +x ${CI_SCRIPTS_DIR}/status_webhook.sh
echo -e "${GREEN}启动/停止脚本创建完成${NC}"
}
# 启动服务
start_service() {
echo -e "${GREEN}启动 Webhook CI 服务...${NC}"
# 检查是否已在运行
if pgrep -f "webhook_receiver.py" > /dev/null; then
echo -e "${YELLOW}Webhook CI 已在运行中,跳过启动${NC}"
return
fi
# 启动服务
cd ${CI_SCRIPTS_DIR}
export WEBHOOK_PORT=${WEBHOOK_PORT}
nohup python3 ${CI_SCRIPTS_DIR}/webhook_receiver.py > ${CI_SCRIPTS_DIR}/webhook_stdout.log 2>&1 &
# 等待服务启动
sleep 2
if pgrep -f "webhook_receiver.py" > /dev/null; then
PID=$(pgrep -f "webhook_receiver.py")
echo -e "${GREEN}Webhook CI 服务启动成功!(PID: ${PID})${NC}"
else
echo -e "${RED}服务启动失败,请检查日志${NC}"
echo -e "${YELLOW}查看日志: tail -f ${CI_SCRIPTS_DIR}/webhook.log${NC}"
exit 1
fi
}
# 检查防火墙
check_firewall() {
echo -e "${YELLOW}检查防火墙配置...${NC}"
if command -v firewall-cmd &> /dev/null; then
if sudo firewall-cmd --list-ports | grep -q "${WEBHOOK_PORT}"; then
echo -e "${GREEN}防火墙端口 ${WEBHOOK_PORT} 已开放${NC}"
else
echo -e "${YELLOW}需要开放防火墙端口 ${WEBHOOK_PORT}${NC}"
echo -e "${YELLOW}执行: sudo firewall-cmd --permanent --add-port=${WEBHOOK_PORT}/tcp && sudo firewall-cmd --reload${NC}"
fi
else
echo -e "${YELLOW}未检测到 firewall-cmd请手动检查防火墙${NC}"
fi
}
# 输出访问信息
print_info() {
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}Webhook CI 安装完成!${NC}"
echo -e "${GREEN}========================================${NC}"
echo -e "${YELLOW}Webhook URL:${NC}"
echo -e " http://${GERRIT_HOST}:${WEBHOOK_PORT}"
echo -e ""
echo -e "${YELLOW}常用命令:${NC}"
echo -e " 启动: ${CI_SCRIPTS_DIR}/start_webhook.sh"
echo -e " 停止: ${CI_SCRIPTS_DIR}/stop_webhook.sh"
echo -e " 状态: ${CI_SCRIPTS_DIR}/status_webhook.sh"
echo -e " 日志: tail -f ${CI_SCRIPTS_DIR}/webhook.log"
echo -e " 标准输出: tail -f ${CI_SCRIPTS_DIR}/webhook_stdout.log"
echo -e " 构建日志: tail -f ${CI_SCRIPTS_DIR}/build.log"
echo -e ""
echo -e "${YELLOW}脚本位置:${NC}"
echo -e " Webhook 接收器: ${CI_SCRIPTS_DIR}/webhook_receiver.py"
echo -e " 构建脚本: ${CI_SCRIPTS_DIR}/build.sh"
echo -e " 部署脚本: ${CI_SCRIPTS_DIR}/deploy.sh"
echo -e ""
echo -e "${YELLOW}下一步 - 配置 Gerrit Webhook:${NC}"
echo -e " 1. 访问 Gerrit: http://${GERRIT_HOST}:${GERRIT_PORT}"
echo -e " 2. 进入项目设置 → Webhooks"
echo -e " 3. 添加 Webhook:"
echo -e " URL: http://${GERRIT_HOST}:${WEBHOOK_PORT}"
echo -e " 事件: patchset-created, change-merged"
echo -e " 4. 根据项目类型修改构建脚本: ${CI_SCRIPTS_DIR}/build.sh"
echo -e ""
echo -e "${YELLOW}测试 Webhook:${NC}"
echo -e " curl http://${GERRIT_HOST}:${WEBHOOK_PORT}"
echo -e "${GREEN}========================================${NC}"
}
# 主函数
main() {
check_python
create_directories
create_webhook_receiver
create_build_script
create_deploy_script
create_start_script
start_service
check_firewall
print_info
}
# 执行主函数
main