test: container lab for the two-tier apply
A disposable ubuntu container, a fake private tier and a fake bootstrap endpoint, so the whole documented path — chezmoi init --apply, dotup pick, dotup private, cmp apply, dotsecrets — can run end to end without touching a real machine or a real credential. The fake tier mirrors the real one's structure (seven secrets and one alias) because dotsecrets is copied verbatim and the "8 exports, not 7" assertion depends on that cardinality; its ids are sequential and obviously synthetic. check-verbatim.sh keeps the fake tier's copies of shipped files honest, and snapshot.sh records file modes so a 644 where a 600 belongs is a diff.
This commit is contained in:
Executable
+114
@@ -0,0 +1,114 @@
|
||||
#!/bin/bash
|
||||
# Run one scenario against the working tree, in a container, offline.
|
||||
#
|
||||
# run.sh <scenario.sh> [--image IMG] [--keep] [--priv REPO]
|
||||
#
|
||||
# Everything the real system talks to is stood in locally: the bootstrap
|
||||
# endpoint, both git remotes, and the secrets manager. Nothing here touches the
|
||||
# real endpoint, the real repos, or the network, so a scenario can be run as
|
||||
# often as it takes without publishing anything or spending a password.
|
||||
#
|
||||
# The code under test is the CURRENT WORKING TREE, committed or not.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
LAB=$PWD
|
||||
PUB=$(CDPATH= cd -- ../.. && pwd)
|
||||
PRIV_DEFAULT=$LAB/fake-private
|
||||
|
||||
scenario=""; IMAGE=dotup-lab:24.04; KEEP=0; PRIV=$PRIV_DEFAULT
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
--image) IMAGE=$2; shift 2 ;;
|
||||
--keep) KEEP=1; shift ;;
|
||||
--priv) PRIV=$2; shift 2 ;;
|
||||
-*) echo "unknown flag $1" >&2; exit 2 ;;
|
||||
*) scenario=$1; shift ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$scenario" ] || { echo "usage: run.sh <scenario.sh> [--image IMG] [--keep]" >&2; exit 2; }
|
||||
[ -f "$scenario" ] || { echo "no such scenario: $scenario" >&2; exit 2; }
|
||||
name=$(basename "$scenario" .sh)
|
||||
|
||||
# Distinct per run. The route and password are secrets in production, so the
|
||||
# lab never reuses a value and never hardcodes one -- a scenario that only
|
||||
# passes against a fixed password is testing the fixture.
|
||||
rand() { head -c 18 /dev/urandom | od -An -tx1 | tr -d ' \n'; }
|
||||
ROUTE=r-$(rand); PASS=$(rand); GIT_TOKEN=$(rand); USER_=ben
|
||||
GW=$(docker network inspect bridge --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}')
|
||||
# Left unset so the server takes any free port and reports it back; several
|
||||
# scenarios run at once and a fixed port makes them collide.
|
||||
PORT=${LAB_PORT:-0}
|
||||
ROOT=$(mktemp -d /tmp/dotup-lab.XXXXXX)
|
||||
C=dotup-lab-$name-$$
|
||||
|
||||
cleanup() {
|
||||
rc=$?
|
||||
[ -n "${SRV:-}" ] && kill "$SRV" 2>/dev/null || :
|
||||
if [ "$KEEP" = 1 ]; then
|
||||
echo "kept: container $C lab root $ROOT" >&2
|
||||
else
|
||||
docker rm -f "$C" >/dev/null 2>&1 || :
|
||||
rm -rf "$ROOT"
|
||||
fi
|
||||
exit $rc
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
echo "== lab: snapshotting working trees =="
|
||||
sh snapshot.sh "$PUB" "$ROOT/git" dotfiles-public >/dev/null
|
||||
sh snapshot.sh "$PRIV" "$ROOT/git" dotfiles-private >/dev/null
|
||||
echo " public: $(git -C "$ROOT/git/dotfiles-public.git" ls-tree -r --name-only HEAD | wc -l) files"
|
||||
echo " private: $(git -C "$ROOT/git/dotfiles-private.git" ls-tree -r --name-only HEAD | wc -l) files ($PRIV)"
|
||||
|
||||
export LAB_GIT_ROOT=$ROOT/git LAB_PORT=$PORT LAB_ROUTE=$ROUTE \
|
||||
LAB_USER=$USER_ LAB_PASS=$PASS LAB_GIT_TOKEN=$GIT_TOKEN LAB_BIND=$GW
|
||||
# The blob is byte-for-byte the shape the real endpoint returns: two KEY=VALUE
|
||||
# lines, the repo URL carrying an inline token that the installer has to split
|
||||
# out into a credential file.
|
||||
# {PORT} is filled in by the server once it has bound one. The blob is
|
||||
# otherwise byte-for-byte the shape the real endpoint returns: two KEY=VALUE
|
||||
# lines, the repo URL carrying an inline token the installer must split out.
|
||||
export LAB_BLOB="PRIVATE_REPO_URL=http://git:$GIT_TOKEN@$GW:{PORT}/git/dotfiles-private.git
|
||||
BWS_ACCESS_TOKEN=lab-bws-$(rand)
|
||||
"
|
||||
python3 serve.py >"$ROOT/serve.log" 2>&1 & SRV=$!
|
||||
for _ in $(seq 40); do
|
||||
PORT=$(sed -n 's/^lab: listening on [^:]*:\([0-9]*\).*/\1/p' "$ROOT/serve.log")
|
||||
[ -n "$PORT" ] && [ "$PORT" != 0 ] && break
|
||||
sleep 0.25
|
||||
done
|
||||
[ -n "$PORT" ] && [ "$PORT" != 0 ] || { echo "lab server never reported a port:"; cat "$ROOT/serve.log"; exit 1; }
|
||||
for _ in $(seq 40); do
|
||||
curl -sf -o /dev/null -u "$USER_:$PASS" "http://$GW:$PORT/$ROUTE/bootstrap.env" && break
|
||||
sleep 0.25
|
||||
done
|
||||
curl -sf -o /dev/null -u "$USER_:$PASS" "http://$GW:$PORT/$ROUTE/bootstrap.env" \
|
||||
|| { echo "lab server never came up:"; cat "$ROOT/serve.log"; exit 1; }
|
||||
echo " endpoint up on $GW:$PORT"
|
||||
|
||||
docker image inspect "$IMAGE" >/dev/null 2>&1 || {
|
||||
echo "== lab: building $IMAGE =="; docker build -q -t "$IMAGE" -f Dockerfile . >/dev/null; }
|
||||
docker rm -f "$C" >/dev/null 2>&1 || :
|
||||
docker run -d --name "$C" --add-host lab:"$GW" "$IMAGE" >/dev/null
|
||||
docker cp "$scenario" "$C:/tmp/scenario.sh" >/dev/null
|
||||
[ -d assets ] && docker cp assets "$C:/tmp/assets" >/dev/null
|
||||
|
||||
echo "== lab: $name on $IMAGE =="
|
||||
# Bare -e names inherit from this shell, so the password and the git token never
|
||||
# appear in docker's argv -- /proc/<pid>/cmdline is world readable, which is the
|
||||
# same hole this repo was fixed to stop opening.
|
||||
export BOOT_URL="http://$GW:$PORT/$ROUTE" BOOT_USER=$USER_ BOOT_PW=$PASS \
|
||||
PUB_URL="http://$GW:$PORT/git/dotfiles-public.git"
|
||||
set +e
|
||||
docker exec -u ben \
|
||||
-e BOOT_URL -e BOOT_USER -e BOOT_PW -e PUB_URL \
|
||||
-e HOME=/home/ben -e LANG=en_US.UTF-8 \
|
||||
"$C" bash /tmp/scenario.sh
|
||||
rc=$?
|
||||
set -e
|
||||
# Redact before anything is printed or kept: a scenario log that quoted the
|
||||
# password back would be as bad as committing it.
|
||||
sed -i -e "s|$PASS|<BOOT_PW>|g" -e "s|$GIT_TOKEN|<GIT_TOKEN>|g" -e "s|$ROUTE|<ROUTE>|g" \
|
||||
"$ROOT/serve.log" 2>/dev/null || :
|
||||
echo "== lab: $name exit $rc =="
|
||||
exit $rc
|
||||
Reference in New Issue
Block a user