From 14be2b654bf195a406640a91cbfe4669f0a433fe Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Wed, 22 Jul 2026 09:27:54 +0800 Subject: [PATCH] chore: sync ci-pipeline and validate scripts from develop --- scripts/ci/validate_migration.sh | 182 +++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 scripts/ci/validate_migration.sh diff --git a/scripts/ci/validate_migration.sh b/scripts/ci/validate_migration.sh new file mode 100644 index 000000000..998dc1651 --- /dev/null +++ b/scripts/ci/validate_migration.sh @@ -0,0 +1,182 @@ +#!/bin/bash +# CI Validate: Alembic迁移验证(并行Job 3/3) +# 需要PostgreSQL数据库 +set -eu + +echo "=== CI Validate: Alembic迁移验证 ===" + +# --- DooD模式检测:确定宿主机访问地址 --- +detect_docker_host() { + local test_port="${1:-5432}" + + local candidates=() + + # 1. host.docker.internal + if python3 -c "import socket; socket.gethostbyname('host.docker.internal')" 2>/dev/null; then + candidates+=("host.docker.internal") + fi + + # 2. docker0 桥接网关 + candidates+=("172.17.0.1") + + # 3. 默认网关 + local gw="" + gw=$(ip route 2>/dev/null | grep default | awk '{print $3}' | head -1) + if [ -n "$gw" ] && [ "$gw" != "127.0.0.1" ]; then + candidates+=("$gw") + fi + + # 4. 宿主机同网段的.1或.254 + local my_ip="" + my_ip=$(hostname -I 2>/dev/null | awk '{print $1}') + if [ -n "$my_ip" ]; then + local subnet=$(echo "$my_ip" | cut -d. -f1-3) + candidates+=("${subnet}.1") + candidates+=("${subnet}.254") + fi + + # 5. 127.0.0.1 最后尝试 + candidates+=("127.0.0.1") + + for candidate in "${candidates[@]}"; do + if python3 -c " +import socket +s = socket.socket() +s.settimeout(2) +try: + s.connect(('$candidate', $test_port)) + s.close() + print('ok') +except: + pass +" 2>/dev/null | grep -q ok; then + echo "$candidate" + return 0 + fi + done + + echo "127.0.0.1" + return 1 +} + +# 获取宿主机IP +if [ -S /var/run/docker.sock ]; then + DOCKER_HOST_IP=$(detect_docker_host 5433) + if [ "$DOCKER_HOST_IP" = "127.0.0.1" ]; then + DOCKER_HOST_IP=$(detect_docker_host 22) + fi + echo "检测到DooD模式,宿主机地址: $DOCKER_HOST_IP" +else + DOCKER_HOST_IP="127.0.0.1" + echo "非DooD模式,使用 127.0.0.1" +fi +PG_HOST="$DOCKER_HOST_IP" +echo "PG host: $PG_HOST" + +# 指数退避TCP连接检查 +wait_tcp_ready() { + local host="$1" + local port="$2" + local max_attempts="${3:-5}" + local delay=1 + local attempt=1 + while [ "$attempt" -le "$max_attempts" ]; do + if python3 -c "import socket; s=socket.socket(); s.settimeout(3); s.connect(('$host', $port)); s.close()" 2>/dev/null; then + return 0 + fi + echo "TCP连接尝试 $attempt/$max_attempts 失败,${delay}s后重试..." + sleep "$delay" + delay=$((delay * 2)) + attempt=$((attempt + 1)) + done + return 1 +} + +USE_SHARED_PG="${CI_USE_SHARED_PG:-false}" + +if [ "$USE_SHARED_PG" = "true" ]; then + # 使用常驻共享PG实例 + echo "使用常驻共享PG实例(CI_USE_SHARED_PG=true)" + SHARED_PG_HOST="$PG_HOST" + SHARED_PG_PORT="5433" + SHARED_PG_USER="postgres" + SHARED_PG_PASSWORD="ci_pg_2026!" + CI_DB_NAME="ci_run_${GITHUB_RUN_ID:-$$}" + + echo "等待共享PG连接就绪..." + wait_tcp_ready "$SHARED_PG_HOST" "$SHARED_PG_PORT" 5 + + echo "创建测试数据库: $CI_DB_NAME" + PGPASSWORD="$SHARED_PG_PASSWORD" python3 -c " +import psycopg2 +conn = psycopg2.connect(host='$SHARED_PG_HOST', port=$SHARED_PG_PORT, user='$SHARED_PG_USER', password='$SHARED_PG_PASSWORD', dbname='postgres') +conn.autocommit = True +cur = conn.cursor() +cur.execute(f'CREATE DATABASE \"$CI_DB_NAME\"') +cur.close() +conn.close() +" + export DATABASE_URL="postgresql+psycopg://${SHARED_PG_USER}:${SHARED_PG_PASSWORD}@${SHARED_PG_HOST}:${SHARED_PG_PORT}/${CI_DB_NAME}" + echo "✅ 共享PG数据库已创建: $CI_DB_NAME" + + # 执行迁移 + PYTHONPATH="$PWD/apps/api:$PWD" python3 -m alembic upgrade head + echo "✅ Alembic migrations applied successfully" + + # 清理数据库 + echo "清理测试数据库: $CI_DB_NAME" + PGPASSWORD="$SHARED_PG_PASSWORD" python3 -c " +import psycopg2 +conn = psycopg2.connect(host='$SHARED_PG_HOST', port=$SHARED_PG_PORT, user='$SHARED_PG_USER', password='$SHARED_PG_PASSWORD', dbname='postgres') +conn.autocommit = True +cur = conn.cursor() +cur.execute(f'DROP DATABASE IF EXISTS \"$CI_DB_NAME\" WITH (FORCE)') +cur.close() +conn.close() +" 2>/dev/null || echo "WARN: 数据库清理失败" + echo "✅ 共享PG数据库已清理" +else + # 使用临时PG容器(默认模式) + echo "使用临时PG容器模式" + PG_CONTAINER=ci-pg-validate-migration-${GITHUB_RUN_ID:-$$} + docker rm -f "$PG_CONTAINER" 2>/dev/null || true + docker run -d --name "$PG_CONTAINER" \ + --shm-size=256m \ + -e POSTGRES_USER=postgres \ + -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_DB=xiaoxia_saas \ + -P \ + --health-cmd "pg_isready -U postgres" \ + --health-interval 3s \ + --health-timeout 3s \ + --health-retries 20 \ + postgres:16-alpine + PG_PORT=$(docker port "$PG_CONTAINER" 5432/tcp | cut -d: -f2) + echo "PostgreSQL port: $PG_PORT" + export DATABASE_URL="postgresql+psycopg://postgres:postgres@${PG_HOST}:${PG_PORT}/xiaoxia_saas" + + # 等待容器健康 + for i in $(seq 1 30); do + if docker inspect --format='{{.State.Health.Status}}' "$PG_CONTAINER" 2>/dev/null | grep -q healthy; then + echo "PostgreSQL container is healthy on port $PG_PORT" + break + fi + echo "Waiting for PostgreSQL container health... ($i/30)" + sleep 2 + done + docker inspect --format='{{.State.Health.Status}}' "$PG_CONTAINER" | grep -q healthy + + # TCP连通性检查 + echo "验证TCP连通性 ($PG_HOST:$PG_PORT)..." + wait_tcp_ready "$PG_HOST" "$PG_PORT" 5 + echo "TCP connectivity to PostgreSQL confirmed on port $PG_PORT" + + # 执行迁移 + PYTHONPATH="$PWD/apps/api:$PWD" python3 -m alembic upgrade head + echo "✅ Alembic migrations applied successfully" + + docker rm -f "$PG_CONTAINER" 2>/dev/null || true +fi + +echo "" +echo "=== CI Validate: Alembic迁移验证 通过 ✅ ==="