154 lines
4.6 KiB
Bash
Executable File
154 lines
4.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
umask 077
|
|
|
|
ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
|
|
BASE_ENV=${WNH_LOAD_BASE_ENV_FILE:-"$ROOT/output/runtime/load.env"}
|
|
LABEL=${1:-"cycle-$(date -u +%Y%m%dT%H%M%SZ)"}
|
|
MODE=${2:-load}
|
|
|
|
if [[ ! "$LABEL" =~ ^[A-Za-z0-9._-]+$ ]]; then
|
|
echo "Run label may contain only letters, numbers, dot, underscore, and dash." >&2
|
|
exit 1
|
|
fi
|
|
|
|
case "$MODE" in
|
|
load | resilience | both) ;;
|
|
*)
|
|
echo "Mode must be load, resilience, or both." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
WNH_LOAD_ENV_FILE="$BASE_ENV" "$ROOT/scripts/ensure-local-load-env.sh"
|
|
|
|
if [[ ! -f "$BASE_ENV" ]]; then
|
|
echo "Missing base load environment: $BASE_ENV." >&2
|
|
exit 1
|
|
fi
|
|
|
|
run_id="$(date -u +%Y%m%d%H%M%S)-$$"
|
|
safe_id=$(printf '%s' "$run_id" | tr -cd 'A-Za-z0-9')
|
|
project="who_need_help_load_$safe_id"
|
|
temporary_env=$(mktemp "${TMPDIR:-/tmp}/wnh-load-cycle.XXXXXX.env")
|
|
export WNH_LOAD_ENV_FILE=$temporary_env
|
|
export WNH_LOAD_TOOLS_IMAGE="who-need-help:load-tools-$safe_id"
|
|
|
|
cp "$BASE_ENV" "$temporary_env"
|
|
chmod 600 "$temporary_env"
|
|
|
|
set_env_value() {
|
|
local name=$1
|
|
local value=$2
|
|
local replacement="$name=$value"
|
|
|
|
if grep -q "^${name}=" "$temporary_env"; then
|
|
sed -i "s|^${name}=.*|$replacement|" "$temporary_env"
|
|
else
|
|
printf '%s\n' "$replacement" >>"$temporary_env"
|
|
fi
|
|
}
|
|
|
|
set_env_value LOAD_PROJECT "$project"
|
|
set_env_value COMPOSE_PROJECT_NAME "$project"
|
|
set_env_value TRAEFIK_PROJECT_CONSTRAINT "$project"
|
|
set_env_value TRAEFIK_DOCKER_NETWORK "${project}_ingress"
|
|
set_env_value APP_IMAGE "who-need-help:load-$safe_id"
|
|
set_env_value SOCKET_PROXY_IMAGE "who-need-help:socket-proxy-load-$safe_id"
|
|
set_env_value POSTGIS_IMAGE "who-need-help:postgis-load-$safe_id"
|
|
|
|
set -a
|
|
# shellcheck source=/dev/null
|
|
. "$temporary_env"
|
|
set +a
|
|
|
|
for name in APP_IMAGE SOCKET_PROXY_IMAGE POSTGIS_IMAGE; do
|
|
if [[ -z "${!name:-}" ]]; then
|
|
echo "$name is missing from the generated cycle environment." >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
if [[ -n "$(docker ps -aq --filter "label=com.docker.compose.project=$project")" ]] ||
|
|
[[ -n "$(docker network ls -q --filter "label=com.docker.compose.project=$project")" ]] ||
|
|
[[ -n "$(docker volume ls -q --filter "label=com.docker.compose.project=$project")" ]]; then
|
|
echo "Generated load-cycle project already owns Docker resources: $project." >&2
|
|
exit 1
|
|
fi
|
|
|
|
started=0
|
|
|
|
cleanup() {
|
|
local status=$?
|
|
local cleanup_status=0
|
|
trap - EXIT HUP INT TERM
|
|
|
|
if [[ "$started" -eq 1 ]] ||
|
|
[[ -n "$(docker ps -aq --filter "label=com.docker.compose.project=$project")" ]] ||
|
|
[[ -n "$(docker network ls -q --filter "label=com.docker.compose.project=$project")" ]]; then
|
|
"$ROOT/scripts/load-stack-stop.sh" >/dev/null 2>&1 || cleanup_status=1
|
|
fi
|
|
|
|
while IFS= read -r volume; do
|
|
[[ -n "$volume" ]] || continue
|
|
observed_project=$(docker volume inspect --format \
|
|
'{{index .Labels "com.docker.compose.project"}}' "$volume")
|
|
references=$(docker ps -aq --filter "volume=$volume")
|
|
|
|
if [[ "$observed_project" != "$project" ]] || [[ -n "$references" ]]; then
|
|
echo "Refusing unexpected or referenced load-cycle volume: $volume" >&2
|
|
cleanup_status=1
|
|
continue
|
|
fi
|
|
|
|
docker volume rm "$volume" >/dev/null || cleanup_status=1
|
|
done < <(docker volume ls -q --filter "label=com.docker.compose.project=$project")
|
|
|
|
for image in "$APP_IMAGE" "$SOCKET_PROXY_IMAGE" "$POSTGIS_IMAGE" \
|
|
"$WNH_LOAD_TOOLS_IMAGE"; do
|
|
if image_id=$(docker image inspect --format '{{.Id}}' "$image" 2>/dev/null); then
|
|
if [[ -n "$(docker ps -aq --filter "ancestor=$image_id")" ]]; then
|
|
echo "Refusing referenced load-cycle image: $image" >&2
|
|
cleanup_status=1
|
|
else
|
|
docker image rm "$image" >/dev/null || cleanup_status=1
|
|
fi
|
|
fi
|
|
done
|
|
|
|
rm -f "$temporary_env"
|
|
|
|
if [[ -n "$(docker ps -aq --filter "label=com.docker.compose.project=$project")" ]] ||
|
|
[[ -n "$(docker network ls -q --filter "label=com.docker.compose.project=$project")" ]] ||
|
|
[[ -n "$(docker volume ls -q --filter "label=com.docker.compose.project=$project")" ]]; then
|
|
echo "Load-cycle cleanup left project resources behind: $project." >&2
|
|
cleanup_status=1
|
|
fi
|
|
|
|
if [[ "$status" -eq 0 && "$cleanup_status" -ne 0 ]]; then
|
|
status=$cleanup_status
|
|
fi
|
|
|
|
exit "$status"
|
|
}
|
|
|
|
trap cleanup EXIT HUP INT TERM
|
|
|
|
"$ROOT/scripts/load-stack-up.sh"
|
|
started=1
|
|
|
|
case "$MODE" in
|
|
load)
|
|
"$ROOT/scripts/load-run.sh" "$LABEL"
|
|
;;
|
|
resilience)
|
|
"$ROOT/scripts/load-resilience-run.sh" "$LABEL"
|
|
;;
|
|
both)
|
|
"$ROOT/scripts/load-run.sh" "$LABEL-load"
|
|
"$ROOT/scripts/load-resilience-run.sh" "$LABEL-resilience"
|
|
;;
|
|
esac
|
|
|
|
echo "Load cycle completed; its project resources will now be removed: $project"
|