#!/usr/bin/env bash # Package the shared layer and every function zip for HCP plan/apply. # Runs on the Terraform worker during plan (see artifacts.tf). set -euo pipefail ROOT="$(cd "$(dirname "$0")" && pwd)" BUILD="${ROOT}/build" REPO="$(cd "${ROOT}/.." && pwd)" FUNCS="${REPO}/functions" SHARED="${REPO}/src/shared" FUNCTIONS=( admin_authorizer aggregate_orders close_form slack_notifier submit_order sync_roster ) # Copy a regular file, refusing symlinks and any path that resolves outside the # expected tree. copy_file() { local src_path="$1" local dest="$2" local base="$3" if [[ -L "${src_path}" ]]; then echo "error: refusing symlink source: ${src_path}" >&2 exit 1 fi if [[ ! -f "${src_path}" ]]; then echo "error: missing regular file: ${src_path}" >&2 exit 1 fi local resolved resolved="$(cd "$(dirname "${src_path}")" && pwd)/$(basename "${src_path}")" case "${resolved}" in "${base}"/*) ;; *) echo "error: path escapes ${base}: ${resolved}" >&2 exit 1 ;; esac mkdir -p "$(dirname "${dest}")" # -P: never follow symlinks if the destination path is replaced mid-run. cp -P "${src_path}" "${dest}" } # Shipped by the python3.12 Lambda runtime. Keeping them in the layer adds tens # of megabytes to the base64-encoded plan payload for no runtime benefit. RUNTIME_PROVIDED=( boto3 botocore jmespath s3transfer urllib3 ) rm -rf "${BUILD}" mkdir -p "${BUILD}/layer/python" "${BUILD}/packages" # --------------------------------------------------------------------------- # Shared layer: pip dependencies + the `shared` package. # # Wheels must match the Lambda target (python3.12 / arm64), not the worker. # --only-binary=:all: makes a source-only package fail loudly here rather than # silently shipping a wheel built for the wrong platform. # --------------------------------------------------------------------------- python3 -m pip install \ --quiet \ --disable-pip-version-check \ -r "${SHARED}/requirements.txt" \ -t "${BUILD}/layer/python" \ --platform manylinux2014_aarch64 \ --implementation cp \ --python-version 3.12 \ --only-binary=:all: \ --upgrade # boto3 is pinned in src/shared/requirements.txt so local development and the # test suite resolve a known version, but it must not ship in the layer: the # runtime already provides it. Drop each package and its metadata after the # install rather than removing the pin. for pkg in "${RUNTIME_PROVIDED[@]}"; do rm -rf "${BUILD}/layer/python/${pkg}" find "${BUILD}/layer/python" -maxdepth 1 \ \( -name "${pkg}-*.dist-info" -o -name "${pkg}-*.egg-info" \) \ -prune -exec rm -rf {} + done cp -RP "${SHARED}/shared" "${BUILD}/layer/python/shared" # --------------------------------------------------------------------------- # Function packages: handler only. boto3 and fpdf2 come from the shared layer, # so functions/*/requirements.txt is not part of the deployment artifact. # --------------------------------------------------------------------------- for fn in "${FUNCTIONS[@]}"; do mkdir -p "${BUILD}/functions/${fn}" copy_file "${FUNCS}/${fn}/handler.py" "${BUILD}/functions/${fn}/handler.py" "${FUNCS}" done # Byte-compiled caches would make the zip hash unstable across workers. find "${BUILD}" -name '__pycache__' -type d -prune -exec rm -rf {} + find "${BUILD}" -name '*.pyc' -type f -delete