优化
This commit is contained in:
@@ -1,411 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
if [ "$#" -ne 7 ]; then
|
||||
echo "用法: $0 <环境> <镜像仓库> <镜像标签> <服务> <环境文件> <Compose文件> <项目名>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
DEPLOY_ENV="$1"
|
||||
IMAGE_REPOSITORY="$2"
|
||||
IMAGE_TAG="$3"
|
||||
SERVICE="$4"
|
||||
ENV_FILE="$5"
|
||||
COMPOSE_FILE="$6"
|
||||
BASE_PROJECT="$7"
|
||||
|
||||
case "$DEPLOY_ENV" in
|
||||
test|production) ;;
|
||||
*) echo "蓝绿执行器仅用于 test/production,当前: $DEPLOY_ENV" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
for file in "$ENV_FILE" "$COMPOSE_FILE"; do
|
||||
[ -f "$file" ] || { echo "必需文件不存在: $file" >&2; exit 2; }
|
||||
done
|
||||
|
||||
case "$SERVICE" in
|
||||
*[!A-Za-z0-9_-]*|'') echo "非法服务名: $SERVICE" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
env_value() {
|
||||
sed -n "s/^$1=//p" "$ENV_FILE" | tail -n 1 | tr -d '\r'
|
||||
}
|
||||
|
||||
service_contract() {
|
||||
case "$SERVICE:$COMPOSE_FILE" in
|
||||
gateway:*) echo "gateway|9000|9000|gateway|gateway-active" ;;
|
||||
auth:*) echo "auth|9100|9100|auth-service|auth" ;;
|
||||
iam:*) echo "iam|9201|9201|iam-service|iam" ;;
|
||||
firstleg:*) echo "business|9202|9202|firstleg-service|firstleg" ;;
|
||||
finance:*) echo "finance|9203|9203|finance-service|finance" ;;
|
||||
order:*) echo "order|9204|9204|order-service|order" ;;
|
||||
warehouse:*) echo "warehouse|9205|9205|warehouse-service|warehouse" ;;
|
||||
file:*) echo "file|9206|9206|file-service|file" ;;
|
||||
print:*) echo "print|9207|9207|print-service|print" ;;
|
||||
integration-service:*) echo "integration|9210|9210|integration-service|integration-service" ;;
|
||||
audit-service:*) echo "audit|9700|9700|audit-service|audit-service" ;;
|
||||
gateway-overseas:*) echo "gateway-overseas|9300|9300|gateway-overseas|gateway-overseas-active" ;;
|
||||
lastleg-service:*) echo "lastleg|9303|9303|lastleg-service|lastleg-service" ;;
|
||||
warehouse-overseas-service:*) echo "warehouse-overseas|9305|9305|warehouse-overseas-service|warehouse-overseas-service" ;;
|
||||
event-bridge-service:*overseas*) echo "event-bridge-overseas|9312|9313|event-bridge-overseas|event-bridge-overseas" ;;
|
||||
event-bridge-service:*) echo "event-bridge|9312|9312|event-bridge-cn|event-bridge-cn" ;;
|
||||
*) echo "不支持蓝绿部署的服务: $SERVICE" >&2; exit 2 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
IFS='|' read -r CONTAINER_STEM INTERNAL_PORT BASE_PORT NACOS_SERVICE ROUTING_ALIAS <<EOF
|
||||
$(service_contract)
|
||||
EOF
|
||||
|
||||
STATE_ROOT="${BLUE_GREEN_STATE_ROOT:-/data/firstleg/deploy/blue-green-state}"
|
||||
STATE_KEY="${BASE_PROJECT}-${SERVICE}"
|
||||
STATE_FILE="$STATE_ROOT/$STATE_KEY.active"
|
||||
ROUTING_NETWORK="${BLUE_GREEN_ROUTING_NETWORK:-firstleg-${DEPLOY_ENV}-routing}"
|
||||
HEALTH_TIMEOUT="${DEPLOY_HEALTH_TIMEOUT_SECONDS:-240}"
|
||||
HEALTH_POLL="${DEPLOY_HEALTH_POLL_SECONDS:-5}"
|
||||
STABILIZATION_SECONDS="${BLUE_GREEN_STABILIZATION_SECONDS:-10}"
|
||||
DRAIN_SECONDS="${BLUE_GREEN_DRAIN_SECONDS:-30}"
|
||||
STOP_TIMEOUT="${BLUE_GREEN_STOP_TIMEOUT_SECONDS:-45}"
|
||||
IMAGE="$IMAGE_REPOSITORY/$SERVICE:$IMAGE_TAG"
|
||||
|
||||
for value in "$HEALTH_TIMEOUT" "$HEALTH_POLL" "$STABILIZATION_SECONDS" "$DRAIN_SECONDS" "$STOP_TIMEOUT"; do
|
||||
case "$value" in ''|*[!0-9]*) echo "蓝绿时间参数必须为非负整数: $value" >&2; exit 2 ;; esac
|
||||
done
|
||||
|
||||
mkdir -p "$STATE_ROOT"
|
||||
chmod 750 "$STATE_ROOT"
|
||||
docker network inspect "$ROUTING_NETWORK" >/dev/null 2>&1 || docker network create "$ROUTING_NETWORK" >/dev/null
|
||||
|
||||
legacy_container="firstleg-${DEPLOY_ENV}-${CONTAINER_STEM}"
|
||||
active_slot=""
|
||||
active_container=""
|
||||
active_port=""
|
||||
active_worker=""
|
||||
if [ -s "$STATE_FILE" ]; then
|
||||
IFS='|' read -r active_slot active_container active_port active_image active_worker < "$STATE_FILE"
|
||||
elif docker inspect "$legacy_container" >/dev/null 2>&1; then
|
||||
active_slot="legacy"
|
||||
active_container="$legacy_container"
|
||||
active_port="$BASE_PORT"
|
||||
fi
|
||||
|
||||
if [ "$active_slot" = "blue" ]; then
|
||||
target_slot="green"
|
||||
else
|
||||
target_slot="blue"
|
||||
[ "$active_slot" = "legacy" ] && target_slot="green"
|
||||
fi
|
||||
|
||||
if [ "$target_slot" = "blue" ]; then
|
||||
target_port=$((BASE_PORT + 20000))
|
||||
else
|
||||
target_port=$((BASE_PORT + 10000))
|
||||
fi
|
||||
candidate="firstleg-${DEPLOY_ENV}-${CONTAINER_STEM}-${target_slot}"
|
||||
worker_candidate="${candidate}-worker"
|
||||
|
||||
if docker inspect "$candidate" >/dev/null 2>&1; then
|
||||
if [ "$candidate" = "$active_container" ]; then
|
||||
echo "活动槽状态异常,候选容器与活动容器相同: $candidate" >&2
|
||||
exit 1
|
||||
fi
|
||||
docker rm -f "$candidate" >/dev/null
|
||||
fi
|
||||
|
||||
compose() {
|
||||
docker compose --project-name "$BASE_PROJECT" --env-file "$ENV_FILE" -f "$COMPOSE_FILE" "$@"
|
||||
}
|
||||
|
||||
container_health() {
|
||||
container="$1"
|
||||
docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' \
|
||||
"$container" 2>/dev/null || echo missing
|
||||
}
|
||||
|
||||
cleanup_candidate() {
|
||||
docker rm -f "$candidate" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
wait_for_container() {
|
||||
container="$1"
|
||||
label="$2"
|
||||
elapsed=0
|
||||
while [ "$elapsed" -lt "$HEALTH_TIMEOUT" ]; do
|
||||
state="$(container_health "$container")"
|
||||
echo "$label 健康状态: $state(已等待 ${elapsed}s)"
|
||||
case "$state" in
|
||||
healthy) return 0 ;;
|
||||
unhealthy|exited|dead|missing) return 1 ;;
|
||||
esac
|
||||
sleep "$HEALTH_POLL"
|
||||
elapsed=$((elapsed + HEALTH_POLL))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
needs_worker() {
|
||||
case "$SERVICE" in
|
||||
iam|firstleg|finance|order|event-bridge-service|lastleg-service) return 0 ;;
|
||||
file)
|
||||
[ "$(env_value FILE_EXPORT_CONSUMER_ENABLED)" = true ] \
|
||||
|| [ "$(env_value FILE_EXPORT_PUBLISHER_ENABLED)" = true ] \
|
||||
|| [ "$(env_value FILE_EXPORT_RECONCILER_ENABLED)" = true ] \
|
||||
|| [ "$(env_value AUDIT_OUTBOX_PUBLISHER_ENABLED)" = true ]
|
||||
;;
|
||||
audit-service)
|
||||
[ "$(env_value AUDIT_MQ_CONSUMER_ENABLED)" = true ] \
|
||||
|| [ "$(env_value AUDIT_DLQ_CONSUMER_ENABLED)" = true ] \
|
||||
|| [ "$(env_value AUDIT_DLQ_COMPENSATION_ENABLED)" = true ]
|
||||
;;
|
||||
auth|warehouse|warehouse-overseas-service)
|
||||
[ "$(env_value AUDIT_OUTBOX_PUBLISHER_ENABLED)" = true ]
|
||||
;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
start_worker_candidate() {
|
||||
docker rm -f "$worker_candidate" >/dev/null 2>&1 || true
|
||||
echo "启动单例 Worker: $worker_candidate"
|
||||
if [ "$SERVICE" = file ]; then
|
||||
worker_java_options="-Xms512m -Xmx2g -XX:+ExitOnOutOfMemoryError -Dfile.encoding=UTF-8"
|
||||
else
|
||||
worker_java_options="-Xms256m -Xmx1g -XX:+ExitOnOutOfMemoryError -Dfile.encoding=UTF-8"
|
||||
fi
|
||||
if ! compose run -d --no-deps --name "$worker_candidate" \
|
||||
-e "NACOS_REGISTER_ENABLED=false" \
|
||||
-e "BAIYUE_RUNTIME_SCHEDULING_ENABLED=true" \
|
||||
-e "BAIYUE_DEPLOY_SLOT=${target_slot}-worker" \
|
||||
-e "JAVA_TOOL_OPTIONS=$worker_java_options" \
|
||||
-e "BAIYUE_RELEASE_ID=$IMAGE_TAG" "$SERVICE" >/dev/null; then
|
||||
return 1
|
||||
fi
|
||||
docker network connect "$ROUTING_NETWORK" "$worker_candidate"
|
||||
if ! wait_for_container "$worker_candidate" "候选 Worker"; then
|
||||
docker logs --tail 200 "$worker_candidate" 2>&1 || true
|
||||
docker rm -f "$worker_candidate" >/dev/null 2>&1 || true
|
||||
return 1
|
||||
fi
|
||||
worker_image="$(docker inspect --format '{{.Config.Image}}' "$worker_candidate")"
|
||||
[ "$worker_image" = "$IMAGE" ] || return 1
|
||||
}
|
||||
|
||||
connect_routing_aliases() {
|
||||
container="$1"
|
||||
shift
|
||||
docker network inspect "$ROUTING_NETWORK" --format '{{json .Containers}}' | grep -q "\"$container\"" \
|
||||
&& return 0
|
||||
set -- docker network connect $(for alias_name in "$@"; do printf '%s\n' --alias "$alias_name"; done)
|
||||
"$@" "$ROUTING_NETWORK" "$container"
|
||||
}
|
||||
|
||||
bootstrap_legacy_routes() {
|
||||
for pair in \
|
||||
gateway-active,gateway:gateway auth,auth-service:auth iam,iam-service:iam \
|
||||
firstleg,firstleg-service:business finance,finance-service:finance order,order-service:order \
|
||||
warehouse,warehouse-service:warehouse file,file-service:file print,print-service:print \
|
||||
integration-service:integration audit-service:audit \
|
||||
gateway-overseas-active,gateway-overseas:gateway-overseas \
|
||||
lastleg-service:lastleg warehouse-overseas-service:warehouse-overseas \
|
||||
event-bridge-cn:event-bridge event-bridge-overseas:event-bridge-overseas
|
||||
do
|
||||
aliases="${pair%%:*}"
|
||||
stem="${pair#*:}"
|
||||
container="firstleg-${DEPLOY_ENV}-${stem}"
|
||||
if docker inspect "$container" >/dev/null 2>&1; then
|
||||
connect_routing_aliases "$container" $(printf '%s' "$aliases" | tr ',' ' ') || true
|
||||
fi
|
||||
done
|
||||
for container in firstleg-prod-admin-web firstleg-prod-client-web; do
|
||||
if docker inspect "$container" >/dev/null 2>&1; then
|
||||
connect_routing_aliases "$container" "${container##*-}" || true
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
switch_gateway_proxy() {
|
||||
[ "$SERVICE" = "gateway" ] || [ "$SERVICE" = "gateway-overseas" ] || return 0
|
||||
if [ "$DEPLOY_ENV" = test ]; then
|
||||
echo "测试 Gateway 仍由固定入口承载,不在后端蓝绿执行器中切换" >&2
|
||||
return 1
|
||||
fi
|
||||
if [ "$SERVICE" = "gateway" ]; then required_alias=gateway-active; else required_alias=gateway-overseas-active; fi
|
||||
for frontend_service in admin-web client-web; do
|
||||
frontend_state="$STATE_ROOT/frontend-$frontend_service.active"
|
||||
if [ ! -s "$frontend_state" ]; then
|
||||
echo "$frontend_service 尚未完成首次蓝绿迁移,拒绝切换 Gateway" >&2
|
||||
return 1
|
||||
fi
|
||||
IFS='|' read -r frontend_slot frontend frontend_port frontend_image < "$frontend_state"
|
||||
if ! docker inspect "$frontend" >/dev/null 2>&1 \
|
||||
|| ! docker exec "$frontend" nginx -T 2>&1 | grep -q "$required_alias"; then
|
||||
echo "$frontend 尚未使用稳定上游 $required_alias,拒绝切换 Gateway" >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
route_root="${BLUE_GREEN_ROUTE_CONFIG_ROOT:-/www/server/panel/vhost/nginx}"
|
||||
if [ "$SERVICE" = "gateway" ]; then
|
||||
config="$route_root/00-blue-green-gateway.conf"
|
||||
else
|
||||
config="$route_root/00-blue-green-gateway-overseas.conf"
|
||||
fi
|
||||
if [ -z "$config" ] || [ ! -f "$config" ]; then
|
||||
echo "$SERVICE 蓝绿切换要求环境文件配置有效的 Nginx 配置路径" >&2
|
||||
return 1
|
||||
fi
|
||||
backup="$config.blue-green-backup"
|
||||
cp -f "$config" "$backup"
|
||||
if ! sed -i "s#127\.0\.0\.1:${active_port:-$BASE_PORT}#127.0.0.1:$target_port#g" "$config" \
|
||||
|| cmp -s "$config" "$backup"; then
|
||||
cp -f "$backup" "$config"
|
||||
echo "未能在 Nginx 配置中切换 $SERVICE 上游端口" >&2
|
||||
return 1
|
||||
fi
|
||||
if ! nginx -t || ! nginx -s reload; then
|
||||
cp -f "$backup" "$config"
|
||||
nginx -t && nginx -s reload || true
|
||||
return 1
|
||||
fi
|
||||
rm -f "$backup"
|
||||
}
|
||||
|
||||
reload_frontend_proxies() {
|
||||
[ "$SERVICE" = "gateway" ] || [ "$SERVICE" = "gateway-overseas" ] || return 0
|
||||
[ "$DEPLOY_ENV" = production ] || return 0
|
||||
for frontend_service in admin-web client-web; do
|
||||
frontend_state="$STATE_ROOT/frontend-$frontend_service.active"
|
||||
[ -s "$frontend_state" ] || return 1
|
||||
IFS='|' read -r frontend_slot frontend frontend_port frontend_image < "$frontend_state"
|
||||
docker inspect "$frontend" >/dev/null 2>&1 || return 1
|
||||
docker exec "$frontend" nginx -t
|
||||
docker exec "$frontend" nginx -s reload
|
||||
done
|
||||
}
|
||||
|
||||
rollback_gateway_proxy() {
|
||||
[ "$SERVICE" = "gateway" ] || [ "$SERVICE" = "gateway-overseas" ] || return 0
|
||||
[ "$DEPLOY_ENV" = production ] || return 0
|
||||
route_root="${BLUE_GREEN_ROUTE_CONFIG_ROOT:-/www/server/panel/vhost/nginx}"
|
||||
if [ "$SERVICE" = "gateway" ]; then
|
||||
config="$route_root/00-blue-green-gateway.conf"
|
||||
else
|
||||
config="$route_root/00-blue-green-gateway-overseas.conf"
|
||||
fi
|
||||
old_port="${active_port:-$BASE_PORT}"
|
||||
sed -i "s#127\.0\.0\.1:$target_port#127.0.0.1:$old_port#g" "$config"
|
||||
nginx -t && nginx -s reload
|
||||
}
|
||||
|
||||
bootstrap_legacy_routes
|
||||
|
||||
export DEPLOY_ENV IMAGE_REPOSITORY IMAGE_TAG
|
||||
echo "启动候选槽: service=$SERVICE slot=$target_slot port=$target_port image=$IMAGE"
|
||||
if ! compose run -d --no-deps --name "$candidate" \
|
||||
-e "BAIYUE_DEPLOY_SLOT=$target_slot" -e "BAIYUE_RELEASE_ID=$IMAGE_TAG" \
|
||||
-e "BAIYUE_RUNTIME_SCHEDULING_ENABLED=false" \
|
||||
-e "AUDIT_OUTBOX_PUBLISHER_ENABLED=false" \
|
||||
-e "NOTIFICATION_SCHEDULER_ENABLED=false" \
|
||||
-e "NOTIFICATION_MQ_CONSUMER_ENABLED=false" \
|
||||
-e "FILE_EXPORT_CONSUMER_ENABLED=false" \
|
||||
-e "FILE_EXPORT_PUBLISHER_ENABLED=false" \
|
||||
-e "FILE_EXPORT_RECONCILER_ENABLED=false" \
|
||||
-e "AUDIT_MQ_CONSUMER_ENABLED=false" \
|
||||
-e "AUDIT_DLQ_CONSUMER_ENABLED=false" \
|
||||
-e "AUDIT_DLQ_COMPENSATION_ENABLED=false" \
|
||||
-p "${PUBLISHED_BIND_ADDRESS:-0.0.0.0}:$target_port:$INTERNAL_PORT" "$SERVICE" >/dev/null; then
|
||||
cleanup_candidate
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! wait_for_container "$candidate" "候选 Web 槽 $target_slot"; then
|
||||
echo "候选槽未通过健康检查,保留旧实例" >&2
|
||||
docker logs --tail 200 "$candidate" 2>&1 || true
|
||||
cleanup_candidate
|
||||
exit 1
|
||||
fi
|
||||
|
||||
running_image="$(docker inspect --format '{{.Config.Image}}' "$candidate")"
|
||||
if [ "$running_image" != "$IMAGE" ]; then
|
||||
echo "候选镜像不一致: expected=$IMAGE actual=$running_image" >&2
|
||||
cleanup_candidate
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$NACOS_SERVICE" = "$ROUTING_ALIAS" ]; then
|
||||
connect_routing_aliases "$candidate" "$ROUTING_ALIAS"
|
||||
else
|
||||
connect_routing_aliases "$candidate" "$ROUTING_ALIAS" "$NACOS_SERVICE"
|
||||
fi
|
||||
|
||||
if [ "$STABILIZATION_SECONDS" -gt 0 ]; then
|
||||
echo "候选槽稳定观察 ${STABILIZATION_SECONDS}s"
|
||||
sleep "$STABILIZATION_SECONDS"
|
||||
[ "$(container_health "$candidate")" = "healthy" ] || { cleanup_candidate; exit 1; }
|
||||
fi
|
||||
|
||||
if ! switch_gateway_proxy; then
|
||||
cleanup_candidate
|
||||
exit 1
|
||||
fi
|
||||
|
||||
worker_required=false
|
||||
if needs_worker; then worker_required=true; fi
|
||||
|
||||
if [ "$worker_required" = true ] && [ "$active_slot" != legacy ]; then
|
||||
if [ -n "$active_worker" ] && docker inspect "$active_worker" >/dev/null 2>&1; then
|
||||
echo "暂停旧 Worker,避免新旧任务并行: $active_worker"
|
||||
docker stop --time "$STOP_TIMEOUT" "$active_worker" >/dev/null
|
||||
fi
|
||||
if ! start_worker_candidate; then
|
||||
[ -z "$active_worker" ] || docker start "$active_worker" >/dev/null 2>&1 || true
|
||||
rollback_gateway_proxy || true
|
||||
cleanup_candidate
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
old_container_stopped=false
|
||||
if [ -n "$active_container" ] && docker inspect "$active_container" >/dev/null 2>&1; then
|
||||
if [ "$DRAIN_SECONDS" -gt 0 ]; then
|
||||
echo "蓝绿切换后连接排空 ${DRAIN_SECONDS}s,旧 Web 实例保持运行: $active_container"
|
||||
sleep "$DRAIN_SECONDS"
|
||||
fi
|
||||
echo "优雅停止旧 Web 实例: $active_container"
|
||||
docker stop --time "$STOP_TIMEOUT" "$active_container" >/dev/null
|
||||
old_container_stopped=true
|
||||
fi
|
||||
|
||||
if [ "$worker_required" = true ] && [ "$active_slot" = legacy ]; then
|
||||
if ! start_worker_candidate; then
|
||||
[ "$old_container_stopped" = false ] || docker start "$active_container" >/dev/null 2>&1 || true
|
||||
rollback_gateway_proxy || true
|
||||
cleanup_candidate
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! reload_frontend_proxies; then
|
||||
[ "$old_container_stopped" = false ] || docker start "$active_container" >/dev/null 2>&1 || true
|
||||
if [ "$worker_required" = true ]; then
|
||||
docker rm -f "$worker_candidate" >/dev/null 2>&1 || true
|
||||
[ -z "$active_worker" ] || docker start "$active_worker" >/dev/null 2>&1 || true
|
||||
fi
|
||||
rollback_gateway_proxy || true
|
||||
cleanup_candidate
|
||||
reload_frontend_proxies || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
[ -z "$active_worker" ] || [ "$active_worker" = "$worker_candidate" ] \
|
||||
|| docker rm "$active_worker" >/dev/null 2>&1 || true
|
||||
[ "$old_container_stopped" = false ] || docker rm "$active_container" >/dev/null
|
||||
|
||||
if [ "$worker_required" = true ]; then
|
||||
next_worker="$worker_candidate"
|
||||
else
|
||||
next_worker=""
|
||||
fi
|
||||
printf '%s|%s|%s|%s|%s\n' "$target_slot" "$candidate" "$target_port" "$IMAGE" "$next_worker" > "$STATE_FILE.tmp"
|
||||
chmod 640 "$STATE_FILE.tmp"
|
||||
mv -f "$STATE_FILE.tmp" "$STATE_FILE"
|
||||
echo "蓝绿切换完成: service=$SERVICE active=$target_slot container=$candidate port=$target_port"
|
||||
@@ -28,177 +28,45 @@ for value_name in HEALTH_TIMEOUT_SECONDS HEALTH_POLL_SECONDS; do
|
||||
esac
|
||||
done
|
||||
|
||||
case "$DEPLOY_ENV" in
|
||||
development|test|production) ;;
|
||||
*)
|
||||
echo "仅支持 development/test/production,当前: $DEPLOY_ENV" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$DEPLOY_ENV" = "development" ]; then
|
||||
PUBLISHED_BIND_ADDRESS=127.0.0.1
|
||||
else
|
||||
PUBLISHED_BIND_ADDRESS="${PUBLISHED_BIND_ADDRESS:-0.0.0.0}"
|
||||
fi
|
||||
export PUBLISHED_BIND_ADDRESS
|
||||
|
||||
[ "$DEPLOY_ENV" = "test" ] || { echo "Only test is supported" >&2; exit 2; }
|
||||
case "$SERVICE" in
|
||||
*[!A-Za-z0-9_-]*|'')
|
||||
echo "非法服务名: $SERVICE" >&2
|
||||
exit 2
|
||||
;;
|
||||
gateway|auth|iam|firstleg|finance|order|warehouse|file|print|integration-service|lastleg-service|warehouse-overseas-service|audit-service) ;;
|
||||
*) echo "Unknown or retired service: $SERVICE" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
echo "必需文件不存在: $ENV_FILE" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
env_file_has_key() {
|
||||
grep -q "^$1=" "$ENV_FILE"
|
||||
}
|
||||
|
||||
env_file_value() {
|
||||
sed -n "s/^$1=//p" "$ENV_FILE" | tail -n 1 | tr -d '\r'
|
||||
}
|
||||
|
||||
case "$SERVICE" in
|
||||
gateway-overseas|lastleg-service|warehouse-overseas-service)
|
||||
COMPOSE_FILE="deploy/docker-compose.overseas.yml"
|
||||
PROJECT_NAME="firstleg-$DEPLOY_ENV-overseas"
|
||||
;;
|
||||
event-bridge-service)
|
||||
BRIDGE_REGION="${EVENT_BRIDGE_REGION_OVERRIDE:-}"
|
||||
if [ -z "$BRIDGE_REGION" ] && env_file_has_key REGION_CODE; then
|
||||
BRIDGE_REGION="$(env_file_value REGION_CODE)"
|
||||
fi
|
||||
if [ -z "$BRIDGE_REGION" ]; then
|
||||
echo "部署 event-bridge-service 时,环境文件必须显式包含 REGION_CODE=CN 或 REGION_CODE=OVERSEAS" >&2
|
||||
exit 2
|
||||
fi
|
||||
case "$BRIDGE_REGION" in
|
||||
CN) ;;
|
||||
OVERSEAS)
|
||||
COMPOSE_FILE="deploy/docker-compose.overseas.yml"
|
||||
PROJECT_NAME="firstleg-$DEPLOY_ENV-overseas"
|
||||
;;
|
||||
*)
|
||||
echo "部署 event-bridge-service 时,REGION_CODE 只能为 CN 或 OVERSEAS" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$SERVICE" = "gateway-overseas" ]; then
|
||||
if ! env_file_has_key AUTH_SESSION_VALIDATION_URL || [ -z "$(env_file_value AUTH_SESSION_VALIDATION_URL)" ]; then
|
||||
echo "部署 gateway-overseas 时,环境文件必须包含非空 AUTH_SESSION_VALIDATION_URL" >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
for file in "$ENV_FILE" "$COMPOSE_FILE"; do
|
||||
if [ ! -f "$file" ]; then
|
||||
echo "必需文件不存在: $file" >&2
|
||||
exit 2
|
||||
fi
|
||||
[ "${TARGET_IS_LOCAL:-false}" = "true" ] || { echo "TEST deploy must run on the target host" >&2; exit 2; }
|
||||
[ -f "$ENV_FILE" ] || { echo "Environment file missing" >&2; exit 2; }
|
||||
env_file_value() { sed -n "s/^$1=//p" "$ENV_FILE" | tail -n 1 | tr -d '\r'; }
|
||||
for key in NACOS_SERVER_ADDR NACOS_NAMESPACE NACOS_USERNAME NACOS_PASSWORD REDIS_HOST REDIS_PORT REDIS_DATABASE REDIS_TOKEN_KEY_PREFIX INTERNAL_SERVICE_TOKEN JWT_RSA_JWK_SET JWT_CURRENT_KID ROCKETMQ_NAME_SERVER; do
|
||||
[ -n "$(env_file_value "$key")" ] || { echo "Missing $key" >&2; exit 2; }
|
||||
done
|
||||
|
||||
if [ "$SERVICE" = "finance" ]; then
|
||||
for key in FINANCE_DB_URL FINANCE_DB_USERNAME FINANCE_DB_PASSWORD; do
|
||||
if ! env_file_has_key "$key" || [ -z "$(env_file_value "$key")" ]; then
|
||||
echo "部署 finance 服务时,环境文件必须包含非空 $key" >&2
|
||||
exit 2
|
||||
fi
|
||||
[ "$(env_file_value MQ_TOPIC_PREFIX)" = "test-" ] || { echo "MQ_TOPIC_PREFIX must be test-" >&2; exit 2; }
|
||||
case "$SERVICE" in
|
||||
gateway|print|integration-service) db_prefix='' ;;
|
||||
lastleg-service) db_prefix=LASTLEG ;;
|
||||
warehouse-overseas-service) db_prefix=WAREHOUSE_OVERSEAS ;;
|
||||
audit-service) db_prefix=AUDIT ;;
|
||||
*) db_prefix=$(printf '%s' "$SERVICE" | tr '[:lower:]' '[:upper:]') ;;
|
||||
esac
|
||||
if [ -n "$db_prefix" ]; then
|
||||
for suffix in URL USERNAME PASSWORD; do
|
||||
key="${db_prefix}_DB_${suffix}"
|
||||
[ -n "$(env_file_value "$key")" ] || { echo "Missing $key" >&2; exit 2; }
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$PROJECT_NAME" = "firstleg-$DEPLOY_ENV-overseas" ]; then
|
||||
for key in NACOS_SERVER_ADDR OVERSEAS_NACOS_USERNAME OVERSEAS_NACOS_PASSWORD OVERSEAS_NACOS_NAMESPACE INTERNAL_SERVICE_TOKEN JWT_ISSUER OVERSEAS_JWT_AUDIENCE JWT_JWK_SET_URI; do
|
||||
if ! env_file_has_key "$key" || [ -z "$(env_file_value "$key")" ]; then
|
||||
echo "部署海外服务时,环境文件必须包含非空 $key" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$SERVICE" = "firstleg" ]; then
|
||||
if ! env_file_has_key LASTLEG_EVENT_BRIDGE_URL || [ -z "$(env_file_value LASTLEG_EVENT_BRIDGE_URL)" ]; then
|
||||
echo "部署 firstleg 时,环境文件必须包含非空 LASTLEG_EVENT_BRIDGE_URL" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [ "$(env_file_value TERMINAL_DRAYAGE_MAIL_ENABLED)" = "true" ]; then
|
||||
for key in TERMINAL_DRAYAGE_MAIL_HOST TERMINAL_DRAYAGE_MAIL_PORT TERMINAL_DRAYAGE_MAIL_USERNAME TERMINAL_DRAYAGE_MAIL_PASSWORD; do
|
||||
if ! env_file_has_key "$key" || [ -z "$(env_file_value "$key")" ]; then
|
||||
echo "启用码头车队邮件时,环境文件必须包含非空 $key" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$SERVICE" = "lastleg-service" ]; then
|
||||
for key in LASTLEG_DB_URL LASTLEG_DB_USERNAME LASTLEG_DB_PASSWORD FIRSTLEG_EVENT_BRIDGE_URL DOMESTIC_WAREHOUSE_URL FILE_SERVICE_URL; do
|
||||
if ! env_file_has_key "$key" || [ -z "$(env_file_value "$key")" ]; then
|
||||
echo "部署 lastleg-service 时,环境文件必须包含非空 $key" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [ "$SERVICE" = "firstleg" ] && [ "$(env_file_value TERMINAL_DRAYAGE_MAIL_ENABLED)" = "true" ]; then
|
||||
for key in TERMINAL_DRAYAGE_MAIL_HOST TERMINAL_DRAYAGE_MAIL_PORT TERMINAL_DRAYAGE_MAIL_USERNAME TERMINAL_DRAYAGE_MAIL_PASSWORD; do
|
||||
[ -n "$(env_file_value "$key")" ] || { echo "Missing $key" >&2; exit 2; }
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$SERVICE" = "warehouse-overseas-service" ]; then
|
||||
for key in WAREHOUSE_OVERSEAS_DB_URL WAREHOUSE_OVERSEAS_DB_USERNAME WAREHOUSE_OVERSEAS_DB_PASSWORD; do
|
||||
if ! env_file_has_key "$key" || [ -z "$(env_file_value "$key")" ]; then
|
||||
echo "部署 warehouse-overseas-service 时,环境文件必须包含非空 $key" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$SERVICE" = "event-bridge-service" ]; then
|
||||
if [ "$PROJECT_NAME" = "firstleg-$DEPLOY_ENV-overseas" ]; then
|
||||
EVENT_BRIDGE_KEYS="EVENT_BRIDGE_OVERSEAS_DB_URL EVENT_BRIDGE_OVERSEAS_DB_USERNAME EVENT_BRIDGE_OVERSEAS_DB_PASSWORD EVENT_CONSUMER_URL"
|
||||
else
|
||||
EVENT_BRIDGE_KEYS="EVENT_BRIDGE_DB_URL EVENT_BRIDGE_DB_USERNAME EVENT_BRIDGE_DB_PASSWORD FIRSTLEG_CONTAINER_EVENT_CONSUMER_URL"
|
||||
fi
|
||||
for key in $EVENT_BRIDGE_KEYS; do
|
||||
if ! env_file_has_key "$key" || [ -z "$(env_file_value "$key")" ]; then
|
||||
echo "部署 event-bridge-service 时,环境文件必须包含非空 $key" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$SERVICE" = "file" ]; then
|
||||
if ! env_file_has_key ROCKETMQ_NAME_SERVER || [ -z "$(env_file_value ROCKETMQ_NAME_SERVER)" ]; then
|
||||
echo "部署 file 服务时,环境文件必须包含非空 ROCKETMQ_NAME_SERVER" >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! env_file_has_key MQ_TOPIC_PREFIX; then
|
||||
echo "部署 file 服务时,环境文件必须显式包含 MQ_TOPIC_PREFIX" >&2
|
||||
exit 2
|
||||
fi
|
||||
MQ_PREFIX=$(env_file_value MQ_TOPIC_PREFIX)
|
||||
if [ "$DEPLOY_ENV" = "test" ] && [ "$MQ_PREFIX" != "test-" ]; then
|
||||
echo "测试环境部署 file 服务时 MQ_TOPIC_PREFIX 必须为 test-" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [ "$DEPLOY_ENV" = "development" ] && { [ -z "$MQ_PREFIX" ] || [ "$MQ_PREFIX" = "test-" ]; }; then
|
||||
echo "开发环境部署 file 服务时 MQ_TOPIC_PREFIX 必须非空且不能使用 test-" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [ "$DEPLOY_ENV" = "production" ] && [ -n "$MQ_PREFIX" ]; then
|
||||
echo "生产环境部署 file 服务时 MQ_TOPIC_PREFIX 必须为空,使用正式 Topic" >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
# Architecture/schema changes must use whole-release recovery, never an implicit old image.
|
||||
export PUBLISHED_BIND_ADDRESS=127.0.0.1
|
||||
: "${REGISTRY_USER:?REGISTRY_USER 未设置}"
|
||||
: "${REGISTRY_PASSWORD:?REGISTRY_PASSWORD 未设置}"
|
||||
|
||||
local_registry_logout() {
|
||||
docker logout "$REGISTRY_HOST" >/dev/null 2>&1 || true
|
||||
if [ -n "${deploy_docker_config:-}" ]; then rm -rf -- "$deploy_docker_config"; fi
|
||||
}
|
||||
|
||||
local_compose() {
|
||||
@@ -255,8 +123,8 @@ wait_for_local_health() {
|
||||
return 0
|
||||
;;
|
||||
no-health:running)
|
||||
echo "容器未配置 HEALTHCHECK,以运行状态作为成功依据" >&2
|
||||
return 0
|
||||
echo "Missing HEALTHCHECK; deployment failed" >&2
|
||||
return 1
|
||||
;;
|
||||
unhealthy|no-health:exited|no-health:dead)
|
||||
return 1
|
||||
@@ -270,6 +138,10 @@ wait_for_local_health() {
|
||||
}
|
||||
|
||||
deploy_local() {
|
||||
deploy_docker_config=$(mktemp -d)
|
||||
DOCKER_CONFIG="$deploy_docker_config"
|
||||
export DOCKER_CONFIG
|
||||
trap local_registry_logout EXIT HUP INT TERM
|
||||
printf '%s' "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin "$REGISTRY_HOST"
|
||||
trap local_registry_logout EXIT HUP INT TERM
|
||||
previous_image="$(local_container_image || true)"
|
||||
@@ -306,6 +178,11 @@ deploy_local() {
|
||||
else
|
||||
echo "新镜像容器不存在,无法读取启动日志" >&2
|
||||
fi
|
||||
if [ "${ALLOW_SAME_CONTRACT_ROLLBACK:-false}" != "true" ]; then
|
||||
echo "Automatic rollback disabled: keep writes closed and recover the matched release" >&2
|
||||
local_compose stop "$SERVICE" >/dev/null 2>&1 || true
|
||||
return 1
|
||||
fi
|
||||
expected_prefix="$IMAGE_REPOSITORY/$SERVICE:"
|
||||
case "$previous_image" in
|
||||
"$expected_prefix"*)
|
||||
@@ -346,241 +223,4 @@ deploy_local() {
|
||||
return 1
|
||||
}
|
||||
|
||||
if [ "${TARGET_IS_LOCAL:-false}" = "true" ]; then
|
||||
echo "镜像已在目标服务器本机,直接部署: $IMAGE"
|
||||
if [ "$DEPLOY_ENV" = "production" ]; then
|
||||
printf '%s' "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin "$REGISTRY_HOST"
|
||||
trap local_registry_logout EXIT HUP INT TERM
|
||||
docker pull "$IMAGE"
|
||||
sh deploy/scripts/deploy-blue-green-service.sh \
|
||||
"$DEPLOY_ENV" "$IMAGE_REPOSITORY" "$IMAGE_TAG" "$SERVICE" "$ENV_FILE" "$COMPOSE_FILE" "$PROJECT_NAME"
|
||||
local_registry_logout
|
||||
trap - EXIT HUP INT TERM
|
||||
exit 0
|
||||
fi
|
||||
deploy_local
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SSH_TARGET="${TARGET_SSH_TARGET:?TARGET_SSH_TARGET 未设置}"
|
||||
SSH_KEY="${TARGET_SSH_KEY:?TARGET_SSH_KEY 未设置}"
|
||||
SSH_KNOWN_HOSTS="${TARGET_SSH_KNOWN_HOSTS:?TARGET_SSH_KNOWN_HOSTS 未设置}"
|
||||
REMOTE_DIR="${TARGET_DEPLOY_DIR:?TARGET_DEPLOY_DIR 未设置}"
|
||||
|
||||
for file in "$SSH_KEY" "$SSH_KNOWN_HOSTS"; do
|
||||
if [ ! -f "$file" ]; then
|
||||
echo "SSH 文件不存在: $file" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
run_ssh() {
|
||||
ssh -i "$SSH_KEY" \
|
||||
-o BatchMode=yes \
|
||||
-o Compression=no \
|
||||
-o StrictHostKeyChecking=yes \
|
||||
-o UserKnownHostsFile="$SSH_KNOWN_HOSTS" \
|
||||
-o ConnectTimeout=10 \
|
||||
"$SSH_TARGET" "$@"
|
||||
}
|
||||
|
||||
run_scp() {
|
||||
scp -i "$SSH_KEY" \
|
||||
-o BatchMode=yes \
|
||||
-o Compression=no \
|
||||
-o StrictHostKeyChecking=yes \
|
||||
-o UserKnownHostsFile="$SSH_KNOWN_HOSTS" \
|
||||
-o ConnectTimeout=10 \
|
||||
"$@"
|
||||
}
|
||||
|
||||
run_ssh install -d -m 750 "$REMOTE_DIR"
|
||||
|
||||
SUFFIX="${IMAGE_TAG}.$$.${SERVICE}"
|
||||
REMOTE_ENV_NAME=".env.$PROJECT_NAME"
|
||||
REMOTE_COMPOSE_NAME="docker-compose.$PROJECT_NAME.yml"
|
||||
REMOTE_BLUE_GREEN_NAME="deploy-blue-green-service.sh"
|
||||
REMOTE_ENV_TMP="$REMOTE_DIR/$REMOTE_ENV_NAME.$SUFFIX.tmp"
|
||||
REMOTE_COMPOSE_TMP="$REMOTE_DIR/$REMOTE_COMPOSE_NAME.$SUFFIX.tmp"
|
||||
REMOTE_BLUE_GREEN_TMP="$REMOTE_DIR/$REMOTE_BLUE_GREEN_NAME.$SUFFIX.tmp"
|
||||
run_scp "$ENV_FILE" "$SSH_TARGET:$REMOTE_ENV_TMP"
|
||||
run_scp "$COMPOSE_FILE" "$SSH_TARGET:$REMOTE_COMPOSE_TMP"
|
||||
run_scp "deploy/scripts/deploy-blue-green-service.sh" "$SSH_TARGET:$REMOTE_BLUE_GREEN_TMP"
|
||||
run_ssh sh -s -- "$REMOTE_ENV_TMP" "$REMOTE_COMPOSE_TMP" "$REMOTE_BLUE_GREEN_TMP" \
|
||||
"$REMOTE_DIR/$REMOTE_ENV_NAME" "$REMOTE_DIR/$REMOTE_COMPOSE_NAME" "$REMOTE_DIR/$REMOTE_BLUE_GREEN_NAME" <<'REMOTE_PREPARE'
|
||||
set -eu
|
||||
env_tmp="$1"
|
||||
compose_tmp="$2"
|
||||
blue_green_tmp="$3"
|
||||
env_target="$4"
|
||||
compose_target="$5"
|
||||
blue_green_target="$6"
|
||||
chmod 600 "$env_tmp"
|
||||
chmod 644 "$compose_tmp"
|
||||
chmod 750 "$blue_green_tmp"
|
||||
mv -f "$env_tmp" "$env_target"
|
||||
mv -f "$compose_tmp" "$compose_target"
|
||||
mv -f "$blue_green_tmp" "$blue_green_target"
|
||||
REMOTE_PREPARE
|
||||
|
||||
remote_registry_logout() {
|
||||
run_ssh docker logout "$REGISTRY_HOST" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
echo "目标服务器从共享仓库拉取增量镜像: $IMAGE"
|
||||
printf '%s' "$REGISTRY_PASSWORD" | run_ssh docker login -u "$REGISTRY_USER" --password-stdin "$REGISTRY_HOST"
|
||||
trap remote_registry_logout EXIT HUP INT TERM
|
||||
run_ssh docker pull "$IMAGE"
|
||||
|
||||
if [ "$DEPLOY_ENV" = "production" ]; then
|
||||
echo "$DEPLOY_ENV 服务使用蓝绿执行器,旧实例在候选槽健康前保持运行"
|
||||
run_ssh sh "$REMOTE_DIR/$REMOTE_BLUE_GREEN_NAME" \
|
||||
"$DEPLOY_ENV" "$IMAGE_REPOSITORY" "$IMAGE_TAG" "$SERVICE" \
|
||||
"$REMOTE_DIR/$REMOTE_ENV_NAME" "$REMOTE_DIR/$REMOTE_COMPOSE_NAME" "$PROJECT_NAME"
|
||||
remote_registry_logout
|
||||
trap - EXIT HUP INT TERM
|
||||
exit 0
|
||||
fi
|
||||
|
||||
run_ssh sh -s -- "$REMOTE_DIR" "$DEPLOY_ENV" "$IMAGE_REPOSITORY" "$IMAGE_TAG" "$SERVICE" "$PROJECT_NAME" "$REMOTE_ENV_NAME" "$REMOTE_COMPOSE_NAME" "$HEALTH_TIMEOUT_SECONDS" "$HEALTH_POLL_SECONDS" <<'REMOTE_DEPLOY'
|
||||
set -eu
|
||||
remote_dir="$1"
|
||||
deploy_env="$2"
|
||||
image_repository="$3"
|
||||
image_tag="$4"
|
||||
service="$5"
|
||||
project_name="$6"
|
||||
env_file="$7"
|
||||
compose_file="$8"
|
||||
health_timeout_seconds="$9"
|
||||
health_poll_seconds="${10}"
|
||||
cd "$remote_dir"
|
||||
export DEPLOY_ENV="$deploy_env"
|
||||
export IMAGE_REPOSITORY="$image_repository"
|
||||
export IMAGE_TAG="$image_tag"
|
||||
if [ "$deploy_env" = "development" ]; then
|
||||
export PUBLISHED_BIND_ADDRESS=127.0.0.1
|
||||
else
|
||||
export PUBLISHED_BIND_ADDRESS="${PUBLISHED_BIND_ADDRESS:-0.0.0.0}"
|
||||
fi
|
||||
|
||||
compose() {
|
||||
docker compose \
|
||||
--project-name "$project_name" \
|
||||
--env-file "$env_file" \
|
||||
-f "$compose_file" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
container_id() {
|
||||
compose ps --all --quiet "$service" | head -n 1
|
||||
}
|
||||
|
||||
container_image() {
|
||||
id="$(container_id)"
|
||||
if [ -n "$id" ]; then
|
||||
docker inspect --format '{{.Config.Image}}' "$id"
|
||||
fi
|
||||
}
|
||||
|
||||
health_state() {
|
||||
id="$(container_id)"
|
||||
if [ -z "$id" ]; then
|
||||
echo missing
|
||||
return
|
||||
fi
|
||||
docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}no-health:{{.State.Status}}{{end}}' "$id" 2>/dev/null || echo missing
|
||||
}
|
||||
|
||||
wait_for_health() {
|
||||
elapsed=0
|
||||
while [ "$elapsed" -lt "$health_timeout_seconds" ]; do
|
||||
state="$(health_state)"
|
||||
echo "容器健康状态: $state(已等待 ${elapsed}s)"
|
||||
case "$state" in
|
||||
healthy) return 0 ;;
|
||||
no-health:running)
|
||||
echo "容器未配置 HEALTHCHECK,以运行状态作为成功依据" >&2
|
||||
return 0
|
||||
;;
|
||||
unhealthy|no-health:exited|no-health:dead) return 1 ;;
|
||||
esac
|
||||
sleep "$health_poll_seconds"
|
||||
elapsed=$((elapsed + health_poll_seconds))
|
||||
done
|
||||
echo "等待容器健康超时: ${health_timeout_seconds}s" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
new_image="$image_repository/$service:$image_tag"
|
||||
previous_image="$(container_image || true)"
|
||||
if [ -n "$previous_image" ]; then
|
||||
echo "部署前运行镜像: $previous_image"
|
||||
else
|
||||
echo "首次部署,未发现可回滚的旧镜像"
|
||||
fi
|
||||
|
||||
new_started=true
|
||||
if ! compose up -d --no-deps --pull never "$service"; then
|
||||
echo "新镜像启动命令失败: $new_image" >&2
|
||||
new_started=false
|
||||
fi
|
||||
if [ "$new_started" = "true" ] && wait_for_health; then
|
||||
running_image="$(container_image || true)"
|
||||
if [ "$running_image" != "$new_image" ]; then
|
||||
echo "运行容器镜像与请求镜像不一致: expected=$new_image actual=${running_image:-missing}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "新镜像健康检查通过: $new_image"
|
||||
compose ps "$service"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "新镜像健康检查失败: $new_image" >&2
|
||||
failed_container_id="$(container_id || true)"
|
||||
if [ -n "$failed_container_id" ]; then
|
||||
echo "新镜像容器最近日志(最多 200 行):" >&2
|
||||
docker logs --tail 200 "$failed_container_id" 2>&1 || true
|
||||
else
|
||||
echo "新镜像容器不存在,无法读取启动日志" >&2
|
||||
fi
|
||||
expected_prefix="$image_repository/$service:"
|
||||
case "$previous_image" in
|
||||
"$expected_prefix"*) previous_tag="${previous_image#"$expected_prefix"}" ;;
|
||||
'')
|
||||
echo "首次部署没有可回滚镜像,部署失败" >&2
|
||||
compose stop "$service" >/dev/null 2>&1 || true
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "旧镜像不属于当前服务,拒绝自动回滚: $previous_image" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
if [ "$previous_image" = "$new_image" ]; then
|
||||
echo "旧镜像与新镜像相同,无法自动回滚: $new_image" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "开始自动回滚到: $previous_image" >&2
|
||||
if ! docker pull "$previous_image"; then
|
||||
echo "自动回滚失败,无法拉取旧镜像: $previous_image" >&2
|
||||
exit 1
|
||||
fi
|
||||
IMAGE_TAG="$previous_tag"
|
||||
export IMAGE_TAG
|
||||
if ! compose up -d --no-deps --pull never "$service"; then
|
||||
echo "自动回滚失败,旧镜像启动命令失败: $previous_image" >&2
|
||||
exit 1
|
||||
fi
|
||||
if wait_for_health; then
|
||||
echo "自动回滚成功: $previous_image" >&2
|
||||
compose ps "$service" || true
|
||||
else
|
||||
echo "自动回滚失败,旧镜像未恢复健康: $previous_image" >&2
|
||||
fi
|
||||
exit 1
|
||||
REMOTE_DEPLOY
|
||||
|
||||
remote_registry_logout
|
||||
trap - EXIT HUP INT TERM
|
||||
deploy_local
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
if [ "$#" -ne 4 ]; then
|
||||
echo "用法: $0 <镜像仓库> <镜像标签> <逗号分隔服务> <环境文件>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
IMAGE_REPOSITORY="$1"
|
||||
IMAGE_TAG="$2"
|
||||
SERVICE_CSV="$3"
|
||||
ENV_FILE="$4"
|
||||
|
||||
SSH_TARGET="${DEVELOPMENT_SSH_TARGET:-deploy@172.31.198.8}"
|
||||
SSH_KEY="${DEVELOPMENT_SSH_KEY:-/var/jenkins_home/.ssh/dev_deploy}"
|
||||
SSH_KNOWN_HOSTS="${DEVELOPMENT_SSH_KNOWN_HOSTS:-/var/jenkins_home/.ssh/known_hosts}"
|
||||
REMOTE_DIR="${DEVELOPMENT_DEPLOY_DIR:-/opt/firstleg/application}"
|
||||
COMPOSE_FILE="${DEPLOY_COMPOSE_FILE:-deploy/docker-compose.yml}"
|
||||
PROJECT_NAME="${DEPLOY_PROJECT_NAME:-firstleg-development}"
|
||||
|
||||
if [ ! -f "$SSH_KEY" ]; then
|
||||
echo "开发环境 SSH 私钥不存在: $SSH_KEY" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [ ! -f "$SSH_KNOWN_HOSTS" ]; then
|
||||
echo "SSH 主机指纹文件不存在: $SSH_KNOWN_HOSTS" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
echo "开发环境变量文件不存在: $ENV_FILE" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [ ! -f "$COMPOSE_FILE" ]; then
|
||||
echo "Compose 文件不存在: $COMPOSE_FILE" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
SERVICES=$(printf '%s' "$SERVICE_CSV" | tr ',' ' ')
|
||||
for service in $SERVICES; do
|
||||
case "$service" in
|
||||
*[!A-Za-z0-9_-]*|'')
|
||||
echo "非法服务名: $service" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "${DEVELOPMENT_DEPLOY_LOCAL:-false}" = "true" ]; then
|
||||
echo "开发镜像已在开发节点本机构建,直接部署: $SERVICE_CSV"
|
||||
install -d -m 750 "$REMOTE_DIR"
|
||||
|
||||
SUFFIX="${IMAGE_TAG}.$$.${SERVICE_CSV}"
|
||||
LOCAL_ENV_TMP="$REMOTE_DIR/.env.$SUFFIX.tmp"
|
||||
LOCAL_COMPOSE_TMP="$REMOTE_DIR/docker-compose.yml.$SUFFIX.tmp"
|
||||
install -m 600 "$ENV_FILE" "$LOCAL_ENV_TMP"
|
||||
install -m 644 "$COMPOSE_FILE" "$LOCAL_COMPOSE_TMP"
|
||||
mv -f "$LOCAL_ENV_TMP" "$REMOTE_DIR/.env"
|
||||
mv -f "$LOCAL_COMPOSE_TMP" "$REMOTE_DIR/docker-compose.yml"
|
||||
|
||||
for service in $SERVICES; do
|
||||
docker image inspect "$IMAGE_REPOSITORY/$service:$IMAGE_TAG" >/dev/null
|
||||
done
|
||||
|
||||
cd "$REMOTE_DIR"
|
||||
export DEPLOY_ENV=development
|
||||
export IMAGE_REPOSITORY IMAGE_TAG
|
||||
export PUBLISHED_BIND_ADDRESS=127.0.0.1
|
||||
|
||||
docker compose \
|
||||
--project-name "$PROJECT_NAME" \
|
||||
--env-file .env \
|
||||
-f docker-compose.yml \
|
||||
pull $SERVICES
|
||||
|
||||
docker compose \
|
||||
--project-name "$PROJECT_NAME" \
|
||||
--env-file .env \
|
||||
-f docker-compose.yml \
|
||||
up -d --remove-orphans --pull never $SERVICES
|
||||
|
||||
docker compose \
|
||||
--project-name "$PROJECT_NAME" \
|
||||
--env-file .env \
|
||||
-f docker-compose.yml \
|
||||
ps $SERVICES
|
||||
exit 0
|
||||
fi
|
||||
|
||||
run_ssh() {
|
||||
ssh -i "$SSH_KEY" \
|
||||
-o BatchMode=yes \
|
||||
-o StrictHostKeyChecking=yes \
|
||||
-o UserKnownHostsFile="$SSH_KNOWN_HOSTS" \
|
||||
-o ConnectTimeout=10 \
|
||||
"$SSH_TARGET" "$@"
|
||||
}
|
||||
|
||||
run_scp() {
|
||||
scp -i "$SSH_KEY" \
|
||||
-o BatchMode=yes \
|
||||
-o StrictHostKeyChecking=yes \
|
||||
-o UserKnownHostsFile="$SSH_KNOWN_HOSTS" \
|
||||
-o ConnectTimeout=10 \
|
||||
"$@"
|
||||
}
|
||||
|
||||
run_ssh install -d -m 750 "$REMOTE_DIR"
|
||||
|
||||
SUFFIX="${IMAGE_TAG}.$$.${SERVICE_CSV}"
|
||||
REMOTE_ENV_TMP="$REMOTE_DIR/.env.$SUFFIX.tmp"
|
||||
REMOTE_COMPOSE_TMP="$REMOTE_DIR/docker-compose.yml.$SUFFIX.tmp"
|
||||
|
||||
run_scp "$ENV_FILE" "$SSH_TARGET:$REMOTE_ENV_TMP"
|
||||
run_scp "$COMPOSE_FILE" "$SSH_TARGET:$REMOTE_COMPOSE_TMP"
|
||||
|
||||
run_ssh sh -s -- "$REMOTE_DIR" "$REMOTE_ENV_TMP" "$REMOTE_COMPOSE_TMP" <<'REMOTE_PREPARE'
|
||||
set -eu
|
||||
remote_dir="$1"
|
||||
env_tmp="$2"
|
||||
compose_tmp="$3"
|
||||
chmod 600 "$env_tmp"
|
||||
chmod 644 "$compose_tmp"
|
||||
mv -f "$env_tmp" "$remote_dir/.env"
|
||||
mv -f "$compose_tmp" "$remote_dir/docker-compose.yml"
|
||||
REMOTE_PREPARE
|
||||
|
||||
for service in $SERVICES; do
|
||||
image="$IMAGE_REPOSITORY/$service:$IMAGE_TAG"
|
||||
docker image inspect "$image" >/dev/null
|
||||
echo "传输开发镜像: $image"
|
||||
docker save "$image" | gzip -1 | run_ssh 'gzip -dc | docker load'
|
||||
done
|
||||
|
||||
run_ssh sh -s -- "$REMOTE_DIR" "$IMAGE_REPOSITORY" "$IMAGE_TAG" "$SERVICE_CSV" "$PROJECT_NAME" <<'REMOTE_DEPLOY'
|
||||
set -eu
|
||||
remote_dir="$1"
|
||||
image_repository="$2"
|
||||
image_tag="$3"
|
||||
service_csv="$4"
|
||||
project_name="$5"
|
||||
services=$(printf '%s' "$service_csv" | tr ',' ' ')
|
||||
|
||||
cd "$remote_dir"
|
||||
export DEPLOY_ENV=development
|
||||
export IMAGE_REPOSITORY="$image_repository"
|
||||
export IMAGE_TAG="$image_tag"
|
||||
export PUBLISHED_BIND_ADDRESS=127.0.0.1
|
||||
|
||||
docker compose \
|
||||
--project-name "$project_name" \
|
||||
--env-file .env \
|
||||
-f docker-compose.yml \
|
||||
up -d --remove-orphans --pull never $services
|
||||
|
||||
docker compose \
|
||||
--project-name "$project_name" \
|
||||
--env-file .env \
|
||||
-f docker-compose.yml \
|
||||
ps $services
|
||||
REMOTE_DEPLOY
|
||||
@@ -1,225 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
if [ "$#" -ne 5 ]; then
|
||||
echo "用法: $0 <环境> <镜像仓库> <镜像标签> <逗号分隔服务> <环境文件>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
DEPLOY_ENV="$1"
|
||||
IMAGE_REPOSITORY="$2"
|
||||
IMAGE_TAG="$3"
|
||||
SERVICE_CSV="$4"
|
||||
ENV_FILE="$5"
|
||||
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
echo "环境文件不存在: $ENV_FILE" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
SERVICES=$(printf '%s' "$SERVICE_CSV" | tr ',' ' ')
|
||||
|
||||
for service in $SERVICES; do
|
||||
case "$service" in
|
||||
*[!A-Za-z0-9_-]*|'')
|
||||
echo "非法服务名: $service" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
[ "$#" -eq 5 ] || { echo "Usage: $0 test repository tag service-csv env-file" >&2; exit 2; }
|
||||
[ "$1" = "test" ] || exit 2
|
||||
for service in $(printf '%s' "$4" | tr ',' ' '); do
|
||||
sh deploy/scripts/deploy-built-service.sh "$1" "$2" "$3" "$service" "$5"
|
||||
done
|
||||
|
||||
env_file_has_key() {
|
||||
grep -q "^$1=" "$ENV_FILE"
|
||||
}
|
||||
|
||||
env_file_value() {
|
||||
sed -n "s/^$1=//p" "$ENV_FILE" | tail -n 1 | tr -d '\r'
|
||||
}
|
||||
|
||||
DEPLOY_REGION=CN
|
||||
for service in $SERVICES; do
|
||||
case "$service" in
|
||||
gateway-overseas|lastleg-service|warehouse-overseas-service) DEPLOY_REGION=OVERSEAS ;;
|
||||
event-bridge-service)
|
||||
BRIDGE_REGION="${EVENT_BRIDGE_REGION_OVERRIDE:-}"
|
||||
if [ -z "$BRIDGE_REGION" ] && env_file_has_key REGION_CODE; then
|
||||
BRIDGE_REGION="$(env_file_value REGION_CODE)"
|
||||
fi
|
||||
if [ -z "$BRIDGE_REGION" ]; then
|
||||
echo "部署 event-bridge-service 时,环境文件必须显式包含 REGION_CODE=CN 或 REGION_CODE=OVERSEAS" >&2
|
||||
exit 2
|
||||
fi
|
||||
case "$BRIDGE_REGION" in
|
||||
CN) ;;
|
||||
OVERSEAS) DEPLOY_REGION=OVERSEAS ;;
|
||||
*) echo "部署 event-bridge-service 时,REGION_CODE 只能为 CN 或 OVERSEAS" >&2; exit 2 ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
done
|
||||
if [ "$DEPLOY_REGION" = "OVERSEAS" ]; then
|
||||
for service in $SERVICES; do
|
||||
case "$service" in
|
||||
gateway-overseas|lastleg-service|warehouse-overseas-service|event-bridge-service) ;;
|
||||
*) echo "一次部署不得混合国内与海外服务: $SERVICE_CSV" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
COMPOSE_FILE="deploy/docker-compose.overseas.yml"
|
||||
PROJECT_NAME="firstleg-${DEPLOY_ENV}-overseas"
|
||||
else
|
||||
COMPOSE_FILE="deploy/docker-compose.yml"
|
||||
PROJECT_NAME="firstleg-${DEPLOY_ENV}"
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_REGION" = "OVERSEAS" ]; then
|
||||
for key in NACOS_SERVER_ADDR OVERSEAS_NACOS_USERNAME OVERSEAS_NACOS_PASSWORD OVERSEAS_NACOS_NAMESPACE INTERNAL_SERVICE_TOKEN JWT_ISSUER OVERSEAS_JWT_AUDIENCE JWT_JWK_SET_URI; do
|
||||
if ! env_file_has_key "$key" || [ -z "$(env_file_value "$key")" ]; then
|
||||
echo "部署海外服务时,环境文件必须包含非空 $key" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
case " $SERVICES " in
|
||||
*" gateway-overseas "*)
|
||||
if ! env_file_has_key AUTH_SESSION_VALIDATION_URL || [ -z "$(env_file_value AUTH_SESSION_VALIDATION_URL)" ]; then
|
||||
echo "部署 gateway-overseas 时,环境文件必须包含非空 AUTH_SESSION_VALIDATION_URL" >&2
|
||||
exit 2
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
case " $SERVICES " in
|
||||
*" finance "*)
|
||||
for key in FINANCE_DB_URL FINANCE_DB_USERNAME FINANCE_DB_PASSWORD; do
|
||||
if ! env_file_has_key "$key" || [ -z "$(env_file_value "$key")" ]; then
|
||||
echo "部署 finance 服务时,环境文件必须包含非空 $key" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
case " $SERVICES " in
|
||||
*" firstleg "*)
|
||||
if ! env_file_has_key LASTLEG_EVENT_BRIDGE_URL || [ -z "$(env_file_value LASTLEG_EVENT_BRIDGE_URL)" ]; then
|
||||
echo "部署 firstleg 时,环境文件必须包含非空 LASTLEG_EVENT_BRIDGE_URL" >&2
|
||||
exit 2
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
case " $SERVICES " in
|
||||
*" lastleg-service "*)
|
||||
for key in LASTLEG_DB_URL LASTLEG_DB_USERNAME LASTLEG_DB_PASSWORD FIRSTLEG_EVENT_BRIDGE_URL DOMESTIC_WAREHOUSE_URL FILE_SERVICE_URL; do
|
||||
if ! env_file_has_key "$key" || [ -z "$(env_file_value "$key")" ]; then
|
||||
echo "部署 lastleg-service 时,环境文件必须包含非空 $key" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
case " $SERVICES " in
|
||||
*" warehouse-overseas-service "*)
|
||||
for key in WAREHOUSE_OVERSEAS_DB_URL WAREHOUSE_OVERSEAS_DB_USERNAME WAREHOUSE_OVERSEAS_DB_PASSWORD; do
|
||||
if ! env_file_has_key "$key" || [ -z "$(env_file_value "$key")" ]; then
|
||||
echo "部署 warehouse-overseas-service 时,环境文件必须包含非空 $key" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
case " $SERVICES " in
|
||||
*" event-bridge-service "*)
|
||||
if [ "$DEPLOY_REGION" = "OVERSEAS" ]; then
|
||||
EVENT_BRIDGE_KEYS="EVENT_BRIDGE_OVERSEAS_DB_URL EVENT_BRIDGE_OVERSEAS_DB_USERNAME EVENT_BRIDGE_OVERSEAS_DB_PASSWORD EVENT_CONSUMER_URL"
|
||||
else
|
||||
EVENT_BRIDGE_KEYS="EVENT_BRIDGE_DB_URL EVENT_BRIDGE_DB_USERNAME EVENT_BRIDGE_DB_PASSWORD FIRSTLEG_CONTAINER_EVENT_CONSUMER_URL"
|
||||
fi
|
||||
for key in $EVENT_BRIDGE_KEYS; do
|
||||
if ! env_file_has_key "$key" || [ -z "$(env_file_value "$key")" ]; then
|
||||
echo "部署 event-bridge-service 时,环境文件必须包含非空 $key" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
case " $SERVICES " in
|
||||
*" file "*)
|
||||
if ! env_file_has_key ROCKETMQ_NAME_SERVER || [ -z "$(env_file_value ROCKETMQ_NAME_SERVER)" ]; then
|
||||
echo "部署 file 服务时,环境文件必须包含非空 ROCKETMQ_NAME_SERVER" >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! env_file_has_key MQ_TOPIC_PREFIX; then
|
||||
echo "部署 file 服务时,环境文件必须显式包含 MQ_TOPIC_PREFIX:测试填 test-,生产填空字符串" >&2
|
||||
exit 2
|
||||
fi
|
||||
MQ_PREFIX=$(env_file_value MQ_TOPIC_PREFIX)
|
||||
case "$DEPLOY_ENV" in
|
||||
test)
|
||||
if [ "$MQ_PREFIX" != "test-" ]; then
|
||||
echo "测试环境部署 file 服务时 MQ_TOPIC_PREFIX 必须为 test-" >&2
|
||||
exit 2
|
||||
fi
|
||||
;;
|
||||
production)
|
||||
if [ -n "$MQ_PREFIX" ]; then
|
||||
echo "生产环境部署 file 服务时 MQ_TOPIC_PREFIX 必须显式为空字符串" >&2
|
||||
exit 2
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if [ -z "$MQ_PREFIX" ] || [ "$MQ_PREFIX" = "test-" ]; then
|
||||
echo "预发布环境部署 file 服务时 MQ_TOPIC_PREFIX 必须为非空且不能使用 test-" >&2
|
||||
exit 2
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
export DEPLOY_ENV IMAGE_REPOSITORY IMAGE_TAG
|
||||
|
||||
if [ "$DEPLOY_ENV" = "development" ]; then
|
||||
DEPLOY_COMPOSE_FILE="$COMPOSE_FILE" DEPLOY_PROJECT_NAME="$PROJECT_NAME" exec sh deploy/scripts/deploy-development-service.sh \
|
||||
"$IMAGE_REPOSITORY" "$IMAGE_TAG" "$SERVICE_CSV" "$ENV_FILE"
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_ENV" = "test" ] || [ "$DEPLOY_ENV" = "production" ]; then
|
||||
docker compose \
|
||||
--project-name "$PROJECT_NAME" \
|
||||
--env-file "$ENV_FILE" \
|
||||
-f "$COMPOSE_FILE" \
|
||||
pull $SERVICES
|
||||
for service in $SERVICES; do
|
||||
if [ "$DEPLOY_ENV" = test ]; then
|
||||
docker compose --project-name "$PROJECT_NAME" --env-file "$ENV_FILE" -f "$COMPOSE_FILE" \
|
||||
up -d --no-deps --pull never "$service"
|
||||
continue
|
||||
fi
|
||||
sh deploy/scripts/deploy-blue-green-service.sh \
|
||||
"$DEPLOY_ENV" "$IMAGE_REPOSITORY" "$IMAGE_TAG" "$service" "$ENV_FILE" "$COMPOSE_FILE" "$PROJECT_NAME"
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
docker compose \
|
||||
--project-name "$PROJECT_NAME" \
|
||||
--env-file "$ENV_FILE" \
|
||||
-f "$COMPOSE_FILE" \
|
||||
pull $SERVICES
|
||||
|
||||
docker compose \
|
||||
--project-name "$PROJECT_NAME" \
|
||||
--env-file "$ENV_FILE" \
|
||||
-f "$COMPOSE_FILE" \
|
||||
up -d --remove-orphans --pull never $SERVICES
|
||||
|
||||
docker compose \
|
||||
--project-name "$PROJECT_NAME" \
|
||||
--env-file "$ENV_FILE" \
|
||||
-f "$COMPOSE_FILE" \
|
||||
ps $SERVICES
|
||||
|
||||
@@ -1,47 +1,15 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
# 仅用于测试基础设施容器。所有 SQL 均使用 CREATE ... IF NOT EXISTS 或幂等扩展语句,
|
||||
# 不删除已有业务数据;仓库样例数据使用固定 ID 重复写入。
|
||||
MYSQL_CONTAINER="${TEST_MYSQL_CONTAINER:-firstleg-test-mysql}"
|
||||
|
||||
if ! docker inspect --type container "$MYSQL_CONTAINER" >/dev/null 2>&1; then
|
||||
echo "测试 MySQL 容器不存在:$MYSQL_CONTAINER" >&2
|
||||
echo "请先执行 docker compose -f docker-compose-infra.yml up -d" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(docker inspect -f '{{.State.Running}}' "$MYSQL_CONTAINER")" != "true" ]; then
|
||||
echo "测试 MySQL 容器未运行:$MYSQL_CONTAINER" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for sql_file in \
|
||||
auth/src/main/resources/db/auth.sql \
|
||||
iam/src/main/resources/db/iam.sql \
|
||||
auth/src/main/resources/db/phase1-admin-accounts.sql \
|
||||
iam/src/main/resources/db/phase1-admin-rbac.sql \
|
||||
iam/src/main/resources/db/phase1-onboarding.sql \
|
||||
iam/src/main/resources/db/phase1-customer-profile.sql \
|
||||
iam/src/main/resources/db/phase1-member-permissions.sql \
|
||||
iam/src/main/resources/db/rbac-role-management-v2.sql \
|
||||
iam/src/main/resources/db/phase1-platform-master-data-and-permission-management.sql \
|
||||
order/src/main/resources/db/order.sql \
|
||||
order/src/main/resources/db/phase1-order-extension.sql \
|
||||
order/src/main/resources/db/phase1-masterdata-extension.sql \
|
||||
warehouse/src/main/resources/db/warehouse.sql \
|
||||
warehouse/src/main/resources/db/warehouse-partner.sql \
|
||||
warehouse/src/main/resources/db/warehouse-express-company.sql \
|
||||
warehouse/src/main/resources/db/phase1-warehouse-schema-migration.sql \
|
||||
warehouse/src/main/resources/db/warehouse-seed.sql \
|
||||
file/src/main/resources/db/file.sql
|
||||
do
|
||||
if [ ! -f "$sql_file" ]; then
|
||||
echo "找不到初始化 SQL:$sql_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "初始化测试数据库:$sql_file"
|
||||
docker exec -i "$MYSQL_CONTAINER" sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" --default-character-set=utf8mb4' < "$sql_file"
|
||||
# Empty-schema baseline only. Never invoked by Compose/Jenkins automatically.
|
||||
[ "${ALLOW_TEST_SCHEMA_INITIALIZATION:-false}" = "true" ] || { echo 'Explicit TEST SQL authorization required' >&2; exit 2; }
|
||||
: "${TEST_MYSQL_CONTAINER:?Specify the authorized TEST MySQL container}"
|
||||
[ "$#" -eq 2 ] || { echo 'Usage: initialize-test-databases.sh <firstleg-baseline.sql> <overseas-baseline.sql>' >&2; exit 2; }
|
||||
for sql_file in "$1" "$2"; do
|
||||
[ -f "$sql_file" ] || { echo "Missing reviewed baseline: $sql_file" >&2; exit 2; }
|
||||
done
|
||||
[ "$1" != "$2" ] || { echo 'Two distinct reviewed baselines required' >&2; exit 2; }
|
||||
[ "$(docker inspect -f '{{.State.Running}}' "$TEST_MYSQL_CONTAINER")" = 'true' ] || { echo 'TEST MySQL is not running' >&2; exit 1; }
|
||||
for sql_file in "$1" "$2"; do
|
||||
echo "Applying reviewed TEST baseline: $sql_file"
|
||||
docker exec -i "$TEST_MYSQL_CONTAINER" sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" --default-character-set=utf8mb4' < "$sql_file"
|
||||
done
|
||||
|
||||
echo "测试数据库初始化完成。"
|
||||
|
||||
Reference in New Issue
Block a user