preflight.sh 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  4. # shellcheck source=wp13-common.sh
  5. source "${SCRIPT_DIR}/wp13-common.sh"
  6. usage() {
  7. cat <<'EOF'
  8. Usage: preflight.sh [options]
  9. Validate an offline bundle or an existing Compose project before install/upgrade.
  10. Options:
  11. --bundle-root PATH Bundle or repository root.
  12. --compose-file PATH Compose file (default: <root>/deploy/docker/docker-compose.yml).
  13. --env-file PATH Optional Compose environment file.
  14. --environment-profile PATH
  15. Validated JSON profile; selects matching .env and project.
  16. --project-name NAME Compose project (default: dataops-test).
  17. --checksums-only Verify checksums and exit before Docker access.
  18. --skip-checksums Repository development mode only.
  19. --help Show this help.
  20. EOF
  21. }
  22. BUNDLE_ROOT="$(wp13_default_root)"
  23. COMPOSE_FILE=""
  24. ENV_FILE=""
  25. ENVIRONMENT_PROFILE=""
  26. PROJECT_NAME=""
  27. CHECKSUMS_ONLY=0
  28. SKIP_CHECKSUMS=0
  29. while [[ $# -gt 0 ]]; do
  30. case "$1" in
  31. --bundle-root) BUNDLE_ROOT="$2"; shift 2 ;;
  32. --compose-file) COMPOSE_FILE="$2"; shift 2 ;;
  33. --env-file) ENV_FILE="$2"; shift 2 ;;
  34. --environment-profile) ENVIRONMENT_PROFILE="$2"; shift 2 ;;
  35. --project-name) PROJECT_NAME="$2"; shift 2 ;;
  36. --checksums-only) CHECKSUMS_ONLY=1; shift ;;
  37. --skip-checksums) SKIP_CHECKSUMS=1; shift ;;
  38. --help|-h) usage; exit 0 ;;
  39. *) wp13_die "unknown argument: $1" ;;
  40. esac
  41. done
  42. BUNDLE_ROOT="$(cd "${BUNDLE_ROOT}" && pwd)"
  43. COMPOSE_FILE="${COMPOSE_FILE:-${BUNDLE_ROOT}/deploy/docker/docker-compose.yml}"
  44. if [[ "${SKIP_CHECKSUMS}" -eq 0 ]]; then
  45. wp13_verify_checksums "${BUNDLE_ROOT}"
  46. fi
  47. if [[ "${CHECKSUMS_ONLY}" -eq 1 ]]; then
  48. exit 0
  49. fi
  50. if [[ -n "${ENVIRONMENT_PROFILE}" ]]; then
  51. if [[ "${ENVIRONMENT_PROFILE}" != /* ]]; then
  52. ENVIRONMENT_PROFILE="${BUNDLE_ROOT}/${ENVIRONMENT_PROFILE}"
  53. fi
  54. [[ -r "${ENVIRONMENT_PROFILE}" ]] \
  55. || wp13_die "environment profile is not readable: ${ENVIRONMENT_PROFILE}"
  56. wp13_require_command python3
  57. python3 "${SCRIPT_DIR}/product_engineering.py" validate-environments \
  58. --profiles-dir "$(dirname "${ENVIRONMENT_PROFILE}")" >/dev/null
  59. PROFILE_PROJECT="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["project_name"])' "${ENVIRONMENT_PROFILE}")"
  60. PROFILE_ENV_FILE="${ENVIRONMENT_PROFILE%.json}.env"
  61. [[ -r "${PROFILE_ENV_FILE}" ]] \
  62. || wp13_die "matching environment file is not readable: ${PROFILE_ENV_FILE}"
  63. if [[ -n "${PROJECT_NAME}" && "${PROJECT_NAME}" != "${PROFILE_PROJECT}" ]]; then
  64. wp13_die "project name does not match environment profile: ${PROJECT_NAME} != ${PROFILE_PROJECT}"
  65. fi
  66. PROJECT_NAME="${PROFILE_PROJECT}"
  67. if [[ -n "${ENV_FILE}" && "$(cd "$(dirname "${ENV_FILE}")" && pwd)/$(basename "${ENV_FILE}")" != "$(cd "$(dirname "${PROFILE_ENV_FILE}")" && pwd)/$(basename "${PROFILE_ENV_FILE}")" ]]; then
  68. wp13_die "--env-file must match --environment-profile"
  69. fi
  70. ENV_FILE="${PROFILE_ENV_FILE}"
  71. wp13_log "environment profile validated: $(basename "${ENVIRONMENT_PROFILE}")"
  72. fi
  73. PROJECT_NAME="${PROJECT_NAME:-dataops-test}"
  74. wp13_require_command docker
  75. docker version >/dev/null
  76. docker compose version >/dev/null
  77. [[ -f "${COMPOSE_FILE}" ]] || wp13_die "compose file not found: ${COMPOSE_FILE}"
  78. if [[ -n "${ENV_FILE}" ]]; then
  79. [[ -r "${ENV_FILE}" ]] || wp13_die "environment file is not readable: ${ENV_FILE}"
  80. fi
  81. wp13_compose "${COMPOSE_FILE}" "${PROJECT_NAME}" "${ENV_FILE}" config --quiet
  82. wp13_log "Compose configuration resolved"
  83. while IFS= read -r image; do
  84. [[ -n "${image}" ]] || continue
  85. docker image inspect "${image}" >/dev/null 2>&1 \
  86. || wp13_die "required offline image is missing: ${image}"
  87. done < <(
  88. wp13_compose "${COMPOSE_FILE}" "${PROJECT_NAME}" "${ENV_FILE}" \
  89. config --images | LC_ALL=C sort -u
  90. )
  91. wp13_log "all default-profile images are available locally"
  92. expected_head="$(wp13_expected_migration_head "${BUNDLE_ROOT}")"
  93. if [[ -n "${expected_head}" ]]; then
  94. wp13_require_command python3
  95. backend_image="$(
  96. wp13_compose "${COMPOSE_FILE}" "${PROJECT_NAME}" "${ENV_FILE}" \
  97. config --format json \
  98. | python3 -c 'import json,sys; print(json.load(sys.stdin)["services"]["backend"]["image"])'
  99. )"
  100. image_head="$(
  101. docker run --rm --network none --read-only --entrypoint alembic "${backend_image}" \
  102. -c alembic.ini heads \
  103. | awk 'NF {print $1; exit}'
  104. )"
  105. [[ -n "${image_head}" && "${image_head}" == "${expected_head}" ]] \
  106. || wp13_die "image migration head does not match release manifest: image=${image_head:-unknown}, release=${expected_head}"
  107. wp13_log "target image migration head matches release: ${image_head}"
  108. fi
  109. if [[ -n "$(wp13_project_running_ids "${PROJECT_NAME}")" ]]; then
  110. current="$(
  111. wp13_compose "${COMPOSE_FILE}" "${PROJECT_NAME}" "${ENV_FILE}" \
  112. exec -T backend alembic -c alembic.ini current \
  113. | awk 'NF {print $1; exit}'
  114. )"
  115. head="$(
  116. wp13_compose "${COMPOSE_FILE}" "${PROJECT_NAME}" "${ENV_FILE}" \
  117. exec -T backend alembic -c alembic.ini heads \
  118. | awk 'NF {print $1; exit}'
  119. )"
  120. [[ -n "${current}" && "${current}" == "${head}" ]] \
  121. || wp13_die "database migration is not at head: current=${current:-unknown}, head=${head:-unknown}"
  122. wp13_log "running database is self-consistent at migration: ${current}; target=${expected_head:-unversioned}"
  123. else
  124. wp13_log "project is not running; migration will be checked after first start"
  125. fi
  126. available_kb="$(df -Pk "${BUNDLE_ROOT}" | awk 'NR==2 {print $4}')"
  127. [[ "${available_kb:-0}" -ge 10485760 ]] \
  128. || wp13_die "less than 10 GiB free space is available"
  129. wp13_log "preflight passed for project ${PROJECT_NAME}"