#!/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迁移验证 通过 ✅ ==="