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.
115 lines
4.5 KiB
Bash
Executable File
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."
|