From 2cab88939c7066852861695b2b2156c5a015aea4 Mon Sep 17 00:00:00 2001 From: Anastassios Nanos Date: Sat, 26 Sep 2026 01:32:53 +0300 Subject: [PATCH] fix(bundle): Pin urunc and urunit to a commit build-bundle.sh named urunc and urunit by branch only. It cloned the tip of feat/unchanged_containers and of urunit_agent, and recorded whatever commit that was in pins.env after the fact. A push to either branch changed the next bundle with no change here. Two builds of one bundle version could carry different code, and no review ever saw the move. Each is now pinned to one commit, URUNC_REF_DEFAULT and URUNIT_REF_DEFAULT. The build fetches that commit by its SHA, since a --depth 1 --branch clone stops containing a pinned commit as soon as the branch moves on. It then fails if HEAD is anything else. pins.env records the commits in URUNC_REF and URUNIT_REF as before, and adds URUNC_PINNED and URUNIT_PINNED. The urunc pin moves to 0818ff1 on feat/unchanged_containers-exec-fixes. That is feat/unchanged_containers (b2c3cb1, which rc6 to rc8 built) plus the two urunit-agent exec fixes: urunc-dev/urunc#1059, which relays a guest exec's output before reporting its exit, and urunc-dev/urunc#1060, which accepts agent connections with close-on-exec. The agent in the initrd is built from the same checkout. The urunit pin is 71bfdee, the tip of urunit_agent and what rc6 to rc8 shipped. URUNC_REF and URUNIT_REF use ${VAR-default}. Unset takes the pin, a SHA builds that commit, and an empty value builds the tip of *_BRANCH, with a warning in the build log and *_PINNED=false in pins.env. A branch set without a ref is refused, and so is a ref that is not a full SHA. The stock variant keeps its paths: with --urunc-version it takes the release, and it builds no urunit. README.md, DESIGN.md and docs/variants.md name the pinned commits and say how to move a pin. tests/source-pins.sh runs build-bundle.sh against a stub git and docker, so nothing is fetched. It checks that a default build fetches both pins by SHA, that a checkout other than the pin fails the build, that an empty ref builds the branch tip and warns, and that a branch without a ref or a short ref is refused. Against the previous build-bundle.sh, with the pins filled in by hand, it fails the first check: the build asks for `git clone --depth 1 --branch feat/unchanged_containers`. CI runs it in a new Script tests step. CI's bundle job now also checks that each built pins.env carries the pinned commits and *_PINNED=true. Signed-off-by: Anastassios Nanos --- .github/workflows/ci.yml | 10 +++ DESIGN.md | 18 +++--- README.md | 35 ++++++++++- docs/variants.md | 15 +++-- scripts/build-bundle.sh | 122 +++++++++++++++++++++++++++++++----- tests/source-pins.sh | 129 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 299 insertions(+), 30 deletions(-) create mode 100755 tests/source-pins.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f1bd8c..0d97f12 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -124,6 +124,16 @@ jobs: esac grep -q "^ROOTLESS=$want$" "$p" \ || { echo "pins.env does not say ROOTLESS=$want"; exit 1; } + # urunc and urunit are built from pinned commits, and the build + # fails if the checkout is anything else. This checks the record: the + # commits in pins.env are the ones the script pins. + for c in URUNC URUNIT; do + want="$(sed -n "s/^${c}_REF_DEFAULT=\([0-9a-f]\{40\}\)$/\1/p" scripts/build-bundle.sh)" + [ -n "$want" ] || { echo "no ${c}_REF_DEFAULT in build-bundle.sh"; exit 1; } + grep -E "^${c}_(REF|PINNED)=" "$p" + grep -qx "${c}_REF=$want" "$p" || { echo "pins.env does not carry ${c}_REF=$want"; exit 1; } + grep -qx "${c}_PINNED=true" "$p" || { echo "pins.env does not say ${c}_PINNED=true"; exit 1; } + done - uses: actions/upload-artifact@v4 with: diff --git a/DESIGN.md b/DESIGN.md index 7aa4a2b..bf91f2e 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -34,13 +34,17 @@ now done and is what `install.sh` and `scripts/build-bundle.sh` produce. above are not configurable at install time. - **Why three things are built (at release).** Most components are upstream release artifacts. Three have no upstream release and are compiled in the build: urunc - (from `urunc-dev/urunc@feat/unchanged_containers`, CGO static, so native per arch - under qemu/binfmt), urunit (from `NOFireAI/urunit@urunit_agent`, C static), and - the brig `container-initrd`. brig execs into a guest through an in-guest agent - (`urunit-agent`) whose protocol must match the urunc shim, so hull-assets' - prebuilt initrd (hull's agent) is not usable; the build assembles a brig initrd - from urunit + `urunit-agent` (from the same urunc commit as the shim) + busybox + - urunc's `container-init`. The kernel is still fetched, not built: on amd64 from + (from `urunc-dev/urunc` at commit `0818ff1`, on + `feat/unchanged_containers-exec-fixes`, CGO static, so native per arch under + qemu/binfmt), urunit (from `NOFireAI/urunit` at commit `71bfdee`, on + `urunit_agent`, C static), and the brig `container-initrd`. The two sources are + pinned to a commit, not to a branch: a branch tip moves with every push, so it + is not a release input. The README's build section says how to move a pin. + brig execs into a guest through an in-guest agent (`urunit-agent`) whose + protocol must match the urunc shim, so hull-assets' prebuilt initrd (hull's + agent) is not usable; the build assembles a brig initrd from urunit + + `urunit-agent` (from the same urunc commit as the shim) + busybox + urunc's + `container-init`. The kernel is still fetched, not built: on amd64 from the bunny Cloud-Hypervisor kernel image (`harbor.nbfc.io/nubificus/bunny/linux-kernel-cloud-hypervisor`), on arm64 from `ghcr.io/nofireai/hull-assets`. diff --git a/README.md b/README.md index d81547f..f887aba 100644 --- a/README.md +++ b/README.md @@ -271,8 +271,8 @@ the three that do not: | Component | Source | In the tarball | | --- | --- | --- | | brig, brigd | `brig-sh/brig` release | fetched, verified against its signed `checksums.txt` | -| urunc, containerd-shim-urunc-v2 | built from `urunc-dev/urunc` @ `feat/unchanged_containers` | CGO-static, built in a Go container | -| urunit | built from `NOFireAI/urunit` @ `urunit_agent` | C-static; goes into the initrd | +| urunc, containerd-shim-urunc-v2 | built from `urunc-dev/urunc` at commit `0818ff1` (branch `feat/unchanged_containers-exec-fixes`) | CGO-static, built in a Go container | +| urunit | built from `NOFireAI/urunit` at commit `71bfdee` (branch `urunit_agent`) | C-static; goes into the initrd | | container-initrd | built from the above | assembled for brig, not fetched | | guest kernel (amd64) | `harbor.nbfc.io/nubificus/bunny/linux-kernel-cloud-hypervisor` | fetched; extracted from the bunny image's `/.boot/kernel` | | guest kernel (arm64) | `ghcr.io/nofireai/hull-assets` | fetched; the same kernel hull and brig use | @@ -284,6 +284,37 @@ the three that do not: Then it generates the config files, systemd units, `brig-ctl` and the uninstaller, and packs the whole `/var/lib/brig/data` tree. +### urunc and urunit are pinned to a commit + +A branch tip is not a release input. It moves with every push, so two builds +of one bundle version would carry different code. So `build-bundle.sh` pins +each of the two to one commit, `URUNC_REF_DEFAULT` and `URUNIT_REF_DEFAULT`. +The build fetches that commit by its SHA, fails if the checkout is anything +else, and records it in `pins.env` as `URUNC_REF` and `URUNIT_REF`. CI checks +that record on every bundle it builds. + +The urunc pin is `feat/unchanged_containers` plus the two `urunit-agent` exec +fixes, urunc-dev/urunc#1059 and urunc-dev/urunc#1060. The urunit pin is what +v0.1.0-rc6 to rc8 shipped. + +Moving a pin is a reviewed one-line change to the `*_REF_DEFAULT` line, plus +its `*_BRANCH_DEFAULT` when the new commit comes from another branch. A one-off +build of another commit takes it from the environment: + +```console +$ URUNC_REF=<40-character sha> scripts/build-bundle.sh --arch amd64 --version dev +``` + +To build a branch tip on purpose, set the ref empty. The build then warns in its +log and records `URUNC_PINNED=false` in `pins.env`: + +```console +$ URUNC_REF= URUNC_BRANCH=feat/unchanged_containers scripts/build-bundle.sh --arch amd64 --version dev +``` + +A branch given without a ref is refused, since it is unclear which of the two +was meant. + ### Why the initrd is brig's own, not hull-assets' brig execs into a guest through an in-guest agent, `urunit-agent`, whose wire diff --git a/docs/variants.md b/docs/variants.md index 5def71d..e0f0c23 100644 --- a/docs/variants.md +++ b/docs/variants.md @@ -44,7 +44,8 @@ by `build-bundle.sh` and packed into the tarball. The installer just unpacks the ### The runtime is built, then packed No urunc release publishes a binary that boots a generic container. So the build -compiles one, from `urunc-dev/urunc` at branch `feat/unchanged_containers`, in a +compiles one, from `urunc-dev/urunc` at the commit `build-bundle.sh` pins +(`0818ff1`, on branch `feat/unchanged_containers-exec-fixes`), in a `golang:1.26.4` container: ```console @@ -102,7 +103,7 @@ assembles a brig initrd, with urunc's - `container-init`, urunc's early-userspace script (mounts the shared rootfs, stages urunit and the agent, `switch_root`s into urunit); - `urunit`, the tiny C init that becomes PID 1, built static from - `NOFireAI/urunit` at branch `urunit_agent`; + `NOFireAI/urunit` at the pinned commit `71bfdee` (branch `urunit_agent`); - `urunit-agent`, built from the **same** urunc commit that produced the shim, so the protocol matches by construction; - a static `busybox`. @@ -177,10 +178,12 @@ refuses a stock binary, rather than printing a table of zeroes. build from; upstream is better. 3. Settled: the guest kernel is fetched — on amd64 from the bunny Cloud-Hypervisor kernel image, on arm64 from `hull-assets` by the same tags - hull uses; the runtime is built from `urunc-dev/urunc` at - `feat/unchanged_containers`; and - the initrd is built for brig from `NOFireAI/urunit` at `urunit_agent` plus - urunc's own `packaging/container-initrd`, so its agent matches the shim. + hull uses; the runtime is built from `urunc-dev/urunc` at a pinned commit + (`0818ff1`, on `feat/unchanged_containers-exec-fixes`); and the initrd is + built for brig from `NOFireAI/urunit` at a pinned commit (`71bfdee`, on + `urunit_agent`) plus urunc's own `packaging/container-initrd`, so its agent + matches the shim. Both pins are commits because a branch tip moves with every + push; the README's build section says how to move one. 4. Does hvi join `monitors-build`, or does `build-bundle.sh` learn a second source? 5. Is `introspection` a variant at all, or `generic-boot` plus an opt-in flag? diff --git a/scripts/build-bundle.sh b/scripts/build-bundle.sh index d726dc3..d8f3b46 100755 --- a/scripts/build-bundle.sh +++ b/scripts/build-bundle.sh @@ -93,11 +93,52 @@ done # bundle is cut for. BRIG_VERSION=latest still works for a local build. BRIG_VERSION="${BRIG_VERSION:-v0.2.0}" BRIG_REPO="${BRIG_REPO:-brig-sh/brig}" +# urunc and urunit have no release that carries what brig needs, so they are +# built from source, and the source is pinned to one commit each. A branch tip +# moves with every push, so it is not a release input: two builds of one bundle +# version would carry different code. Moving a pin is a reviewed one-line +# change to a *_REF_DEFAULT below, plus its *_BRANCH_DEFAULT when the commit +# comes from another branch. +# +# *_BRANCH names the branch the pinned commit comes from. It is recorded in +# pins.env, and it is what gets built when the matching *_REF is set empty +# (URUNC_REF= scripts/build-bundle.sh ...): the tip, with a warning in the +# build log and *_PINNED=false in pins.env. An unset *_REF takes the pin +# below, which is why these use ${VAR-default} and not ${VAR:-default}. +# +# urunc: feat/unchanged_containers plus the two urunit-agent exec fixes, +# urunc-dev/urunc#1059 and #1060. The agent in the initrd is built from this +# checkout too. URUNC_REPO="${URUNC_REPO:-urunc-dev/urunc}" -URUNC_BRANCH="${URUNC_BRANCH:-feat/unchanged_containers}" +URUNC_BRANCH_DEFAULT=feat/unchanged_containers-exec-fixes +URUNC_REF_DEFAULT=0818ff104781087a12a75059062c3407a8b97c8a URUNC_GO_IMAGE="${URUNC_GO_IMAGE:-golang:1.26.4}" +# urunit: the init in the initrd. What v0.1.0-rc6 to rc8 shipped. URUNIT_REPO="${URUNIT_REPO:-NOFireAI/urunit}" -URUNIT_BRANCH="${URUNIT_BRANCH:-urunit_agent}" +URUNIT_BRANCH_DEFAULT=urunit_agent +URUNIT_REF_DEFAULT=71bfdeefb7bced121c4e75afa97550523af34152 +# A branch given without a ref would otherwise build the pinned commit and +# ignore the branch, so ask which one was meant. +if [ -n "${URUNC_BRANCH:-}" ] && [ "$URUNC_BRANCH" != "$URUNC_BRANCH_DEFAULT" ] && [ -z "${URUNC_REF+set}" ]; then + fatal "URUNC_BRANCH=$URUNC_BRANCH is set but URUNC_REF is not. Set URUNC_REF= to build that branch's tip, or URUNC_REF= to build one commit from it." +fi +if [ -n "${URUNIT_BRANCH:-}" ] && [ "$URUNIT_BRANCH" != "$URUNIT_BRANCH_DEFAULT" ] && [ -z "${URUNIT_REF+set}" ]; then + fatal "URUNIT_BRANCH=$URUNIT_BRANCH is set but URUNIT_REF is not. Set URUNIT_REF= to build that branch's tip, or URUNIT_REF= to build one commit from it." +fi +URUNC_BRANCH="${URUNC_BRANCH:-$URUNC_BRANCH_DEFAULT}" +URUNC_REF="${URUNC_REF-$URUNC_REF_DEFAULT}" +URUNIT_BRANCH="${URUNIT_BRANCH:-$URUNIT_BRANCH_DEFAULT}" +URUNIT_REF="${URUNIT_REF-$URUNIT_REF_DEFAULT}" +# The checkout compares HEAD with the ref, so a ref is a full SHA: a short one, +# a tag or a branch name would never match. +for r in "URUNC_REF=$URUNC_REF" "URUNIT_REF=$URUNIT_REF"; do + v="${r#*=}" + [ -n "$v" ] || continue + case "$v" in + *[!0-9a-f]*) fatal "${r%%=*}='$v' is not a commit: pin a full 40-character SHA" ;; + esac + [ "${#v}" -eq 40 ] || fatal "${r%%=*}='$v' is not a commit: pin a full 40-character SHA" +done # Static musl busybox for the initrd, per arch. Extracted from this image so we # do not depend on busybox.net, which publishes 1.35.0 for x86_64 only. BUSYBOX_IMAGE="${BUSYBOX_IMAGE:-busybox:1.36.1-musl}" @@ -849,6 +890,43 @@ verify() { fi } +# checkout_source : put github.com/ into +# at exactly the commit , or at the tip of when is empty, +# and print the commit it checked out. The commit is fetched by its SHA: a +# --depth 1 --branch clone stops containing a pinned commit as soon as the +# branch moves past it. GitHub serves any reachable commit by SHA, a pull +# request's head included. +checkout_source() { + cs_repo="$1"; cs_branch="$2"; cs_ref="$3"; cs_dir="$4" + # stdout carries the commit and nothing else. + git init -q "$cs_dir" >&2 + git -C "$cs_dir" remote add origin "https://github.com/$cs_repo" >&2 + if [ -n "$cs_ref" ]; then + git -C "$cs_dir" fetch -q --depth 1 origin "$cs_ref" >&2 \ + || fatal "could not fetch $cs_repo@$cs_ref" + else + git -C "$cs_dir" fetch -q --depth 1 origin "refs/heads/$cs_branch" >&2 \ + || fatal "could not fetch the $cs_branch branch of $cs_repo" + fi + git -C "$cs_dir" -c advice.detachedHead=false checkout -q FETCH_HEAD >&2 + cs_head="$(git -C "$cs_dir" rev-parse HEAD)" + if [ -n "$cs_ref" ] && [ "$cs_head" != "$cs_ref" ]; then + fatal "$cs_repo: checked out $cs_head, not the pinned $cs_ref" + fi + echo "$cs_head" +} + +# pinned_note : log what was checked out, +# and warn when it was a branch tip. +pinned_note() { + if [ -n "$4" ]; then + info "$1: checked out $2@$5 (pinned; from branch $3)" + else + info "WARNING: $1: built $2@$5, the tip of $3 at build time. It is not pinned," + info "WARNING: and a rebuild of this bundle version may carry different code." + fi +} + info "building $NAME (variant $VARIANT)" info "fetching components" @@ -861,15 +939,17 @@ if [ "$VARIANT" = "stock" ] && [ -n "$URUNC_VERSION_STOCK" ]; then verify "$DL/containerd-shim-urunc-v2" "" "containerd-shim-urunc-v2_static_$ARCH" URUNC_SOURCE="$URUNC_REPO@$URUNC_VERSION_STOCK" URUNC_REF="$URUNC_VERSION_STOCK" + URUNC_PINNED=true else - command -v docker >/dev/null 2>&1 || fatal "docker is required to build urunc from $URUNC_REPO@$URUNC_BRANCH" + command -v docker >/dev/null 2>&1 || fatal "docker is required to build urunc from $URUNC_REPO" command -v git >/dev/null 2>&1 || fatal "git is required to build urunc from source" - info "building urunc from $URUNC_REPO@$URUNC_BRANCH ($URUNC_GO_IMAGE, linux/$ARCH)" src="$TMP_DIR/urunc-src" - git clone --depth 1 --branch "$URUNC_BRANCH" "https://github.com/$URUNC_REPO" "$src" 2>/dev/null \ - || git clone "https://github.com/$URUNC_REPO" "$src" - ( cd "$src" && git checkout "$URUNC_BRANCH" 2>/dev/null ) || true - URUNC_REF="$(cd "$src" && git rev-parse HEAD)" + URUNC_PINNED=true + [ -n "$URUNC_REF" ] || URUNC_PINNED=false + u_commit="$(checkout_source "$URUNC_REPO" "$URUNC_BRANCH" "$URUNC_REF" "$src")" + pinned_note urunc "$URUNC_REPO" "$URUNC_BRANCH" "$URUNC_REF" "$u_commit" + URUNC_REF="$u_commit" + info "building urunc $URUNC_REF ($URUNC_GO_IMAGE, linux/$ARCH)" # --platform builds native inside a target-arch container (needs binfmt for # a cross build). The static urunc binary is CGO, so this is not a Go cross # compile. @@ -888,16 +968,20 @@ fi # brig builds its own initrd rather than shipping hull-assets': it execs into a # guest through urunit-agent, which has to match the urunc shim's protocol, so # the agent is built from the same urunc checkout ($src) as the shim above. -URUNIT_REF="" -if [ "$VARIANT" != "stock" ]; then +URUNIT_PINNED="" +if [ "$VARIANT" = "stock" ]; then + # stock boots the unikernel's own init, so there is no urunit to build. + URUNIT_REF="" +else command -v docker >/dev/null 2>&1 || fatal "docker is required to build the brig initrd" [ -d "${src:-}" ] || fatal "the urunc checkout is needed to build the initrd (build urunc from source)" - info "building urunit from $URUNIT_REPO@$URUNIT_BRANCH (linux/$ARCH)" usrc="$TMP_DIR/urunit-src" - git clone --depth 1 --branch "$URUNIT_BRANCH" "https://github.com/$URUNIT_REPO" "$usrc" 2>/dev/null \ - || git clone "https://github.com/$URUNIT_REPO" "$usrc" - ( cd "$usrc" && git checkout "$URUNIT_BRANCH" 2>/dev/null ) || true - URUNIT_REF="$(cd "$usrc" && git rev-parse HEAD)" + URUNIT_PINNED=true + [ -n "$URUNIT_REF" ] || URUNIT_PINNED=false + ut_commit="$(checkout_source "$URUNIT_REPO" "$URUNIT_BRANCH" "$URUNIT_REF" "$usrc")" + pinned_note urunit "$URUNIT_REPO" "$URUNIT_BRANCH" "$URUNIT_REF" "$ut_commit" + URUNIT_REF="$ut_commit" + info "building urunit $URUNIT_REF (linux/$ARCH)" docker run --rm --platform "linux/$ARCH" -v "$usrc":/u -w /u alpine:3.20 \ sh -c "apk add --no-cache build-base linux-headers make musl-dev >/dev/null && make static" \ || fatal "urunit build failed" @@ -1359,9 +1443,11 @@ URUNC_VERSION=$URUNC_SOURCE URUNC_REPO=$URUNC_REPO URUNC_BRANCH=$URUNC_BRANCH URUNC_REF=$URUNC_REF +URUNC_PINNED=$URUNC_PINNED URUNIT_REPO=$([ "$VARIANT" = "stock" ] && echo "" || echo "$URUNIT_REPO") URUNIT_BRANCH=$([ "$VARIANT" = "stock" ] && echo "" || echo "$URUNIT_BRANCH") URUNIT_REF=$URUNIT_REF +URUNIT_PINNED=$URUNIT_PINNED INITRD_SOURCE=$([ "$VARIANT" = "stock" ] && echo "" || echo "built:$URUNC_REF") PREFIX=$PREFIX DATA_DIR=$DATA_DIR @@ -1383,6 +1469,12 @@ ASSETS_REPO=$([ "$KERNEL_FROM_ASSETS" = true ] && echo "$ASSETS_REPO" || echo "" ASSETS_VERSION=$([ "$KERNEL_FROM_ASSETS" = true ] && echo "$ASSETS_VERSION" || echo "") ASSETS_URUNC_REF=$ASSETS_URUNC_REF PINS +if [ "$URUNC_PINNED" = false ]; then + echo "# WARNING: urunc is the tip of $URUNC_BRANCH at build time, not a pinned commit." >> "$STAGE/pins.env" +fi +if [ "$URUNIT_PINNED" = false ]; then + echo "# WARNING: urunit is the tip of $URUNIT_BRANCH at build time, not a pinned commit." >> "$STAGE/pins.env" +fi # Deterministic tar: sorted names, one fixed timestamp, root-owned, fixed # directory and file modes, no extended headers. Fetched components are diff --git a/tests/source-pins.sh b/tests/source-pins.sh new file mode 100755 index 0000000..e46f6d8 --- /dev/null +++ b/tests/source-pins.sh @@ -0,0 +1,129 @@ +#!/bin/sh +# +# Run build-bundle.sh against a stub git and docker, and check which urunc and +# urunit commits it asks for. Nothing is fetched: the stub git records the ref +# it was asked to fetch and answers rev-parse with it, the stub docker fakes the +# urunc build and fails the urunit build, which ends the run. +# +# Needs the tools build-bundle.sh checks for (curl, tar, sha256sum, awk, sed). + +set -eu + +here="$(cd "$(dirname "$0")/.." && pwd)" + +skip() { echo "SKIP: $*"; exit 0; } +fail() { echo "FAIL: $*" >&2; exit 1; } +ok() { echo "ok - $*"; } + +command -v sha256sum >/dev/null 2>&1 || skip "build-bundle.sh needs sha256sum" + +urunc_pin="$(sed -n 's/^URUNC_REF_DEFAULT=\([0-9a-f]*\)$/\1/p' "$here/scripts/build-bundle.sh")" +urunit_pin="$(sed -n 's/^URUNIT_REF_DEFAULT=\([0-9a-f]*\)$/\1/p' "$here/scripts/build-bundle.sh")" +urunc_branch="$(sed -n 's/^URUNC_BRANCH_DEFAULT=\(.*\)$/\1/p' "$here/scripts/build-bundle.sh")" +[ "${#urunc_pin}" -eq 40 ] || fail "no URUNC_REF_DEFAULT in build-bundle.sh" +[ "${#urunit_pin}" -eq 40 ] || fail "no URUNIT_REF_DEFAULT in build-bundle.sh" +tip=1111111111111111111111111111111111111111 + +T="$(mktemp -d)" +trap 'rm -rf "$T"' EXIT +mkdir -p "$T/stub" + +# git: log every call. fetch remembers what it was asked for, and rev-parse +# HEAD answers with it, or with $STUB_HEAD, or with $tip for a branch. +cat > "$T/stub/git" <> "$T/git.log" +while [ \$# -gt 0 ]; do + case "\$1" in + -C|-c) shift 2 ;; + *) break ;; + esac +done +case "\$1" in + fetch) for a in "\$@"; do last="\$a"; done; echo "\$last" > "$T/fetched" ;; + rev-parse) + f="\$(cat "$T/fetched")" + if [ -n "\${STUB_HEAD:-}" ]; then echo "\$STUB_HEAD" + elif [ "\${#f}" -eq 40 ]; then echo "\$f" + else echo $tip; fi ;; +esac +exit 0 +STUB + +# docker: the urunc build writes the two binaries the script copies out; the +# urunit build fails. +cat > "$T/stub/docker" <<'STUB' +#!/bin/sh +case "$*" in + *"apk add"*) exit 1 ;; + *"make static"*) + prev="" + for a in "$@"; do + case "$prev" in -v) case "$a" in *:/app) src="${a%:/app}" ;; esac ;; esac + prev="$a" + done + mkdir -p "$src/dist" + : > "$src/dist/urunc_static_amd64" + : > "$src/dist/containerd-shim-urunc-v2_static_amd64" + exit 0 ;; +esac +exit 1 +STUB +chmod 0755 "$T/stub"/* + +# build [VAR=value...]: run build-bundle.sh with those variables, output in +# $T/out.log. URUNC_* and URUNIT_* start unset. +build() { + : > "$T/git.log" + : > "$T/fetched" + # Every run ends in a failure, by design: at the stub urunit build or at + # the check under test. + env -u URUNC_REF -u URUNC_BRANCH -u URUNIT_REF -u URUNIT_BRANCH \ + PATH="$T/stub:$PATH" "$@" \ + sh "$here/scripts/build-bundle.sh" --arch amd64 --version test --out "$T/dist" \ + > "$T/out.log" 2>&1 || true +} + +# 1. Defaults: both repos are fetched at their pinned commit, by SHA. +build +grep -qx "git -C .*/urunc-src fetch -q --depth 1 origin $urunc_pin" "$T/git.log" \ + || { cat "$T/git.log" "$T/out.log" >&2; fail "urunc was not fetched at its pin"; } +grep -qx "git -C .*/urunit-src fetch -q --depth 1 origin $urunit_pin" "$T/git.log" \ + || { cat "$T/git.log" "$T/out.log" >&2; fail "urunit was not fetched at its pin"; } +grep -q "urunc: checked out urunc-dev/urunc@$urunc_pin (pinned" "$T/out.log" \ + || { cat "$T/out.log" >&2; fail "the build log does not name the pinned urunc"; } +if grep -q -e "git clone" -e "WARNING" "$T/git.log" "$T/out.log"; then + fail "a pinned build cloned a branch or warned" +fi +ok "a default build fetches urunc and urunit at their pinned commits" + +# 2. A checkout that is not the pinned commit fails the build. +build STUB_HEAD=2222222222222222222222222222222222222222 +grep -q "checked out 2222222222222222222222222222222222222222, not the pinned $urunc_pin" "$T/out.log" \ + || { cat "$T/out.log" >&2; fail "a wrong checkout was not refused"; } +ok "a checkout other than the pin fails the build" + +# 3. An empty ref builds the branch tip, and says so. +build URUNC_REF= +grep -qx "git -C .*/urunc-src fetch -q --depth 1 origin refs/heads/$urunc_branch" "$T/git.log" \ + || { cat "$T/git.log" >&2; fail "URUNC_REF= did not fetch the branch"; } +grep -q "WARNING: urunc: built urunc-dev/urunc@$tip, the tip of $urunc_branch" "$T/out.log" \ + || { cat "$T/out.log" >&2; fail "a tip build did not warn"; } +ok "URUNC_REF= builds the tip of the branch, with a warning" + +# 4. A branch without a ref is refused before anything is fetched. +build URUNC_BRANCH=feat/something-else +grep -q "URUNC_BRANCH=feat/something-else is set but URUNC_REF is not" "$T/out.log" \ + || { cat "$T/out.log" >&2; fail "a branch without a ref was not refused"; } +[ ! -s "$T/git.log" ] || fail "git ran for a refused build" +ok "a branch without a ref is refused" + +# 5. A ref that is not a full SHA is refused before anything is fetched. +build URUNIT_REF=urunit_agent +grep -q "URUNIT_REF='urunit_agent' is not a commit" "$T/out.log" \ + || { cat "$T/out.log" >&2; fail "a branch name as a ref was not refused"; } +build URUNC_REF=0818ff1 +grep -q "URUNC_REF='0818ff1' is not a commit" "$T/out.log" \ + || { cat "$T/out.log" >&2; fail "a short SHA was not refused"; } +[ ! -s "$T/git.log" ] || fail "git ran for a refused build" +ok "a ref that is not a full SHA is refused"