补强生产数据路径与备份来源护栏(07-26-internal-ops-recovery-baseline)
这个提交包含在:
@@ -41,6 +41,13 @@ mkdir -p "$SOURCE_STORAGE/nested" "$NONEMPTY_STORAGE" "$BACKUP_ROOT"
|
||||
printf 'synthetic attachment for restore drill\n' >"$SOURCE_STORAGE/nested/marker.txt"
|
||||
printf 'must not be overwritten\n' >"$NONEMPTY_STORAGE/existing.txt"
|
||||
|
||||
if BUILD_SHA=not-a-commit SOURCE_IMAGE_DIGEST= \
|
||||
BACKUP_DIR="$BACKUP_ROOT" BACKUP_RUN_ID=invalid-source \
|
||||
"$ROOT_DIR/scripts/backup-all.sh" >/dev/null 2>&1; then
|
||||
echo "[ERR] invalid backup source identity was accepted" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if env -u STORAGE_DIR_HOST -u STORAGE_DIR \
|
||||
OUT_FILE="$DRILL_DIR/implicit-storage.tar.gz" \
|
||||
"$ROOT_DIR/scripts/backup-storage.sh" >/dev/null 2>&1; then
|
||||
@@ -57,6 +64,28 @@ if env -u DB_CONTAINER DB_TOOL_MODE=docker DATABASE_URL=postgresql://invalid \
|
||||
fi
|
||||
|
||||
if command -v node >/dev/null 2>&1; then
|
||||
if deploy_error="$(DEPLOY_SERVER=unused POSTGRES_DATA_DIR=./relative \
|
||||
STORAGE_HOST_DIR=/srv/hyb-oa/storage DEPLOY_DATA_MODE=existing \
|
||||
"$ROOT_DIR/deploy/trialrun/deploy.sh" 2>&1)"; then
|
||||
echo "[ERR] formal deploy accepted a relative PostgreSQL data path" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$deploy_error" != *"POSTGRES_DATA_DIR"* ]]; then
|
||||
echo "[ERR] relative deploy path did not fail at the data-path guard" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if deploy_error="$(DEPLOY_SERVER=unused POSTGRES_DATA_DIR=/srv/hyb-oa/data \
|
||||
STORAGE_HOST_DIR=/srv/hyb-oa/data DEPLOY_DATA_MODE=existing \
|
||||
"$ROOT_DIR/deploy/trialrun/deploy.sh" 2>&1)"; then
|
||||
echo "[ERR] formal deploy accepted overlapping data paths" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$deploy_error" != *"必须相互独立"* ]]; then
|
||||
echo "[ERR] overlapping deploy paths did not fail at the isolation guard" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MUTATED_PORT_COMPOSE="$DRILL_DIR/docker-compose-db-port.yml"
|
||||
sed '/ db:/a\ ports:\n - "5432:5432"' \
|
||||
"$ROOT_DIR/deploy/trialrun/docker-compose.yml" >"$MUTATED_PORT_COMPOSE"
|
||||
@@ -103,6 +132,12 @@ HOST_PORT="$(docker port "$CONTAINER_NAME" 5432/tcp | awk -F: 'NR==1 {print $NF}
|
||||
DATABASE_URL="postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@127.0.0.1:$HOST_PORT/$POSTGRES_DB"
|
||||
export DATABASE_URL STORAGE_DIR_HOST="$SOURCE_STORAGE" BACKUP_DIR="$BACKUP_ROOT" BACKUP_RUN_ID="$DRILL_RUN_ID"
|
||||
export DB_TOOL_MODE="docker" DB_CONTAINER="$CONTAINER_NAME" POSTGRES_USER POSTGRES_PASSWORD POSTGRES_DB
|
||||
if [[ "${DRILL_SOURCE_ID_MODE:-both}" == "digest_only" ]]; then
|
||||
export BUILD_SHA=""
|
||||
else
|
||||
export BUILD_SHA="abcdef1234567890abcdef1234567890abcdef12"
|
||||
fi
|
||||
export SOURCE_IMAGE_DIGEST="sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
|
||||
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -qc 'CREATE TABLE restore_drill_marker (value text NOT NULL)'
|
||||
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -qc "INSERT INTO restore_drill_marker(value) VALUES ('synthetic-round-trip')"
|
||||
@@ -113,14 +148,17 @@ DB_BACKUP="$BATCH_DIR/database.dump"
|
||||
STORAGE_BACKUP="$BATCH_DIR/storage.tar.gz"
|
||||
|
||||
if command -v node >/dev/null 2>&1; then
|
||||
node - "$BATCH_DIR/manifest.json" "$DB_BACKUP" "$STORAGE_BACKUP" <<'NODE'
|
||||
node - "$BATCH_DIR/manifest.json" "$DB_BACKUP" "$STORAGE_BACKUP" "$BUILD_SHA" "$SOURCE_IMAGE_DIGEST" <<'NODE'
|
||||
const crypto = require("node:crypto");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const [manifestPath, dbPath, storagePath] = process.argv.slice(2);
|
||||
const [manifestPath, dbPath, storagePath, expectedBuildSha, expectedImageDigest] = process.argv.slice(2);
|
||||
const manifestText = fs.readFileSync(manifestPath, "utf8");
|
||||
const manifest = JSON.parse(manifestText);
|
||||
if (manifest.status !== "complete" || manifest.schemaVersion !== 1) throw new Error("manifest contract mismatch");
|
||||
if (manifest.buildSha !== (expectedBuildSha || null) || manifest.sourceImageDigest !== expectedImageDigest) {
|
||||
throw new Error("manifest source identity mismatch");
|
||||
}
|
||||
if (path.isAbsolute(manifest.artifacts.database.file) || path.isAbsolute(manifest.artifacts.storage.file)) {
|
||||
throw new Error("manifest contains absolute artifact path");
|
||||
}
|
||||
@@ -130,19 +168,21 @@ if (sha256(dbPath) !== manifest.artifacts.database.sha256) throw new Error("data
|
||||
if (sha256(storagePath) !== manifest.artifacts.storage.sha256) throw new Error("storage checksum mismatch");
|
||||
NODE
|
||||
else
|
||||
python3 - "$BATCH_DIR/manifest.json" "$DB_BACKUP" "$STORAGE_BACKUP" <<'PYTHON'
|
||||
python3 - "$BATCH_DIR/manifest.json" "$DB_BACKUP" "$STORAGE_BACKUP" "$BUILD_SHA" "$SOURCE_IMAGE_DIGEST" <<'PYTHON'
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
manifest_path, db_path, storage_path = sys.argv[1:]
|
||||
manifest_path, db_path, storage_path, expected_build_sha, expected_image_digest = sys.argv[1:]
|
||||
with open(manifest_path, encoding="utf-8") as manifest_file:
|
||||
manifest_text = manifest_file.read()
|
||||
manifest = json.loads(manifest_text)
|
||||
if manifest.get("status") != "complete" or manifest.get("schemaVersion") != 1:
|
||||
raise RuntimeError("manifest contract mismatch")
|
||||
if manifest.get("buildSha") != (expected_build_sha or None) or manifest.get("sourceImageDigest") != expected_image_digest:
|
||||
raise RuntimeError("manifest source identity mismatch")
|
||||
for artifact in (manifest["artifacts"]["database"], manifest["artifacts"]["storage"]):
|
||||
if os.path.isabs(artifact["file"]):
|
||||
raise RuntimeError("manifest contains absolute artifact path")
|
||||
|
||||
@@ -29,6 +29,7 @@ Usage: bash scripts/qa/run-release-gate.sh <all|static|smoke-local|full-local|pr
|
||||
PROJECT_WIDE_SKIP_BUSINESS_FLOW=1 跳过业务流 E2E
|
||||
PROJECT_WIDE_SKIP_VISUAL=1 跳过项目级视觉 E2E
|
||||
DEPLOY_SERVER / REMOTE_DIR / REMOTE_ENV_FILE / SYNC_ENV
|
||||
COMPOSE_PROJECT_NAME / POSTGRES_DATA_DIR / STORAGE_HOST_DIR / DEPLOY_DATA_MODE
|
||||
USAGE
|
||||
}
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户