CI / verify (pull_request) Successful in 1m10s
CI / infrastructure-smoke (pull_request) Successful in 42s
CI / package-release (pull_request) Successful in 49s
CI / migration-smoke (pull_request) Successful in 2m21s
CI / image-build (pull_request) Successful in 59s
CI / deploy-smoke (same-origin) (pull_request) Successful in 52s
CI / deploy-smoke (split) (pull_request) Successful in 2m35s
CI / package-publication (pull_request) Successful in 1m3s
scripts/test-ci-sweep.sh proves the stale sweep by running `ci-sweep.sh --stale --max-age-seconds 0` on the runner's shared Docker daemon. With a zero threshold that removes every `ekklesia-ci-` stack on the daemon, not just the test's own: any job running at that moment loses its containers, networks, and volumes. It reported "CI sweep checks passed" while doing it. Core's infrastructure-smoke job runs the self-test on every run, and twice tore down Signage's integration stack mid-scenario (Zayin/Signage run 288: its first attempt, and the second one during connectivity-blip). `ci-sweep.sh --stale` now takes `--prefix P`, which narrows the sweep to stacks whose names begin with P and must itself begin with the CI prefix, so it can only narrow a sweep, never widen one. The self-test confines every stale sweep to its own fake run and proves that another run's stack survives even the zero threshold. The workflow's own sweeps are unchanged. Regression: a bystander stack from another run in the self-test, and a Docker-free guard that every stale sweep in the self-test is confined and the workflow's keep the default threshold. Co-Authored-By: Claude Opus 5.5 <[email protected]>
143 lines
6.6 KiB
Bash
Executable File
143 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Proves scripts/ci-sweep.sh against real Docker resources: what it removes, and above all what it
|
|
# leaves alone. CI runs it before trusting the sweep with a shared runner's daemon.
|
|
#
|
|
# bash scripts/test-ci-sweep.sh
|
|
#
|
|
# Every resource it creates is named and labeled for this run alone, under fake run identifiers, and
|
|
# every sweep it runs is confined to those identifiers, so it can never touch a real stack: the
|
|
# runner's daemon is shared, and another job's live stack may be on it right now. It removes all of
|
|
# its own resources on exit, whatever happens.
|
|
|
|
set -Eeuo pipefail
|
|
|
|
repository_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$repository_root"
|
|
|
|
image="$(sed -n 's/^[[:space:]]*image: \(postgres:[^[:space:]]*\)$/\1/p' deploy/compose.yaml | head -1)"
|
|
[[ -n "$image" ]] || {
|
|
echo "test-ci-sweep: no pinned PostgreSQL image in deploy/compose.yaml." >&2
|
|
exit 1
|
|
}
|
|
docker image inspect "$image" >/dev/null 2>&1 || docker pull --quiet "$image" >/dev/null
|
|
|
|
nonce="$(openssl rand -hex 4)"
|
|
compose_label="com.docker.compose.project"
|
|
ci_label="org.riverofglorymn.ekklesia.ci.project"
|
|
failures=0
|
|
created=()
|
|
|
|
check() {
|
|
if eval "$1"; then
|
|
echo "ok $2"
|
|
else
|
|
echo "FAIL $2"
|
|
failures=$((failures + 1))
|
|
fi
|
|
}
|
|
|
|
# project_exists <project>: any container, network, volume, or image still carries its label.
|
|
project_exists() {
|
|
local project="$1"
|
|
[[ -n "$(
|
|
docker ps --all --quiet --filter "label=${compose_label}=${project}"
|
|
docker ps --all --quiet --filter "label=${ci_label}=${project}"
|
|
docker network ls --quiet --filter "label=${compose_label}=${project}"
|
|
docker volume ls --quiet --filter "label=${compose_label}=${project}"
|
|
docker image ls --quiet --filter "label=${ci_label}=${project}"
|
|
)" ]]
|
|
}
|
|
|
|
staging_exists() {
|
|
docker run --rm --entrypoint test -v /tmp:/host-tmp "$image" -d "/host-tmp/$1"
|
|
}
|
|
|
|
# make_stack <project>: what a CI script leaves behind when it is killed: a Compose-labeled network,
|
|
# volume, and running container; a CI-labeled container and image; and a staging directory on the
|
|
# Docker host.
|
|
make_stack() {
|
|
local project="$1"
|
|
created+=("$project")
|
|
docker network create --label "${compose_label}=${project}" "${project}_default" >/dev/null
|
|
docker volume create --label "${compose_label}=${project}" "${project}_data" >/dev/null
|
|
docker run --detach --name "${project}-db" --label "${compose_label}=${project}" \
|
|
--network "${project}_default" -v "${project}_data:/data" --entrypoint sleep "$image" 600 >/dev/null
|
|
docker run --detach --name "${project}-browser" --label "${ci_label}=${project}" \
|
|
--entrypoint sleep "$image" 600 >/dev/null
|
|
printf 'FROM %s\nLABEL %s=%s %s=%s\n' "$image" "$ci_label" "$project" \
|
|
"org.riverofglorymn.ekklesia.ci.created" "$(date +%s)" |
|
|
docker build --quiet --tag "${project}-image:test" - >/dev/null
|
|
docker run --rm --entrypoint mkdir -v /tmp:/host-tmp "$image" -p "/host-tmp/${project}"
|
|
}
|
|
|
|
job_container="test-ci-sweep-job-${nonce}"
|
|
cleanup() {
|
|
local status=$?
|
|
trap - EXIT INT TERM
|
|
docker rm --force "$job_container" >/dev/null 2>&1 || true
|
|
for project in "${created[@]}"; do
|
|
docker ps --all --quiet --filter "label=${compose_label}=${project}" | xargs -r docker rm --force >/dev/null 2>&1 || true
|
|
docker ps --all --quiet --filter "label=${ci_label}=${project}" | xargs -r docker rm --force >/dev/null 2>&1 || true
|
|
docker network rm "${project}_default" >/dev/null 2>&1 || true
|
|
docker volume rm --force "${project}_data" >/dev/null 2>&1 || true
|
|
docker image rm --force "${project}-image:test" >/dev/null 2>&1 || true
|
|
docker run --rm --entrypoint rm -v /tmp:/host-tmp "$image" -rf "/host-tmp/${project}" >/dev/null 2>&1 || true
|
|
done
|
|
exit "$status"
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
# shellcheck source=scripts/lib/ci-project.sh
|
|
source "$repository_root/scripts/lib/ci-project.sh"
|
|
|
|
# Fake run identifiers: nothing here can match a real run's stacks.
|
|
export GITHUB_RUN_ID="sweeptest${nonce}"
|
|
export GITHUB_RUN_ATTEMPT="1"
|
|
# Every stack this test sweeps begins with this; nothing it sweeps reaches past it.
|
|
own_run="$(ci_name_safe "ekklesia-ci-${GITHUB_RUN_ID}-")"
|
|
mine="$(EKKLESIA_CI_JOB_KEY=alpha ci_project_name mine)"
|
|
sibling="$(EKKLESIA_CI_JOB_KEY=beta ci_project_name sibling)"
|
|
foreign="ekklesia-sweep-control-${nonce}"
|
|
# Stands in for another run's live stack on the shared daemon — a module's integration job, say.
|
|
bystander="$(GITHUB_RUN_ID="bystander${nonce}" EKKLESIA_CI_JOB_KEY=gamma ci_project_name live)"
|
|
for project in "$mine" "$sibling" "$foreign" "$bystander"; do
|
|
make_stack "$project"
|
|
done
|
|
# A job container connected to this job's stack network, exactly as compose-job-network.sh does.
|
|
docker run --detach --name "$job_container" --entrypoint sleep "$image" 600 >/dev/null
|
|
docker network connect "${mine}_default" "$job_container"
|
|
|
|
echo "--- --job removes this job's stacks, at any age, and nothing else"
|
|
EKKLESIA_CI_JOB_KEY=alpha bash scripts/ci-sweep.sh --job
|
|
check '! project_exists "$mine"' "this job's containers, network, volume, and image are gone"
|
|
check '! staging_exists "$mine"' "this job's staging directory on the Docker host is gone"
|
|
check 'project_exists "$sibling"' "another job's stack in the same run is left alone"
|
|
check 'project_exists "$foreign"' "a stack outside the CI prefix is left alone"
|
|
check 'docker inspect "$job_container" >/dev/null 2>&1' \
|
|
"a job container connected to the stack's network is disconnected, never removed"
|
|
|
|
echo "--- --job refuses to run outside CI, where it could remove a developer's own stacks"
|
|
check '! env -u GITHUB_RUN_ID bash scripts/ci-sweep.sh --job >/dev/null 2>&1' \
|
|
"--job without GITHUB_RUN_ID fails"
|
|
|
|
echo "--- --stale removes only CI stacks older than its threshold"
|
|
bash scripts/ci-sweep.sh --stale --prefix "$own_run"
|
|
check 'project_exists "$sibling"' "a young CI stack survives the default threshold"
|
|
bash scripts/ci-sweep.sh --stale --max-age-seconds 0 --prefix "$own_run"
|
|
check '! project_exists "$sibling"' "a CI stack older than the threshold is removed"
|
|
check '! staging_exists "$sibling"' "its staging directory too"
|
|
check 'project_exists "$foreign"' "a stack outside the CI prefix is never removed, at any age"
|
|
check 'project_exists "$bystander"' \
|
|
"another run's stack is never removed by a narrowed sweep, even past the threshold"
|
|
echo "--- --prefix only ever narrows the sweep"
|
|
check '! bash scripts/ci-sweep.sh --stale --prefix "ekklesia-" >/dev/null 2>&1' \
|
|
"a prefix outside the CI prefix is refused"
|
|
check '! bash scripts/ci-sweep.sh --job --prefix "$own_run" >/dev/null 2>&1' \
|
|
"--prefix with --job is refused"
|
|
|
|
if ((failures > 0)); then
|
|
echo "test-ci-sweep: ${failures} check(s) failed." >&2
|
|
exit 1
|
|
fi
|
|
echo "CI sweep checks passed."
|