Files
fips/testing/interop/build-images.sh
T

233 lines
8.5 KiB
Bash
Executable File

#!/bin/bash
# Mixed-version interop harness: build three Docker images, one per git ref.
#
# Given three git refs, this script builds the FIPS daemon from each ref and
# produces three distinct Docker images:
#
# fips-interop:a <- ref A ("version under test")
# fips-interop:b <- ref B ("parent")
# fips-interop:c <- ref C ("release")
#
# It deliberately does NOT reuse the fips-test:latest tag — that tag is owned
# by the flake-lab and the static suite, which bake a single binary set for
# all nodes. This harness needs a different binary set per node, so each ref
# gets its own tag.
#
# Mechanism:
# 1. For each ref, `git worktree add --detach` a temp checkout of this repo.
# 2. `cargo build --release` the four binaries in that worktree.
# 3. Copy fips/fipsctl/fipstop/fips-gateway into a per-ref build context.
# 4. `docker build` that context with the testing/docker Dockerfile, tagging
# fips-interop:<slot>.
# 5. Remove the temp worktree.
#
# The harness reuses the testing/docker/Dockerfile (and its entrypoint.sh,
# resolv.conf), so the runtime environment is identical to the static and
# rekey suites — only the daemon binaries differ between the three images.
#
# Idempotent and safe to re-run: each invocation rebuilds all three images
# from scratch and cleans up its worktrees, including stale worktrees from a
# previous interrupted run.
#
# Usage:
# ./build-images.sh <ref-a> <ref-b> <ref-c>
#
# Example (the motivating run):
# ./build-images.sh fix/fsp-rekey-overlapping-epoch 79975d72 v0.3.0
#
# Environment:
# FIPS_INTEROP_KEEP_WORKTREES=1 Keep temp worktrees after build (debug).
# CARGO_BUILD_JOBS=N Passed through to cargo if set.
# FIPS_INTEROP_RUNS_DIR=DIR Root for harness scratch dirs (.build/,
# .stress-runs/, generated-configs/). When
# unset, falls back to in-tree paths under
# testing/interop/ and prints a warning to
# stderr; set it to a path outside the
# source tree to keep generated artefacts
# out of the checkout.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
DOCKER_CTX_SRC="$REPO_ROOT/testing/docker"
# ── Scratch-dir root ─────────────────────────────────────────────────
#
# FIPS_INTEROP_RUNS_DIR controls where the harness writes its scratch
# directories (.build/, .stress-runs/, generated-configs/). When unset
# we fall back to in-tree paths under testing/interop/ and warn the
# operator, so the warning fires exactly once per invocation. When a
# parent script has already warned it exports _FIPS_INTEROP_WARNED=1
# to suppress duplicate warnings in child scripts.
if [[ -n "${FIPS_INTEROP_RUNS_DIR:-}" ]]; then
RUNS_BASE="$FIPS_INTEROP_RUNS_DIR"
mkdir -p "$RUNS_BASE"
else
RUNS_BASE="$SCRIPT_DIR"
if [[ -z "${_FIPS_INTEROP_WARNED:-}" ]]; then
echo >&2 "WARNING: FIPS_INTEROP_RUNS_DIR not set; harness output will be written under the source tree at $RUNS_BASE. Set FIPS_INTEROP_RUNS_DIR to a path outside the source tree to avoid this."
export _FIPS_INTEROP_WARNED=1
fi
fi
# Per-ref tag slots. Order matters: slot[i] <- ref[i].
SLOTS=(a b c)
# ── Args ─────────────────────────────────────────────────────────────
if [ "$#" -ne 3 ]; then
echo "Usage: $0 <ref-a> <ref-b> <ref-c>" >&2
echo "" >&2
echo " ref-a version under test -> fips-interop:a" >&2
echo " ref-b parent / comparison -> fips-interop:b" >&2
echo " ref-c release baseline -> fips-interop:c" >&2
exit 1
fi
REFS=("$1" "$2" "$3")
# ── Preflight ────────────────────────────────────────────────────────
if ! docker info >/dev/null 2>&1; then
echo "ERROR: Docker daemon is not reachable" >&2
exit 2
fi
if ! command -v cargo >/dev/null 2>&1; then
echo "ERROR: cargo not found on PATH" >&2
exit 2
fi
for ref in "${REFS[@]}"; do
if ! git -C "$REPO_ROOT" rev-parse --verify --quiet "${ref}^{commit}" >/dev/null; then
echo "ERROR: git ref '$ref' does not resolve to a commit" >&2
exit 2
fi
done
# ── Worktree + build-context scratch space ───────────────────────────
WORK_BASE="$RUNS_BASE/.build"
mkdir -p "$WORK_BASE"
# Track worktree paths for cleanup.
CREATED_WORKTREES=()
cleanup() {
if [ "${FIPS_INTEROP_KEEP_WORKTREES:-}" = "1" ]; then
echo ""
echo "FIPS_INTEROP_KEEP_WORKTREES=1 — leaving worktrees in place:"
for wt in "${CREATED_WORKTREES[@]:-}"; do
[ -n "$wt" ] && echo " $wt"
done
return
fi
for wt in "${CREATED_WORKTREES[@]:-}"; do
[ -n "$wt" ] || continue
if [ -d "$wt" ]; then
git -C "$REPO_ROOT" worktree remove --force "$wt" 2>/dev/null \
|| rm -rf "$wt"
fi
done
git -C "$REPO_ROOT" worktree prune 2>/dev/null || true
}
trap cleanup EXIT
# Prune any stale interop worktrees from a prior interrupted run before we
# start, so re-running the script is clean.
git -C "$REPO_ROOT" worktree prune 2>/dev/null || true
# ── Per-ref build ────────────────────────────────────────────────────
build_one() {
local slot="$1"
local ref="$2"
local sha
sha="$(git -C "$REPO_ROOT" rev-parse --short "$ref")"
echo ""
echo "=== Building slot '$slot' ref='$ref' sha=$sha ==="
local wt="$WORK_BASE/worktree-$slot"
local ctx="$WORK_BASE/ctx-$slot"
# Fresh worktree per build (remove a stale one first).
if [ -d "$wt" ]; then
git -C "$REPO_ROOT" worktree remove --force "$wt" 2>/dev/null \
|| rm -rf "$wt"
fi
git -C "$REPO_ROOT" worktree add --detach "$wt" "$ref"
CREATED_WORKTREES+=("$wt")
# Build the four binaries from this ref.
local cargo_jobs_arg=()
if [ -n "${CARGO_BUILD_JOBS:-}" ]; then
cargo_jobs_arg=(--jobs "$CARGO_BUILD_JOBS")
fi
(
cd "$wt"
cargo build --release "${cargo_jobs_arg[@]}" \
--bin fips --bin fipsctl --bin fipstop --bin fips-gateway
)
# Assemble a build context: the testing/docker Dockerfile + support
# files from the MAIN checkout, with the per-ref binaries layered in.
rm -rf "$ctx"
mkdir -p "$ctx"
cp "$DOCKER_CTX_SRC/Dockerfile" "$ctx/Dockerfile"
cp "$DOCKER_CTX_SRC/entrypoint.sh" "$ctx/entrypoint.sh"
cp "$DOCKER_CTX_SRC/resolv.conf" "$ctx/resolv.conf"
for bin in fips fipsctl fipstop fips-gateway; do
cp "$wt/target/release/$bin" "$ctx/$bin"
chmod +x "$ctx/$bin"
done
docker build \
--label "fips.interop.slot=$slot" \
--label "fips.interop.ref=$ref" \
--label "fips.interop.sha=$sha" \
-t "fips-interop:$slot" \
"$ctx"
# The worktree is large (target/ dir); remove it now rather than at
# exit so peak disk use stays at one worktree, not three.
if [ "${FIPS_INTEROP_KEEP_WORKTREES:-}" != "1" ]; then
git -C "$REPO_ROOT" worktree remove --force "$wt" 2>/dev/null \
|| rm -rf "$wt"
rm -rf "$ctx"
fi
echo "=== Built fips-interop:$slot ($ref @ $sha) ==="
}
# Record the ref->sha mapping so the driver and README can report what
# actually ran. Written before the builds so a partial failure still
# leaves a breadcrumb.
MANIFEST="$WORK_BASE/refs.env"
{
echo "# Generated by build-images.sh on $(date -u +%Y-%m-%dT%H:%M:%SZ)"
for i in 0 1 2; do
slot="${SLOTS[$i]}"
ref="${REFS[$i]}"
sha="$(git -C "$REPO_ROOT" rev-parse --short "$ref")"
upper="$(echo "$slot" | tr '[:lower:]' '[:upper:]')"
echo "INTEROP_REF_${upper}=$ref"
echo "INTEROP_SHA_${upper}=$sha"
done
} > "$MANIFEST"
for i in 0 1 2; do
build_one "${SLOTS[$i]}" "${REFS[$i]}"
done
echo ""
echo "=== All three interop images built ==="
docker image ls --filter 'reference=fips-interop' \
--format 'table {{.Repository}}:{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}'
echo ""
echo "Ref manifest: $MANIFEST"
cat "$MANIFEST"
echo ""
echo "Next: bash testing/interop/interop-test.sh"