文件
hyb-oa/deploy/trialrun/check-existing-deployment.sh

175 行
5.5 KiB
Bash
可执行文件

#!/usr/bin/env bash
set -uo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
DEPLOY_SERVER="${DEPLOY_SERVER:-}"
DEPLOY_REFRESH_SERVER="${DEPLOY_REFRESH_SERVER:-}"
DEPLOY_CONTRACT_UNIT="${DEPLOY_CONTRACT_UNIT:-hyb-oa-automatic-backup-daily.service}"
# shellcheck source=deployment-contract-lib.sh
source "$ROOT_DIR/deploy/trialrun/deployment-contract-lib.sh"
CHECK_NAMES=(
deploy_ssh_identity
contract_six_keys
repository_root
repository_writable
env_readable
data_directories
runtime_unique
runtime_build
storage_mount
pgdata_mount
backup_build
root_ssh_identity
refresh_program
)
declare -A CHECK_RESULTS=()
declare -A CONTRACT=()
for check_name in "${CHECK_NAMES[@]}"; do
CHECK_RESULTS["$check_name"]="FAIL"
done
if deployment_contract_safe_ssh_target "$DEPLOY_SERVER" \
&& ssh "$DEPLOY_SERVER" true >/dev/null 2>&1; then
CHECK_RESULTS[deploy_ssh_identity]="PASS"
fi
if deployment_contract_load "$DEPLOY_SERVER" "$DEPLOY_CONTRACT_UNIT" CONTRACT; then
CHECK_RESULTS[contract_six_keys]="PASS"
remote_results="$(ssh "$DEPLOY_SERVER" \
"REMOTE_DIR='${CONTRACT[REMOTE_DIR]}' REMOTE_ENV_FILE='${CONTRACT[REMOTE_ENV_FILE]}' COMPOSE_FILE='${CONTRACT[COMPOSE_FILE]}' COMPOSE_PROJECT_NAME='${CONTRACT[COMPOSE_PROJECT_NAME]}' POSTGRES_DATA_DIR='${CONTRACT[POSTGRES_DATA_DIR]}' STORAGE_HOST_DIR='${CONTRACT[STORAGE_HOST_DIR]}' CONTRACT_UNIT='${DEPLOY_CONTRACT_UNIT}' bash -s" 2>/dev/null <<'REMOTE'
set -uo pipefail
emit() {
local name="$1"
shift
if ("$@") >/dev/null 2>&1; then
printf '%s=PASS\n' "$name"
else
printf '%s=FAIL\n' "$name"
fi
}
repository_root() {
[[ -d "$REMOTE_DIR" && -f "$REMOTE_DIR/server/package.json" \
&& -f "$REMOTE_DIR/$COMPOSE_FILE" ]]
}
repository_writable() { [[ -w "$REMOTE_DIR" ]]; }
env_readable() { [[ -f "$REMOTE_ENV_FILE" && ! -L "$REMOTE_ENV_FILE" && -r "$REMOTE_ENV_FILE" ]]; }
data_directories() { [[ -f "$POSTGRES_DATA_DIR/PG_VERSION" && -d "$STORAGE_HOST_DIR" ]]; }
container_ids() {
local service="$1"
docker ps \
--filter "label=com.docker.compose.project=$COMPOSE_PROJECT_NAME" \
--filter "label=com.docker.compose.service=$service" \
--format '{{.ID}}'
}
container_env() {
local container="$1"
local wanted="$2"
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' "$container" \
| sed -n "s/^${wanted}=//p" | tail -n 1
}
mount_source() {
local container="$1"
local destination="$2"
docker inspect -f '{{json .Mounts}}' "$container" | python3 -c '
import json, sys
destination = sys.argv[1]
mounts = json.load(sys.stdin)
matches = [item.get("Source", "") for item in mounts if item.get("Destination") == destination]
if len(matches) != 1 or not matches[0]:
raise SystemExit(1)
print(matches[0])
' "$destination"
}
mapfile -t servers < <(container_ids server 2>/dev/null || true)
mapfile -t dbs < <(container_ids db 2>/dev/null || true)
server_id="${servers[0]:-}"
db_id="${dbs[0]:-}"
runtime_unique_ok=0
if [[ "${#servers[@]}" -eq 1 && "${#dbs[@]}" -eq 1 \
&& -n "$server_id" && -n "$db_id" ]]; then
runtime_unique_ok=1
fi
runtime_unique() { [[ "$runtime_unique_ok" == 1 ]]; }
runtime_build() {
[[ "$runtime_unique_ok" == 1 ]] || return 1
active_build="$(container_env "$server_id" BUILD_SHA)"
[[ "$active_build" =~ ^[0-9a-fA-F]{40,64}$ ]]
}
storage_mount() {
[[ "$runtime_unique_ok" == 1 ]] || return 1
storage_destination="$(container_env "$server_id" STORAGE_DIR)"
[[ "$storage_destination" == /* ]] || return 1
[[ "$(mount_source "$server_id" "$storage_destination")" == "$STORAGE_HOST_DIR" ]]
}
pgdata_mount() {
[[ "$runtime_unique_ok" == 1 ]] || return 1
[[ "$(mount_source "$db_id" /var/lib/postgresql/data)" == "$POSTGRES_DATA_DIR" ]]
}
backup_build() {
runtime_build || return 1
unit_env="$(systemctl show "$CONTRACT_UNIT" -p Environment --value)"
unit_build=""
for entry in $unit_env; do
if [[ "${entry%%=*}" == "BUILD_SHA" ]]; then
unit_build="${entry#*=}"
fi
done
[[ "$unit_build" == "$active_build" ]]
}
emit repository_root repository_root
emit repository_writable repository_writable
emit env_readable env_readable
emit data_directories data_directories
emit runtime_unique runtime_unique
emit runtime_build runtime_build
emit storage_mount storage_mount
emit pgdata_mount pgdata_mount
emit backup_build backup_build
REMOTE
)"
while IFS='=' read -r result_name result_value; do
case "$result_name" in
repository_root|repository_writable|env_readable|data_directories|runtime_unique|runtime_build|storage_mount|pgdata_mount|backup_build)
[[ "$result_value" == "PASS" || "$result_value" == "FAIL" ]] \
&& CHECK_RESULTS["$result_name"]="$result_value"
;;
esac
done <<<"$remote_results"
fi
refresh_output=""
refresh_status=255
if deployment_contract_safe_ssh_target "$DEPLOY_REFRESH_SERVER"; then
refresh_output="$(ssh "$DEPLOY_REFRESH_SERVER" probe 2>/dev/null)"
refresh_status=$?
fi
if [[ "$refresh_status" -ne 255 ]]; then
CHECK_RESULTS[root_ssh_identity]="PASS"
fi
if [[ "$refresh_status" -eq 0 \
&& "$refresh_output" == "[deployment-contract-refresh][PASS] probe" ]]; then
CHECK_RESULTS[refresh_program]="PASS"
fi
failure_count=0
for check_name in "${CHECK_NAMES[@]}"; do
printf '[deployment-preflight] %s %s\n' "$check_name" "${CHECK_RESULTS[$check_name]}"
if [[ "${CHECK_RESULTS[$check_name]}" != "PASS" ]]; then
failure_count=$((failure_count + 1))
fi
done
printf '[deployment-preflight] summary PASS=%s FAIL=%s\n' \
"$((${#CHECK_NAMES[@]} - failure_count))" "$failure_count"
[[ "$failure_count" -eq 0 ]]