#!/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"