panda-rpki/scripts/docker/build_image.sh
yuyr 15c8de82ba
Some checks failed
ci / rust (push) Has been cancelled
ci / docker-runtime (push) Has been cancelled
freeze initial panda-rpki staging
2026-09-01 12:13:53 +08:00

196 lines
5.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
DOCKERFILE="${DOCKERFILE:-$REPO_ROOT/docker/validator-runtime.Dockerfile}"
TARGET_ARCH=""
IMAGE_TAG=""
OUT_DIR="${OUT_DIR:-$REPO_ROOT/docker-out}"
ALLOW_DIRTY=0
SAVE_IMAGE=1
LOAD_IMAGE=1
INSTALL_BINFMT=1
BUILDER_IMAGE="${BUILDER_IMAGE:-rust:1-bookworm}"
RUNTIME_IMAGE="${RUNTIME_IMAGE:-debian:bookworm-slim}"
usage() {
cat <<'USAGE'
Usage:
scripts/docker/build_image.sh --arch amd64|arm64 [options]
Options:
--arch <arch> Target architecture (required)
--image <tag> Image tag (default: panda-rpki-validator:<version>-<arch>)
--out-dir <path> Image archive/metadata directory (default: docker-out)
--dockerfile <path> Dockerfile path
--builder-image <tag> Builder base image (M4 staging tag; pin before release)
--runtime-image <tag> Runtime base image (M4 staging tag; pin before release)
--allow-dirty Permit staging build and mark tag/metadata dirty
--no-binfmt Do not install binfmt/qemu for a cross-architecture build
--no-save Do not write docker save archive
--no-load Build without loading the image into the local daemon
-h, --help Show this help
USAGE
}
normalize_arch() {
case "$1" in
amd64|x86_64) printf '%s\n' amd64 ;;
arm64|aarch64) printf '%s\n' arm64 ;;
*) return 1 ;;
esac
}
safe_name() {
printf '%s' "$1" | tr '/:' '--'
}
while [[ "$#" -gt 0 ]]; do
case "$1" in
--arch)
TARGET_ARCH="$(normalize_arch "$2")" || { echo "unsupported architecture: $2" >&2; exit 2; }
shift 2
;;
--image)
IMAGE_TAG="$2"
shift 2
;;
--out-dir)
OUT_DIR="$2"
shift 2
;;
--dockerfile)
DOCKERFILE="$2"
shift 2
;;
--builder-image)
BUILDER_IMAGE="$2"
shift 2
;;
--runtime-image)
RUNTIME_IMAGE="$2"
shift 2
;;
--allow-dirty)
ALLOW_DIRTY=1
shift
;;
--no-binfmt)
INSTALL_BINFMT=0
shift
;;
--no-save)
SAVE_IMAGE=0
shift
;;
--no-load)
LOAD_IMAGE=0
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
[[ -n "$TARGET_ARCH" ]] || { echo "--arch is required" >&2; usage >&2; exit 2; }
[[ -f "$DOCKERFILE" ]] || { echo "missing Dockerfile: $DOCKERFILE" >&2; exit 2; }
command -v docker >/dev/null 2>&1 || { echo "missing required command: docker" >&2; exit 2; }
if SOURCE_COMMIT_FULL="$(git -C "$REPO_ROOT" rev-parse --verify HEAD 2>/dev/null)"; then
:
else
SOURCE_COMMIT_FULL=unknown
fi
if SOURCE_COMMIT_SHORT="$(git -C "$REPO_ROOT" rev-parse --short=8 --verify HEAD 2>/dev/null)"; then
:
else
SOURCE_COMMIT_SHORT=unknown
fi
SOURCE_DIRTY=false
if [[ -n "$(git -C "$REPO_ROOT" status --short 2>/dev/null)" || "$SOURCE_COMMIT_FULL" == unknown ]]; then
SOURCE_DIRTY=true
fi
if [[ "$SOURCE_DIRTY" == true && "$ALLOW_DIRTY" != 1 ]]; then
echo "refusing dirty staging tree; pass --allow-dirty for M4 development" >&2
exit 2
fi
VERSION="$(awk -F'"' '$1 == "version = " { print $2; exit }' "$REPO_ROOT/crates/panda-rpki-validator/Cargo.toml")"
REVISION="$VERSION"
if [[ "$SOURCE_DIRTY" == true ]]; then
REVISION="${REVISION}-dirty"
fi
if [[ -z "$IMAGE_TAG" ]]; then
IMAGE_TAG="panda-rpki-validator:${REVISION}-${TARGET_ARCH}"
fi
mkdir -p "$OUT_DIR"
TARGET_PLATFORM="linux/$TARGET_ARCH"
HOST_ARCH=""
case "$(uname -m)" in
x86_64|amd64) HOST_ARCH=amd64 ;;
aarch64|arm64) HOST_ARCH=arm64 ;;
*) echo "unsupported host architecture: $(uname -m)" >&2; exit 2 ;;
esac
if [[ "$HOST_ARCH" != "$TARGET_ARCH" && "$INSTALL_BINFMT" == 1 ]]; then
echo "installing binfmt/qemu for $TARGET_ARCH"
docker run --rm --privileged tonistiigi/binfmt --install "$TARGET_ARCH"
fi
METADATA_PATH="$OUT_DIR/$(safe_name "$IMAGE_TAG").build.env"
IMAGE_TAR="$OUT_DIR/$(safe_name "$IMAGE_TAG").tar.gz"
build_args=(
--platform "$TARGET_PLATFORM"
--file "$DOCKERFILE"
--build-arg "BUILDER_IMAGE=$BUILDER_IMAGE"
--build-arg "RUNTIME_IMAGE=$RUNTIME_IMAGE"
--build-arg "SOURCE_COMMIT=$SOURCE_COMMIT_FULL"
--build-arg "SOURCE_DIRTY=$SOURCE_DIRTY"
--build-arg "BUILD_TIMESTAMP_UTC=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
--tag "$IMAGE_TAG"
)
if [[ "$LOAD_IMAGE" == 1 ]]; then
docker buildx build --load "${build_args[@]}" "$REPO_ROOT"
else
docker buildx build "${build_args[@]}" --output "type=oci,dest=$OUT_DIR/$(safe_name "$IMAGE_TAG").oci.tar" "$REPO_ROOT"
fi
if [[ "$SAVE_IMAGE" == 1 && "$LOAD_IMAGE" == 1 ]]; then
docker save "$IMAGE_TAG" | gzip -c > "$IMAGE_TAR"
fi
IMAGE_DIGEST=""
IMAGE_ID=""
if [[ "$LOAD_IMAGE" == 1 ]]; then
IMAGE_DIGEST="$(docker image inspect --format '{{index .RepoDigests 0}}' "$IMAGE_TAG" 2>/dev/null || true)"
IMAGE_ID="$(docker image inspect --format '{{.Id}}' "$IMAGE_TAG")"
fi
BUILD_TIMESTAMP_UTC="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
{
printf 'image=%s\n' "$IMAGE_TAG"
printf 'platform=%s\n' "$TARGET_PLATFORM"
printf 'arch=%s\n' "$TARGET_ARCH"
printf 'host_arch=%s\n' "$HOST_ARCH"
printf 'binfmt_installed=%s\n' "$([[ "$HOST_ARCH" != "$TARGET_ARCH" && "$INSTALL_BINFMT" == 1 ]] && printf true || printf false)"
printf 'source_commit=%s\n' "$SOURCE_COMMIT_FULL"
printf 'source_commit_short=%s\n' "$SOURCE_COMMIT_SHORT"
printf 'source_dirty=%s\n' "$SOURCE_DIRTY"
printf 'builder_image=%s\n' "$BUILDER_IMAGE"
printf 'runtime_image=%s\n' "$RUNTIME_IMAGE"
printf 'image_digest=%s\n' "$IMAGE_DIGEST"
printf 'image_id=%s\n' "$IMAGE_ID"
printf 'archive=%s\n' "$IMAGE_TAR"
printf 'archive_sha256=%s\n' "$(sha256sum "$IMAGE_TAR" 2>/dev/null | awk '{print $1}' || true)"
printf 'built_at_utc=%s\n' "$BUILD_TIMESTAMP_UTC"
} > "$METADATA_PATH"
echo "built image=$IMAGE_TAG platform=$TARGET_PLATFORM archive=$IMAGE_TAR"