ops(web): bound immutable release history
This commit is contained in:
117
vehicle-data-platform/deploy/install-web-release.sh
Executable file
117
vehicle-data-platform/deploy/install-web-release.sh
Executable file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
RELEASE_ID=${1:?release id is required}
|
||||
ARCHIVE=${2:?web archive is required}
|
||||
ROOT=${PLATFORM_ROOT:-/opt/lingniu-vehicle-platform}
|
||||
BASE_URL=${PLATFORM_BASE_URL:-http://127.0.0.1:20300}
|
||||
SERVICE=${PLATFORM_SERVICE:-lingniu-vehicle-platform}
|
||||
COMPATIBILITY_GENERATIONS=${COMPATIBILITY_GENERATIONS:-3}
|
||||
RELEASE_HISTORY_LIMIT=${RELEASE_HISTORY_LIMIT:-20}
|
||||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
SYSTEMCTL_BIN=${SYSTEMCTL_BIN:-systemctl}
|
||||
CURL_BIN=${CURL_BIN:-curl}
|
||||
|
||||
case "$RELEASE_ID" in
|
||||
''|*[!A-Za-z0-9._-]*) printf 'invalid release id: %s\n' "$RELEASE_ID" >&2; exit 1 ;;
|
||||
esac
|
||||
test -f "$ARCHIVE" || { printf 'web archive is missing: %s\n' "$ARCHIVE" >&2; exit 1; }
|
||||
test -x "$SCRIPT_DIR/prepare-web-release-tree.sh" || { printf 'release tree helper is missing\n' >&2; exit 1; }
|
||||
test -f "$SCRIPT_DIR/prune-release-history.py" || { printf 'release pruning helper is missing\n' >&2; exit 1; }
|
||||
|
||||
old=$(readlink -f "$ROOT/current")
|
||||
next="$ROOT/releases/$RELEASE_ID"
|
||||
env_file="$ROOT/env/platform.env"
|
||||
old_release=$(sed -n 's/^PLATFORM_RELEASE=//p' "$env_file" | tail -1)
|
||||
switched=false
|
||||
|
||||
set_release_env() {
|
||||
python3 - "$env_file" "$1" <<'PY'
|
||||
from __future__ import print_function
|
||||
import io
|
||||
import sys
|
||||
|
||||
path, release = sys.argv[1], sys.argv[2]
|
||||
with io.open(path, 'r', encoding='utf-8') as handle:
|
||||
lines = handle.read().splitlines()
|
||||
updated = []
|
||||
found = False
|
||||
for line in lines:
|
||||
if line.startswith('PLATFORM_RELEASE='):
|
||||
updated.append('PLATFORM_RELEASE=' + release)
|
||||
found = True
|
||||
else:
|
||||
updated.append(line)
|
||||
if not found:
|
||||
updated.append('PLATFORM_RELEASE=' + release)
|
||||
with io.open(path, 'w', encoding='utf-8') as handle:
|
||||
handle.write('\n'.join(updated) + '\n')
|
||||
PY
|
||||
}
|
||||
|
||||
rollback() {
|
||||
local status=$?
|
||||
trap - ERR
|
||||
set +e
|
||||
if test "$switched" = true; then
|
||||
ln -s "$old" "$ROOT/current.rollback"
|
||||
python3 -c 'import os,sys; os.replace(sys.argv[1], sys.argv[2])' "$ROOT/current.rollback" "$ROOT/current"
|
||||
set_release_env "$old_release"
|
||||
"$SYSTEMCTL_BIN" restart "$SERVICE"
|
||||
fi
|
||||
printf 'web release install failed; current restored to %s\n' "$old" >&2
|
||||
exit "$status"
|
||||
}
|
||||
trap rollback ERR
|
||||
|
||||
test ! -e "$next" || { printf 'release already exists: %s\n' "$next" >&2; exit 1; }
|
||||
mkdir -p "$next/web"
|
||||
cp "$old/platform-api" "$next/platform-api"
|
||||
cp "$old/lingniu-vehicle-platform.service" "$next/lingniu-vehicle-platform.service"
|
||||
cp -a "$old/deploy" "$next/deploy"
|
||||
cp "$SCRIPT_DIR/prepare-web-release-tree.sh" "$next/deploy/prepare-web-release-tree.sh"
|
||||
cp "$SCRIPT_DIR/prune-release-history.py" "$next/deploy/prune-release-history.py"
|
||||
cp "$0" "$next/deploy/install-web-release.sh"
|
||||
chmod +x "$next/platform-api" "$next/deploy/"*.sh "$next/deploy/prune-release-history.py"
|
||||
|
||||
while IFS= read -r member; do
|
||||
case "$member" in
|
||||
/*|../*|*/../*|*/..) printf 'web archive contains unsafe path: %s\n' "$member" >&2; exit 1 ;;
|
||||
esac
|
||||
done < <(tar -tzf "$ARCHIVE")
|
||||
tar --no-same-owner -xzf "$ARCHIVE" -C "$next/web"
|
||||
find "$next/web" -type f -name '._*' -delete
|
||||
"$next/deploy/prepare-web-release-tree.sh" "$next/web" "$old/web" "$COMPATIBILITY_GENERATIONS"
|
||||
|
||||
ln -s "$next" "$ROOT/current.next"
|
||||
python3 -c 'import os,sys; os.replace(sys.argv[1], sys.argv[2])' "$ROOT/current.next" "$ROOT/current"
|
||||
switched=true
|
||||
set_release_env "$RELEASE_ID"
|
||||
"$SYSTEMCTL_BIN" restart "$SERVICE"
|
||||
|
||||
ready=false
|
||||
for _ in $(seq 1 15); do
|
||||
if "$SYSTEMCTL_BIN" is-active --quiet "$SERVICE" && "$CURL_BIN" -fsS "$BASE_URL/" >/dev/null; then ready=true; break; fi
|
||||
sleep 1
|
||||
done
|
||||
test "$ready" = true || { printf 'service did not become ready\n' >&2; exit 1; }
|
||||
verify_bin=${VERIFY_WEB_RELEASE_BIN:-$next/deploy/verify-web-release.sh}
|
||||
"$verify_bin" "$next/web" "$BASE_URL" "$next/web/.compatibility-assets"
|
||||
|
||||
if test -f "$ROOT/env/access-tokens.env"; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
. "$ROOT/env/access-tokens.env"
|
||||
set +a
|
||||
test -n "${VIEWER_TOKEN:-}" || { printf 'viewer token is unavailable for health verification\n' >&2; exit 1; }
|
||||
health=$("$CURL_BIN" -fsS -H "Authorization: Bearer $VIEWER_TOKEN" "$BASE_URL/api/ops/health")
|
||||
printf '%s' "$health" | python3 -c 'import json,sys; value=(((json.load(sys.stdin).get("data") or {}).get("runtime") or {}).get("platformRelease")); sys.exit(0 if value == sys.argv[1] else 1)' "$RELEASE_ID"
|
||||
fi
|
||||
|
||||
prune_bin=${PRUNE_RELEASE_BIN:-$next/deploy/prune-release-history.py}
|
||||
python3 "$prune_bin" "$ROOT" "$RELEASE_HISTORY_LIMIT" "$next" "$old"
|
||||
rm -f "$ARCHIVE"
|
||||
switched=false
|
||||
trap - ERR
|
||||
printf 'web_release_install=ok release=%s previous=%s\n' "$RELEASE_ID" "$(basename "$old")"
|
||||
Reference in New Issue
Block a user