package_offline.sh 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  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: package_offline.sh [options]
  9. Build a checksum-verifiable DataOps Compose offline archive.
  10. Options:
  11. --repo-root PATH Repository root.
  12. --output-dir PATH Destination directory (default: <repo>/dist).
  13. --version VALUE Package version (default: UTC timestamp).
  14. --skip-images Contract-test mode; omit Docker image archive.
  15. --help Show this help.
  16. EOF
  17. }
  18. REPO_ROOT="$(wp13_default_root)"
  19. OUTPUT_DIR=""
  20. VERSION="$(date -u '+%Y%m%dT%H%M%SZ')"
  21. SKIP_IMAGES=0
  22. while [[ $# -gt 0 ]]; do
  23. case "$1" in
  24. --repo-root) REPO_ROOT="$2"; shift 2 ;;
  25. --output-dir) OUTPUT_DIR="$2"; shift 2 ;;
  26. --version) VERSION="$2"; shift 2 ;;
  27. --skip-images) SKIP_IMAGES=1; shift ;;
  28. --help|-h) usage; exit 0 ;;
  29. *) wp13_die "unknown argument: $1" ;;
  30. esac
  31. done
  32. REPO_ROOT="$(cd "${REPO_ROOT}" && pwd)"
  33. OUTPUT_DIR="${OUTPUT_DIR:-${REPO_ROOT}/dist}"
  34. mkdir -p "${OUTPUT_DIR}"
  35. OUTPUT_DIR="$(cd "${OUTPUT_DIR}" && pwd)"
  36. for required in \
  37. deploy/docker/docker-compose.yml \
  38. deploy/docker/.env.example \
  39. database \
  40. migrations \
  41. deployment/compose; do
  42. [[ -e "${REPO_ROOT}/${required}" ]] \
  43. || wp13_die "required release input is missing: ${required}"
  44. done
  45. if ! diff -qr \
  46. --exclude='__pycache__' \
  47. --exclude='*.pyc' \
  48. "${REPO_ROOT}/app" "${REPO_ROOT}/deployment/app" >/dev/null; then
  49. wp13_die "deployment/app is not synchronised with app"
  50. fi
  51. if ! diff -qr \
  52. --exclude='__pycache__' \
  53. --exclude='*.pyc' \
  54. "${REPO_ROOT}/migrations" "${REPO_ROOT}/deployment/migrations" >/dev/null; then
  55. wp13_die "deployment/migrations is not synchronised with migrations"
  56. fi
  57. PACKAGE_NAME="dataops-platform-offline-${VERSION}"
  58. TEMP_DIR="$(mktemp -d)"
  59. trap 'rm -rf "${TEMP_DIR}"' EXIT
  60. BUNDLE_ROOT="${TEMP_DIR}/${PACKAGE_NAME}"
  61. mkdir -p \
  62. "${BUNDLE_ROOT}/deploy" \
  63. "${BUNDLE_ROOT}/operations" \
  64. "${BUNDLE_ROOT}/images"
  65. cp -R "${REPO_ROOT}/deploy/docker" "${BUNDLE_ROOT}/deploy/docker"
  66. cp -R "${REPO_ROOT}/database" "${BUNDLE_ROOT}/database"
  67. cp -R "${REPO_ROOT}/migrations" "${BUNDLE_ROOT}/migrations"
  68. cp -R "${REPO_ROOT}/deployment/compose/." "${BUNDLE_ROOT}/operations/"
  69. cp "${REPO_ROOT}/deployment/MANIFEST.md" "${BUNDLE_ROOT}/MANIFEST.md"
  70. find "${BUNDLE_ROOT}" -type d -name __pycache__ -prune -exec rm -rf {} +
  71. find "${BUNDLE_ROOT}" -type f -name '*.pyc' -delete
  72. COMPOSE_FILE="${BUNDLE_ROOT}/deploy/docker/docker-compose.yml"
  73. IMAGE_LIST="${BUNDLE_ROOT}/images/images.txt"
  74. wp13_require_command docker
  75. docker compose -f "${REPO_ROOT}/deploy/docker/docker-compose.yml" \
  76. config --images | LC_ALL=C sort -u >"${IMAGE_LIST}"
  77. {
  78. printf 'version=%s\n' "${VERSION}"
  79. printf 'created_at_utc=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
  80. printf 'source_commit=%s\n' "$(git -C "${REPO_ROOT}" rev-parse HEAD 2>/dev/null || printf unknown)"
  81. if [[ -n "$(git -C "${REPO_ROOT}" status --porcelain --untracked-files=no 2>/dev/null)" ]]; then
  82. printf 'source_worktree=dirty\n'
  83. else
  84. printf 'source_worktree=clean\n'
  85. fi
  86. printf 'compose_sha256=%s\n' "$(wp13_sha256_file "${COMPOSE_FILE}")"
  87. printf 'images_included=%s\n' "$([[ "${SKIP_IMAGES}" -eq 1 ]] && printf no || printf yes)"
  88. while IFS= read -r image; do
  89. [[ -n "${image}" ]] || continue
  90. image_id="$(docker image inspect -f '{{.Id}}' "${image}" 2>/dev/null || printf missing)"
  91. printf 'image=%s|%s\n' "${image}" "${image_id}"
  92. done <"${IMAGE_LIST}"
  93. } >"${BUNDLE_ROOT}/release-manifest.txt"
  94. if [[ "${SKIP_IMAGES}" -eq 0 ]]; then
  95. while IFS= read -r image; do
  96. [[ -n "${image}" ]] || continue
  97. docker image inspect "${image}" >/dev/null 2>&1 \
  98. || wp13_die "cannot package missing image: ${image}"
  99. done <"${IMAGE_LIST}"
  100. # shellcheck disable=SC2046
  101. docker save -o "${BUNDLE_ROOT}/images/dataops-images.tar" $(cat "${IMAGE_LIST}")
  102. fi
  103. wp13_write_checksums "${BUNDLE_ROOT}"
  104. ARCHIVE="${OUTPUT_DIR}/${PACKAGE_NAME}.tar.gz"
  105. tar -czf "${ARCHIVE}" -C "${TEMP_DIR}" "${PACKAGE_NAME}"
  106. wp13_log "offline package created: ${ARCHIVE}"