Files
member-console/test/teardown-stack.sh
T
cgalo5758 d45a51d2e8 Profile-gate FedWiki and Discourse in the test stack
Put the fedwiki chain (init, render, farm, caddy) behind a fedwiki
compose profile symmetric with discourse's; the default composition is
neither, selected via COMPOSE_PROFILES in test/.env, so a default stack
no longer binds host 443.

Guard every script and walkthrough on service presence: shared
skipUnlessIntegrationEndpointReachable helper, seed-stack presence
checks (also repairing its unsourced .env and container-native render
invocation), generic root-owned testdata reclaim in teardown, discourse
coverage in verify-stack-isolation, and fedwiki's 8090 base in the port
probe.

Update stack docs and finalize status bookkeeping for all three
changes; archives the test-stack-integration-profiles change.
2026-08-01 04:14:05 -05:00

115 lines
4.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Tears down the test stack for the current worktree and returns it to a
# clean state: containers + volumes removed, test/.env, test/secrets/, and
# test/testdata/ deleted.
#
# Safe to run when nothing is up.
set -euo pipefail
REPO_ROOT="$(git rev-parse --show-toplevel)"
TEST_DIR="${REPO_ROOT}/test"
ENV_FILE="${TEST_DIR}/.env"
COMPOSE_FILE="${TEST_DIR}/compose.yaml"
# Compose only acts on services whose profiles are active, so a plain `down`
# silently leaves every profiled service (e.g. the discourse group) running
# along with its volumes. Enumerate the profiles the file declares rather than
# hardcoding them, so a profile added later is torn down without editing this.
env_file_args=()
[[ -f "$ENV_FILE" ]] && env_file_args=(--env-file "$ENV_FILE")
profile_args=()
while IFS= read -r profile; do
[[ -n "$profile" ]] && profile_args+=(--profile "$profile")
done < <(docker compose -f "$COMPOSE_FILE" "${env_file_args[@]}" config --profiles 2>/dev/null || true)
compose_down() {
docker compose -f "$COMPOSE_FILE" "$@" "${profile_args[@]}" \
down -v --remove-orphans || true
}
if [[ -f "$ENV_FILE" ]]; then
echo "Bringing down compose stack with volumes..."
compose_down "${env_file_args[@]}"
else
# A .env deleted by hand, or a teardown that died partway, leaves containers
# with nothing left to name their project. Recover the name from Docker's own
# compose labels instead of re-deriving the bootstrap slug — that also catches
# containers left by an earlier slug for this same directory.
echo "No test/.env found; searching for stale containers from ${TEST_DIR}..."
stale_projects=()
while IFS= read -r project; do
[[ -n "$project" ]] && stale_projects+=("$project")
done < <(docker ps -aq \
--filter "label=com.docker.compose.project.working_dir=${TEST_DIR}" 2>/dev/null |
xargs -r docker inspect \
--format '{{ index .Config.Labels "com.docker.compose.project" }}' 2>/dev/null |
sort -u)
if [[ ${#stale_projects[@]} -eq 0 ]]; then
echo " none found; skipping docker compose down."
fi
for project in "${stale_projects[@]}"; do
echo "Bringing down stale project '${project}' with volumes..."
compose_down --project-name "$project"
done
fi
remove_path() {
local path="$1"
[[ -e "$path" ]] || return 0
echo "Removing ${path}"
if rm -rf "$path" 2>/dev/null; then
return 0
fi
# Some testdata subdirs (postgres, temporal) are written by containers
# running as root and end up owned by root on the host. Fall back to a
# short-lived root container to delete them, avoiding a sudo prompt.
echo " (root-owned files; removing via docker)"
docker run --rm -v "${TEST_DIR}:/work" --workdir /work \
alpine:3 rm -rf "./$(basename "$path")"
}
# Determine whether this is the main worktree. Bootstrap copies test/secrets/
# from main into other worktrees, so deleting secrets/ on main wipes the
# source-of-truth. Refuse to do that.
MAIN_WT="$(git worktree list --porcelain | awk '/^worktree / { print $2; exit }')"
IS_MAIN=0
if [[ "$REPO_ROOT" == "$MAIN_WT" ]]; then
IS_MAIN=1
fi
for path in \
"${TEST_DIR}/.env" \
"${TEST_DIR}/secrets"; do
if [[ "$path" == "${TEST_DIR}/secrets" && "$IS_MAIN" -eq 1 ]]; then
echo "Keeping ${path} (main worktree — source-of-truth for other worktrees)"
continue
fi
remove_path "$path"
done
# Clean runtime data under testdata/ but preserve committed seed files
# (e.g. testdata/fedwiki/config.json, testdata/keycloak/master-realm.json).
# git clean -fdX removes only gitignored paths.
if [[ -d "${TEST_DIR}/testdata" ]]; then
echo "Cleaning gitignored runtime data under ${TEST_DIR}/testdata/"
if ! git -C "$REPO_ROOT" clean -fdX -- test/testdata/ 2>/dev/null; then
# Several services write their data dirs as root (postgres, temporal,
# caddy, discourse's postgres), so the host user cannot unlink them.
# Hand ownership back through a short-lived root container — generically,
# over the whole tree — rather than enumerating per-service paths, which
# is how gated services' data used to survive teardown. git clean then
# does the selective delete: it, not this container, knows which paths
# are runtime data and which are committed seed material.
echo " (root-owned files; reclaiming ownership via docker)"
docker run --rm -v "${TEST_DIR}:/work" --workdir /work alpine:3 \
chown -R "$(id -u):$(id -g)" testdata || true
git -C "$REPO_ROOT" clean -fdX -- test/testdata/ 2>/dev/null || true
fi
fi
echo "Teardown complete."