| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- #!/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: package_offline.sh [options]
- Build a checksum-verifiable DataOps Compose offline archive.
- Options:
- --repo-root PATH Repository root.
- --output-dir PATH Destination directory (default: <repo>/dist).
- --version VALUE Package version (default: source commit UTC timestamp).
- --source-date-epoch N Reproducible build timestamp (default: source commit time).
- --skip-images Contract-test mode; omit Docker image archive.
- --help Show this help.
- EOF
- }
- REPO_ROOT="$(wp13_default_root)"
- OUTPUT_DIR=""
- VERSION=""
- SOURCE_DATE_EPOCH=""
- SKIP_IMAGES=0
- while [[ $# -gt 0 ]]; do
- case "$1" in
- --repo-root) REPO_ROOT="$2"; shift 2 ;;
- --output-dir) OUTPUT_DIR="$2"; shift 2 ;;
- --version) VERSION="$2"; shift 2 ;;
- --source-date-epoch) SOURCE_DATE_EPOCH="$2"; shift 2 ;;
- --skip-images) SKIP_IMAGES=1; shift ;;
- --help|-h) usage; exit 0 ;;
- *) wp13_die "unknown argument: $1" ;;
- esac
- done
- REPO_ROOT="$(cd "${REPO_ROOT}" && pwd)"
- OUTPUT_DIR="${OUTPUT_DIR:-${REPO_ROOT}/dist}"
- mkdir -p "${OUTPUT_DIR}"
- OUTPUT_DIR="$(cd "${OUTPUT_DIR}" && pwd)"
- wp13_require_command python3
- SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(git -C "${REPO_ROOT}" show -s --format=%ct HEAD 2>/dev/null || printf 0)}"
- [[ "${SOURCE_DATE_EPOCH}" =~ ^[0-9]+$ ]] \
- || wp13_die "--source-date-epoch must be a non-negative integer"
- VERSION="${VERSION:-$(python3 -c 'import datetime,sys; print(datetime.datetime.fromtimestamp(int(sys.argv[1]), datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ"))' "${SOURCE_DATE_EPOCH}")}"
- for required in \
- deploy/docker/docker-compose.yml \
- deploy/docker/.env.example \
- database \
- migrations \
- deployment/environments \
- docs/architecture/OPENAPI.yaml \
- deployment/compose; do
- [[ -e "${REPO_ROOT}/${required}" ]] \
- || wp13_die "required release input is missing: ${required}"
- done
- if ! diff -qr \
- --exclude='__pycache__' \
- --exclude='*.pyc' \
- "${REPO_ROOT}/app" "${REPO_ROOT}/deployment/app" >/dev/null; then
- wp13_die "deployment/app is not synchronised with app"
- fi
- if ! diff -qr \
- --exclude='__pycache__' \
- --exclude='*.pyc' \
- "${REPO_ROOT}/migrations" "${REPO_ROOT}/deployment/migrations" >/dev/null; then
- wp13_die "deployment/migrations is not synchronised with migrations"
- fi
- PACKAGE_NAME="dataops-platform-offline-${VERSION}"
- TEMP_DIR="$(mktemp -d)"
- trap 'rm -rf "${TEMP_DIR}"' EXIT
- BUNDLE_ROOT="${TEMP_DIR}/${PACKAGE_NAME}"
- mkdir -p \
- "${BUNDLE_ROOT}/deploy" \
- "${BUNDLE_ROOT}/environments" \
- "${BUNDLE_ROOT}/operations" \
- "${BUNDLE_ROOT}/images" \
- "${BUNDLE_ROOT}/trace"
- cp -R "${REPO_ROOT}/deploy/docker" "${BUNDLE_ROOT}/deploy/docker"
- cp -R "${REPO_ROOT}/database" "${BUNDLE_ROOT}/database"
- cp -R "${REPO_ROOT}/migrations" "${BUNDLE_ROOT}/migrations"
- cp -R "${REPO_ROOT}/deployment/environments/." "${BUNDLE_ROOT}/environments/"
- cp -R "${REPO_ROOT}/deployment/compose/." "${BUNDLE_ROOT}/operations/"
- cp "${REPO_ROOT}/deployment/MANIFEST.md" "${BUNDLE_ROOT}/MANIFEST.md"
- cp "${REPO_ROOT}/docs/architecture/OPENAPI.yaml" "${BUNDLE_ROOT}/trace/OPENAPI.yaml"
- find "${BUNDLE_ROOT}" -type d -name __pycache__ -prune -exec rm -rf {} +
- find "${BUNDLE_ROOT}" -type f -name '*.pyc' -delete
- COMPOSE_FILE="${BUNDLE_ROOT}/deploy/docker/docker-compose.yml"
- IMAGE_LIST="${BUNDLE_ROOT}/images/images.txt"
- wp13_require_command docker
- docker compose -f "${REPO_ROOT}/deploy/docker/docker-compose.yml" \
- config --images | LC_ALL=C sort -u >"${IMAGE_LIST}"
- python3 "${REPO_ROOT}/deployment/compose/product_engineering.py" sbom \
- --repo-root "${REPO_ROOT}" \
- --output-dir "${BUNDLE_ROOT}/trace"
- python3 "${REPO_ROOT}/deployment/compose/product_engineering.py" manifest \
- --repo-root "${REPO_ROOT}" \
- --version "${VERSION}" \
- --source-date-epoch "${SOURCE_DATE_EPOCH}" \
- --output "${BUNDLE_ROOT}/release-manifest.json"
- CREATED_AT_UTC="$(python3 -c 'import datetime,sys; print(datetime.datetime.fromtimestamp(int(sys.argv[1]), datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"))' "${SOURCE_DATE_EPOCH}")"
- {
- printf 'version=%s\n' "${VERSION}"
- printf 'created_at_utc=%s\n' "${CREATED_AT_UTC}"
- printf 'source_date_epoch=%s\n' "${SOURCE_DATE_EPOCH}"
- printf 'source_commit=%s\n' "$(git -C "${REPO_ROOT}" rev-parse HEAD 2>/dev/null || printf unknown)"
- printf 'source_tree=%s\n' "$(git -C "${REPO_ROOT}" rev-parse 'HEAD^{tree}' 2>/dev/null || printf unknown)"
- printf 'compose_sha256=%s\n' "$(wp13_sha256_file "${COMPOSE_FILE}")"
- printf 'release_manifest_sha256=%s\n' "$(wp13_sha256_file "${BUNDLE_ROOT}/release-manifest.json")"
- printf 'openapi_sha256=%s\n' "$(wp13_sha256_file "${BUNDLE_ROOT}/trace/OPENAPI.yaml")"
- printf 'images_included=%s\n' "$([[ "${SKIP_IMAGES}" -eq 1 ]] && printf no || printf yes)"
- while IFS= read -r image; do
- [[ -n "${image}" ]] || continue
- image_id="$(docker image inspect -f '{{.Id}}' "${image}" 2>/dev/null || printf missing)"
- printf 'image=%s|%s\n' "${image}" "${image_id}"
- done <"${IMAGE_LIST}"
- } >"${BUNDLE_ROOT}/release-manifest.txt"
- if [[ "${SKIP_IMAGES}" -eq 0 ]]; then
- while IFS= read -r image; do
- [[ -n "${image}" ]] || continue
- docker image inspect "${image}" >/dev/null 2>&1 \
- || wp13_die "cannot package missing image: ${image}"
- done <"${IMAGE_LIST}"
- # shellcheck disable=SC2046
- docker save -o "${BUNDLE_ROOT}/images/dataops-images.tar" $(cat "${IMAGE_LIST}")
- fi
- wp13_write_checksums "${BUNDLE_ROOT}"
- ARCHIVE="${OUTPUT_DIR}/${PACKAGE_NAME}.tar.gz"
- python3 "${REPO_ROOT}/deployment/compose/reproducible_archive.py" \
- --source "${BUNDLE_ROOT}" \
- --output "${ARCHIVE}" \
- --source-date-epoch "${SOURCE_DATE_EPOCH}"
- wp13_log "offline package created: ${ARCHIVE}"
|