175 行
5.5 KiB
Bash
可执行文件
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 ]]
|