Files
gerrit/install_webhook_ci.sh

485 lines
15 KiB
Bash
Raw Normal View History

2025-12-22 17:12:39 +08:00
#!/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