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"