CaiWuXiTong/tests/scripts/release-guard.sh
WuYongJie 36af0e211a 修复生产内存保护:网页限额与重型导入隔离 (#430)
网页设置 2GiB 内存边界;生产重型导入统一进入 1CPU/1GiB 独立容器,保留只读文件、互斥、超时清理和发布保护。相关回归与资源隔离测试通过。
2026-10-21 14:30:06 +08:00

149 lines
6.5 KiB
Bash

#!/bin/sh
# Run in a disposable Linux container, never on a production host.
set -eu
REPO="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
fixture="$(mktemp -d /tmp/caiwu-release-guard.XXXXXX)"
cleanup() {
for legacy in activate-release.sh activate-incremental-release.sh; do
chattr -i "$fixture/legacy-entrypoints/$legacy" 2>/dev/null || true
done
rm -rf "$fixture"
}
trap cleanup EXIT
mkdir -p "$fixture/bin"
cat > "$fixture/bin/docker" <<'EOF'
#!/bin/sh
printf '%s\n' "$*" >> "$DOCKER_CALLS"
if [ "$1" = inspect ]; then
case "$*" in
*Config.Image*) printf 'caiwu_akaunting:old\n' ;;
*) printf '%s\n' "${FAKE_RUNNING_SHA:-}" ;;
esac
exit 0
fi
exit 91
EOF
chmod +x "$fixture/bin/docker"
PATH="$fixture/bin:$PATH"
export PATH
old=1111111111111111111111111111111111111111
new=2222222222222222222222222222222222222222
requested=3333333333333333333333333333333333333333
passed=0
pass() { passed=$((passed + 1)); printf 'PASS: %s\n' "$*"; }
fail() { printf 'FAIL: %s\n' "$*" >&2; exit 1; }
new_runtime() {
REMOTE_DIR="$fixture/$1"
mkdir -p "$REMOTE_DIR/runtime"
: > "$REMOTE_DIR/runtime/.env"
: > "$REMOTE_DIR/runtime/config-db.env"
printf 'CLICKHOUSE_PASSWORD=fixture\nBUSINESS_CLICKHOUSE_PASSWORD=fixture\n' > "$REMOTE_DIR/runtime/clickhouse.env"
printf 'BUSINESS_STORAGE_DRIVER=qiniu\nQINIU_ACCESS_KEY=fixture\nQINIU_SECRET_KEY=fixture\n' > "$REMOTE_DIR/runtime/qiniu-storage.env"
printf '%s\n' "$old" > "$REMOTE_DIR/.deployed-sha"
printf 'previous runtime configuration\n' > "$REMOTE_DIR/compose.jiangling.yml"
DOCKER_CALLS="$REMOTE_DIR/docker-calls"
: > "$DOCKER_CALLS"
FAKE_RUNNING_SHA="$old"
export DOCKER_CALLS FAKE_RUNNING_SHA
}
for activation in activate-release.sh activate-incremental-release.sh; do
for scenario in stale missing-protocol marker-image-mismatch unchanged; do
new_runtime "$activation-$scenario"
: > "$REMOTE_DIR/upload.tar.gz"
expected="$old"
case "$scenario" in
stale) printf '%s\n' "$new" > "$REMOTE_DIR/.deployed-sha"; FAKE_RUNNING_SHA="$new" ;;
marker-image-mismatch) FAKE_RUNNING_SHA="$new" ;;
esac
set +e
if [ "$scenario" = missing-protocol ]; then
sh "$REPO/deploy/$activation" "$REMOTE_DIR/upload.tar.gz" tag "$REMOTE_DIR" "$requested" 0 2 > "$REMOTE_DIR/output" 2>&1
else
sh "$REPO/deploy/$activation" "$REMOTE_DIR/upload.tar.gz" tag "$REMOTE_DIR" "$requested" 0 2 "$expected" fixture > "$REMOTE_DIR/output" 2>&1
fi
status=$?
set -e
if [ "$scenario" = unchanged ]; then
[ "$status" -eq 91 ] || { cat "$REMOTE_DIR/output"; fail 'accepted release did not reach image loading'; }
else
[ "$status" -ne 0 ] || fail "$activation accepted $scenario"
if grep -Ev '^inspect ' "$DOCKER_CALLS" | grep -q .; then
fail "$activation mutated Docker before rejecting $scenario"
fi
[ "$(cat "$REMOTE_DIR/compose.jiangling.yml")" = 'previous runtime configuration' ] || fail 'rejected release overwrote runtime configuration'
fi
if [ "$scenario" != missing-protocol ]; then
[ -s "$REMOTE_DIR/runtime/private/deployments.tsv" ] || fail 'deployment receipt missing'
if [ "$scenario" != unchanged ]; then
grep -q 'blocked' "$REMOTE_DIR/runtime/private/deployments.tsv" || fail 'rejection not recorded'
fi
fi
pass "$activation: $scenario"
done
done
. "$REPO/deploy/release-guard.sh"
new_runtime bootstrap
rm "$REMOTE_DIR/.deployed-sha"
FAKE_RUNNING_SHA=''
EXPECTED_PREVIOUS_SHA=none
DEPLOY_SHA="$new"
caiwu_verify_release_base
pass 'first deployment with no running application'
EXPECTED_PREVIOUS_SHA=''
if caiwu_verify_release_base 2>/dev/null; then fail 'empty baseline accepted'; fi
pass 'empty baseline rejected'
new_runtime ordered-uploaders
EXPECTED_PREVIOUS_SHA="$old"
DEPLOY_SHA="$new"
# Both uploaders checked the same old revision. The newer one activates first.
(
exec 9> "$REMOTE_DIR/.deploy.lock"
flock -x 9
caiwu_verify_release_base
printf '%s\n' "$new" > "$REMOTE_DIR/.deployed-sha"
)
FAKE_RUNNING_SHA="$new"
DEPLOY_SHA="$requested"
if (
exec 9> "$REMOTE_DIR/.deploy.lock"
flock -x 9
caiwu_verify_release_base
) 2>/dev/null; then fail 'delayed uploader overwrote the newer release'; fi
[ "$(cat "$REMOTE_DIR/.deployed-sha")" = "$new" ] || fail 'production marker regressed'
pass 'delayed uploader is rejected after a newer activation'
new_runtime staged-config
RELEASE_DIR="$fixture/staging"
RELEASE_APP_URL=''
mkdir -p "$RELEASE_DIR"
for file in compose.jiangling.yml activate-release.sh activate-incremental-release.sh release-guard.sh retired-activation.sh install-release-protection.sh config-db-init.sh run-finance-batch.sh run-finance-import.py finance-batch-imports.json clickhouse-resource-policy.xml update-runtime-url.sh configure-dingtalk-callback.sh host-nginx-8442-dingtalk.conf setup-incremental-registry.sh registry-config.yml; do
printf 'new staged runtime configuration\n' > "$RELEASE_DIR/$file"
done
printf '#!/bin/sh\nexit 0\n' > "$RELEASE_DIR/install-release-protection.sh"
rm "$RELEASE_DIR/registry-config.yml"
if caiwu_install_release_files 2>/dev/null; then fail 'partial upload accepted'; fi
[ "$(cat "$REMOTE_DIR/compose.jiangling.yml")" = 'previous runtime configuration' ] || fail 'partial upload replaced runtime files'
pass 'incomplete staging directory leaves runtime files untouched'
printf 'complete\n' > "$RELEASE_DIR/registry-config.yml"
caiwu_install_release_files
[ "$(cat "$REMOTE_DIR/compose.jiangling.yml")" = 'new staged runtime configuration' ] || fail 'staged configuration not installed'
pass 'complete isolated upload installs the intended runtime files'
new_runtime legacy-entrypoints
for legacy in activate-release.sh activate-incremental-release.sh; do
printf '#!/bin/sh\nexit 0\n' > "$REMOTE_DIR/$legacy"
done
sh "$REPO/deploy/install-release-protection.sh" "$REMOTE_DIR"
for legacy in activate-release.sh activate-incremental-release.sh; do
if cp "$REPO/deploy/$legacy" "$REMOTE_DIR/$legacy" 2>/dev/null; then fail 'old uploader replaced the protected entry point'; fi
if sh "$REMOTE_DIR/$legacy" old-arguments 2>/dev/null; then fail 'old activation path accepted a release'; fi
pass "$legacy: legacy uploader and activation both rejected"
done
sh "$REPO/deploy/install-release-protection.sh" "$REMOTE_DIR"
pass 'installing entry point protection again is idempotent'
printf 'PASS: %s release protection scenarios\n' "$passed"