From 50e77ac7a471fc9b675c1272ac59acf9d4455cf5 Mon Sep 17 00:00:00 2001 From: Evan Jarrett Date: Thu, 13 Aug 2026 20:26:48 -0500 Subject: [PATCH] test/e2e: snapshot the appview DB per migration level Wiping ui.db on every switch also wipes the OAuth sessions and the appview's oauth_p256/jwt_rsa keys, so each batch cost an interactive `docker-credential-atcr login`. There are only five distinct migration levels across the stack (27 for batches 00-08, 28 for 09-11, 29 for 12-13, 32 for 14, 34 for 15), so the DB is snapshotted per level and restored instead of re-migrated. One login now serves every batch sharing a level. Also skip the compose pin from val/01 onward: a7c7db6 lands there, so the branch's own compose already has the shared netns and pinning would drag in later batches' changes. Co-Authored-By: Claude Opus 5 (1M context) --- test/e2e/val-switch.sh | 74 +++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/test/e2e/val-switch.sh b/test/e2e/val-switch.sh index b13ef50..1e48422 100755 --- a/test/e2e/val-switch.sh +++ b/test/e2e/val-switch.sh @@ -28,6 +28,8 @@ BRANCH="${1:?usage: val-switch.sh }" REPO=/home/data/atcr.io PROJECT=atcrio UI_VOLUME=${PROJECT}_atcr-ui +SNAPDIR=${ATCR_VAL_SNAPDIR:-$HOME/.cache/atcr-val-snapshots} +mkdir -p "$SNAPDIR" cd "$REPO" @@ -37,18 +39,49 @@ git checkout -- docker-compose.yml 2>/dev/null || true echo "==> checking out ${BRANCH}" git checkout "$BRANCH" -echo "==> pinning docker-compose.yml to main (dev topology only, do not commit)" -git checkout main -- docker-compose.yml -# `git checkout -- ` STAGES the file. Left staged, the next commit -# on the batch branch silently swallows main's compose file. Unstage it so it -# shows as an ordinary working-tree modification. -git restore --staged docker-compose.yml +# Only batch 00 predates a7c7db6, which is what puts the appview in the hold's +# netns so did:web:localhost%3A8080 resolves. From val/01 onward the branch's +# own compose already has it, and pinning would drag in later batches' changes. +if grep -q 'network_mode: "service:atcr-hold"' docker-compose.yml; then + echo "==> branch compose already has the shared netns; no pin needed" +else + echo "==> pinning docker-compose.yml to main (dev topology only, do not commit)" + git checkout main -- docker-compose.yml + # `git checkout -- ` STAGES the file. Left staged, the next commit + # on the batch branch silently swallows main's compose file. Unstage it so it + # shows as an ordinary working-tree modification. + git restore --staged docker-compose.yml +fi -echo "==> tearing down appview + its DB volume" +# The appview DB only has to match the branch's MIGRATION LEVEL, not the +# branch. Across the whole stack there are five distinct levels (27 for batches +# 00-08, 28 for 09-11, 29 for 12-13, 32 for 14, 34 for 15), so a per-level +# snapshot is reused instead of re-migrating from empty every time. +# +# This matters because ui.db holds the OAuth sessions AND the appview's +# oauth_p256/jwt_rsa keys. Nuking it logs out the registry credential helper, so +# a naive wipe-per-switch costs an interactive `docker-credential-atcr login` on +# every batch. Restoring a snapshot keeps the session alive across every batch +# that shares a level. +LEVEL=$(ls pkg/appview/db/migrations/*.yaml 2>/dev/null \ + | sed -E 's|.*/([0-9]{4})_.*|\1|' | sort -n | tail -1) +LEVEL=${LEVEL:-0000} +SNAP="$SNAPDIR/ui.level-${LEVEL}.db" +echo "==> branch migration level: ${LEVEL}" + +echo "==> tearing down appview" docker compose rm -sf atcr-appview -docker volume rm "$UI_VOLUME" 2>/dev/null || echo " (volume already gone)" -echo "==> starting appview on a fresh DB" +if [ -f "$SNAP" ]; then + echo "==> restoring DB snapshot for level ${LEVEL} (keeps OAuth sessions alive)" + docker run --rm -v "${UI_VOLUME}:/v" -v "$SNAPDIR:/s:ro" alpine sh -c \ + 'rm -f /v/ui.db /v/ui.db-wal /v/ui.db-shm && cp /s/'"$(basename "$SNAP")"' /v/ui.db' +else + echo "==> no snapshot for level ${LEVEL}; starting from an empty DB" + docker volume rm "$UI_VOLUME" 2>/dev/null || echo " (volume already gone)" +fi + +echo "==> starting appview" docker compose up -d atcr-appview echo "==> waiting for appview to answer on 127.0.0.1:5000" @@ -61,13 +94,24 @@ for i in $(seq 1 90); do sleep 1 done -echo "==> migration level on the fresh DB" -docker exec atcr-appview sh -c \ - 'sqlite3 /var/lib/atcr/ui.db "select max(version) from schema_migrations"' 2>/dev/null \ - || echo " (db not created yet — check: docker logs atcr-appview)" +ACTUAL=$(docker exec atcr-appview sh -c \ + 'sqlite3 /var/lib/atcr/ui.db "select max(version) from schema_migrations"' 2>/dev/null || true) +echo "==> migration level on the running DB: ${ACTUAL:-unknown} (expected ${LEVEL#0})" + +# Bank a snapshot for this level so the next batch at the same level restores +# instead of re-migrating — and keeps its OAuth sessions. +if [ ! -f "$SNAP" ] && [ -n "$ACTUAL" ]; then + echo "==> banking a snapshot for level ${LEVEL}" + docker exec atcr-appview sh -c \ + 'sqlite3 /var/lib/atcr/ui.db ".backup /var/lib/atcr/ui.snap.db"' \ + && docker run --rm -v "${UI_VOLUME}:/v" -v "$SNAPDIR:/s" alpine sh -c \ + "mv /v/ui.snap.db /s/$(basename "$SNAP")" \ + && echo " saved $SNAP" +fi echo echo "branch: $(git log --oneline -1)" -echo "compose: pinned to main (expect it to show as modified; leave it unstaged)" +echo "migrations: level ${ACTUAL:-?}" echo "appview: http://127.0.0.1:5000 (NOT localhost — that 307s)" -echo "hold admin: http://127.0.0.1:8080/admin (OAuth as did:plc:pddp4xt5lgnv2qsegbzzs4xg / evan.jarrett.net)" +echo "hold admin: http://127.0.0.1:8080/admin (login again if the hold rebuilt)" +echo "registry: docker-credential-atcr login (needed after an empty-DB start)"