| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- #!/usr/bin/env bash
- set -euo pipefail
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
- # shellcheck source=wp13-common.sh
- source "${SCRIPT_DIR}/wp13-common.sh"
- usage() {
- cat <<'EOF'
- Usage: preflight.sh [options]
- Validate an offline bundle or an existing Compose project before install/upgrade.
- Options:
- --bundle-root PATH Bundle or repository root.
- --compose-file PATH Compose file (default: <root>/deploy/docker/docker-compose.yml).
- --env-file PATH Optional Compose environment file.
- --environment-profile PATH
- Validated JSON profile; selects matching .env and project.
- --project-name NAME Compose project (default: dataops-test).
- --checksums-only Verify checksums and exit before Docker access.
- --skip-checksums Repository development mode only.
- --help Show this help.
- EOF
- }
- BUNDLE_ROOT="$(wp13_default_root)"
- COMPOSE_FILE=""
- ENV_FILE=""
- ENVIRONMENT_PROFILE=""
- PROJECT_NAME=""
- CHECKSUMS_ONLY=0
- SKIP_CHECKSUMS=0
- while [[ $# -gt 0 ]]; do
- case "$1" in
- --bundle-root) BUNDLE_ROOT="$2"; shift 2 ;;
- --compose-file) COMPOSE_FILE="$2"; shift 2 ;;
- --env-file) ENV_FILE="$2"; shift 2 ;;
- --environment-profile) ENVIRONMENT_PROFILE="$2"; shift 2 ;;
- --project-name) PROJECT_NAME="$2"; shift 2 ;;
- --checksums-only) CHECKSUMS_ONLY=1; shift ;;
- --skip-checksums) SKIP_CHECKSUMS=1; shift ;;
- --help|-h) usage; exit 0 ;;
- *) wp13_die "unknown argument: $1" ;;
- esac
- done
- BUNDLE_ROOT="$(cd "${BUNDLE_ROOT}" && pwd)"
- COMPOSE_FILE="${COMPOSE_FILE:-${BUNDLE_ROOT}/deploy/docker/docker-compose.yml}"
- if [[ "${SKIP_CHECKSUMS}" -eq 0 ]]; then
- wp13_verify_checksums "${BUNDLE_ROOT}"
- fi
- if [[ "${CHECKSUMS_ONLY}" -eq 1 ]]; then
- exit 0
- fi
- if [[ -n "${ENVIRONMENT_PROFILE}" ]]; then
- if [[ "${ENVIRONMENT_PROFILE}" != /* ]]; then
- ENVIRONMENT_PROFILE="${BUNDLE_ROOT}/${ENVIRONMENT_PROFILE}"
- fi
- [[ -r "${ENVIRONMENT_PROFILE}" ]] \
- || wp13_die "environment profile is not readable: ${ENVIRONMENT_PROFILE}"
- wp13_require_command python3
- python3 "${SCRIPT_DIR}/product_engineering.py" validate-environments \
- --profiles-dir "$(dirname "${ENVIRONMENT_PROFILE}")" >/dev/null
- PROFILE_PROJECT="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["project_name"])' "${ENVIRONMENT_PROFILE}")"
- PROFILE_ENV_FILE="${ENVIRONMENT_PROFILE%.json}.env"
- [[ -r "${PROFILE_ENV_FILE}" ]] \
- || wp13_die "matching environment file is not readable: ${PROFILE_ENV_FILE}"
- if [[ -n "${PROJECT_NAME}" && "${PROJECT_NAME}" != "${PROFILE_PROJECT}" ]]; then
- wp13_die "project name does not match environment profile: ${PROJECT_NAME} != ${PROFILE_PROJECT}"
- fi
- PROJECT_NAME="${PROFILE_PROJECT}"
- if [[ -n "${ENV_FILE}" && "$(cd "$(dirname "${ENV_FILE}")" && pwd)/$(basename "${ENV_FILE}")" != "$(cd "$(dirname "${PROFILE_ENV_FILE}")" && pwd)/$(basename "${PROFILE_ENV_FILE}")" ]]; then
- wp13_die "--env-file must match --environment-profile"
- fi
- ENV_FILE="${PROFILE_ENV_FILE}"
- wp13_log "environment profile validated: $(basename "${ENVIRONMENT_PROFILE}")"
- fi
- PROJECT_NAME="${PROJECT_NAME:-dataops-test}"
- wp13_require_command docker
- docker version >/dev/null
- docker compose version >/dev/null
- [[ -f "${COMPOSE_FILE}" ]] || wp13_die "compose file not found: ${COMPOSE_FILE}"
- if [[ -n "${ENV_FILE}" ]]; then
- [[ -r "${ENV_FILE}" ]] || wp13_die "environment file is not readable: ${ENV_FILE}"
- fi
- wp13_compose "${COMPOSE_FILE}" "${PROJECT_NAME}" "${ENV_FILE}" config --quiet
- wp13_log "Compose configuration resolved"
- while IFS= read -r image; do
- [[ -n "${image}" ]] || continue
- docker image inspect "${image}" >/dev/null 2>&1 \
- || wp13_die "required offline image is missing: ${image}"
- done < <(
- wp13_compose "${COMPOSE_FILE}" "${PROJECT_NAME}" "${ENV_FILE}" \
- config --images | LC_ALL=C sort -u
- )
- wp13_log "all default-profile images are available locally"
- expected_head="$(wp13_expected_migration_head "${BUNDLE_ROOT}")"
- if [[ -n "${expected_head}" ]]; then
- wp13_require_command python3
- backend_image="$(
- wp13_compose "${COMPOSE_FILE}" "${PROJECT_NAME}" "${ENV_FILE}" \
- config --format json \
- | python3 -c 'import json,sys; print(json.load(sys.stdin)["services"]["backend"]["image"])'
- )"
- image_head="$(
- docker run --rm --network none --read-only --entrypoint alembic "${backend_image}" \
- -c alembic.ini heads \
- | awk 'NF {print $1; exit}'
- )"
- [[ -n "${image_head}" && "${image_head}" == "${expected_head}" ]] \
- || wp13_die "image migration head does not match release manifest: image=${image_head:-unknown}, release=${expected_head}"
- wp13_log "target image migration head matches release: ${image_head}"
- fi
- if [[ -n "$(wp13_project_running_ids "${PROJECT_NAME}")" ]]; then
- current="$(
- wp13_compose "${COMPOSE_FILE}" "${PROJECT_NAME}" "${ENV_FILE}" \
- exec -T backend alembic -c alembic.ini current \
- | awk 'NF {print $1; exit}'
- )"
- head="$(
- wp13_compose "${COMPOSE_FILE}" "${PROJECT_NAME}" "${ENV_FILE}" \
- exec -T backend alembic -c alembic.ini heads \
- | awk 'NF {print $1; exit}'
- )"
- [[ -n "${current}" && "${current}" == "${head}" ]] \
- || wp13_die "database migration is not at head: current=${current:-unknown}, head=${head:-unknown}"
- wp13_log "running database is self-consistent at migration: ${current}; target=${expected_head:-unversioned}"
- else
- wp13_log "project is not running; migration will be checked after first start"
- fi
- available_kb="$(df -Pk "${BUNDLE_ROOT}" | awk 'NR==2 {print $4}')"
- [[ "${available_kb:-0}" -ge 10485760 ]] \
- || wp13_die "less than 10 GiB free space is available"
- wp13_log "preflight passed for project ${PROJECT_NAME}"
|