606 lines
19 KiB
Bash
Executable File
606 lines
19 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
|
AUTO_DEPLOY="$ROOT_DIR/deploy/scripts/auto-deploy.sh"
|
|
TEST_ROOT="$(mktemp -d)"
|
|
trap 'rm -rf "$TEST_ROOT"' EXIT
|
|
|
|
REVISION="1111111111111111111111111111111111111111"
|
|
PREVIOUS_REVISION="2222222222222222222222222222222222222222"
|
|
APP_DIGEST="$(printf 'a%.0s' {1..64})"
|
|
ROUTE_DIGEST="$(printf 'b%.0s' {1..64})"
|
|
PREVIOUS_APP_DIGEST="$(printf 'c%.0s' {1..64})"
|
|
PREVIOUS_ROUTE_DIGEST="$(printf 'd%.0s' {1..64})"
|
|
APP_IMAGE="registry.example/team/watermaps@sha256:$APP_DIGEST"
|
|
ROUTE_IMAGE="registry.example/team/watermaps-route-data@sha256:$ROUTE_DIGEST"
|
|
PREVIOUS_APP_IMAGE="registry.example/team/watermaps@sha256:$PREVIOUS_APP_DIGEST"
|
|
PREVIOUS_ROUTE_IMAGE="registry.example/team/watermaps-route-data@sha256:$PREVIOUS_ROUTE_DIGEST"
|
|
RELEASE_POINTER="registry.example/team/watermaps-release:main"
|
|
RELEASE_IMAGE="registry.example/team/watermaps-release:$REVISION"
|
|
FAKE_BIN="$TEST_ROOT/bin"
|
|
mkdir -p "$FAKE_BIN"
|
|
|
|
fail() {
|
|
printf 'Fehler: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
assert_contains() {
|
|
local expected="$1"
|
|
local actual="$2"
|
|
grep -Fq -- "$expected" <<<"$actual" ||
|
|
fail "Erwarteter Text fehlt: $expected"
|
|
}
|
|
|
|
assert_file_contains() {
|
|
local expected="$1"
|
|
local file="$2"
|
|
grep -Fq -- "$expected" "$file" ||
|
|
fail "$file enthält nicht: $expected"
|
|
}
|
|
|
|
assert_file_not_contains() {
|
|
local unexpected="$1"
|
|
local file="$2"
|
|
if grep -Fq -- "$unexpected" "$file"; then
|
|
fail "$file enthält unerwartet: $unexpected"
|
|
fi
|
|
}
|
|
|
|
assert_no_temporary_artifacts() {
|
|
local artifacts=()
|
|
|
|
shopt -s nullglob
|
|
artifacts+=(
|
|
"$CASE_DATA/releases"/.candidate-*
|
|
"$CASE_RUNTIME"/.auto-deploy-images.*
|
|
"$CASE_INSTALL"/.current.*.new
|
|
)
|
|
shopt -u nullglob
|
|
|
|
if (( ${#artifacts[@]} > 0 )); then
|
|
printf 'Temporäre Artefakte wurden nicht aufgeräumt:\n' >&2
|
|
printf ' %s\n' "${artifacts[@]}" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
write_images_file() {
|
|
local destination="$1"
|
|
local revision="$2"
|
|
local app_image="$3"
|
|
local route_image="$4"
|
|
|
|
install -d -m 0755 "$(dirname "$destination")"
|
|
{
|
|
printf 'WATERMAPS_DEPLOY_REVISION=%s\n' "$revision"
|
|
printf 'WATERMAPS_APP_IMAGE=%s\n' "$app_image"
|
|
printf 'WATERMAPS_ROUTE_DATA_IMAGE=%s\n' "$route_image"
|
|
} >"$destination"
|
|
chmod 0600 "$destination"
|
|
}
|
|
|
|
cat >"$FAKE_BIN/fake-populate-release" <<'SH'
|
|
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
|
|
destination="$1"
|
|
revision="$2"
|
|
|
|
mkdir -p \
|
|
"$destination/deploy/nginx" \
|
|
"$destination/deploy/scripts" \
|
|
"$destination/deploy/systemd" \
|
|
"$destination/database/migrations"
|
|
|
|
for file in \
|
|
deploy/compose.production.yml \
|
|
deploy/nginx/bootstrap.conf \
|
|
deploy/nginx/https.conf.template \
|
|
deploy/scripts/common.sh \
|
|
deploy/scripts/auto-deploy.sh \
|
|
deploy/scripts/auto-deploy-entrypoint.sh \
|
|
deploy/scripts/bootstrap-server.sh \
|
|
deploy/scripts/renew-certificate.sh \
|
|
deploy/scripts/rollback.sh \
|
|
deploy/scripts/update-route-data.sh \
|
|
deploy/systemd/watermaps-auto-deploy.service \
|
|
deploy/systemd/watermaps-auto-deploy.timer \
|
|
deploy/systemd/watermaps-certbot-renew.service \
|
|
deploy/systemd/watermaps-certbot-renew.timer \
|
|
deploy/systemd/watermaps-route-update.service \
|
|
deploy/systemd/watermaps-route-update.timer \
|
|
database/schema.sql \
|
|
database/migrations/0001_initial.sql; do
|
|
printf 'Testinhalt für %s\n' "$file" >"$destination/$file"
|
|
done
|
|
|
|
cat >"$destination/deploy/scripts/deploy.sh" <<'DEPLOY'
|
|
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
|
|
[[ "${1:-}" == "--images-file" && -n "${2:-}" ]] ||
|
|
exit 97
|
|
images_file="$2"
|
|
revision="$(
|
|
awk -F= '$1 == "WATERMAPS_DEPLOY_REVISION" { print substr($0, length($1) + 2) }' \
|
|
"$images_file"
|
|
)"
|
|
app_image="$(
|
|
awk -F= '$1 == "WATERMAPS_APP_IMAGE" { print substr($0, length($1) + 2) }' \
|
|
"$images_file"
|
|
)"
|
|
route_image="$(
|
|
awk -F= '$1 == "WATERMAPS_ROUTE_DATA_IMAGE" { print substr($0, length($1) + 2) }' \
|
|
"$images_file"
|
|
)"
|
|
|
|
printf '%s|%s|%s|%s|%s|%s\n' \
|
|
"$0" \
|
|
"$revision" \
|
|
"${WATERMAPS_DISABLE_INTERNAL_ROLLBACK:-}" \
|
|
"$images_file" \
|
|
"$app_image" \
|
|
"$route_image" \
|
|
>>"$FAKE_DEPLOY_LOG"
|
|
|
|
if [[ "$revision" == "$FAKE_REVISION" ]]; then
|
|
[[ "$app_image" == "$FAKE_APP_IMAGE" ]] || exit 98
|
|
[[ "$route_image" == "$FAKE_ROUTE_IMAGE" ]] || exit 99
|
|
if [[ "${FAKE_DEPLOY_STATUS:-0}" -ne 0 ]]; then
|
|
exit "$FAKE_DEPLOY_STATUS"
|
|
fi
|
|
fi
|
|
|
|
cp "$images_file" "$WATERMAPS_ACTIVE_IMAGES_FILE"
|
|
DEPLOY
|
|
chmod 0755 "$destination/deploy/scripts/"*.sh
|
|
|
|
{
|
|
printf 'format_version=1\n'
|
|
printf 'revision=%s\n' "$revision"
|
|
printf 'source=https://registry.example/team/watermaps\n'
|
|
printf 'app_image=%s\n' "$FAKE_APP_IMAGE"
|
|
printf 'route_data_image=%s\n' "$FAKE_ROUTE_IMAGE"
|
|
} >"$destination/release.env"
|
|
|
|
(
|
|
cd "$destination"
|
|
{
|
|
sha256sum release.env
|
|
find deploy database -type f -print |
|
|
LC_ALL=C sort |
|
|
xargs sha256sum
|
|
} >SHA256SUMS
|
|
)
|
|
|
|
if [[ "${FAKE_CORRUPT_BUNDLE:-0}" == "1" ]]; then
|
|
printf 'nachträgliche Manipulation\n' >>"$destination/database/schema.sql"
|
|
fi
|
|
SH
|
|
|
|
cat >"$FAKE_BIN/id" <<'SH'
|
|
#!/usr/bin/env bash
|
|
if [[ "${1:-}" == "-u" ]]; then
|
|
printf '0\n'
|
|
else
|
|
exec /usr/bin/id "$@"
|
|
fi
|
|
SH
|
|
|
|
cat >"$FAKE_BIN/mountpoint" <<'SH'
|
|
#!/usr/bin/env bash
|
|
exit 0
|
|
SH
|
|
|
|
cat >"$FAKE_BIN/curl" <<'SH'
|
|
#!/usr/bin/env bash
|
|
printf 'curl|%s\n' "$*" >>"$FAKE_CURL_LOG"
|
|
printf '{"commit":{"id":"%s"}}\n' "$FAKE_MAIN_REVISION"
|
|
SH
|
|
|
|
cat >"$FAKE_BIN/jq" <<'SH'
|
|
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
input="$(tr -d '\r\n')"
|
|
revision="${input#*\"id\":\"}"
|
|
revision="${revision%%\"*}"
|
|
[[ "$revision" =~ ^[0-9a-f]{40}$ ]] || exit 1
|
|
printf '%s\n' "$revision"
|
|
SH
|
|
|
|
cat >"$FAKE_BIN/docker" <<'SH'
|
|
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
|
|
printf 'docker|%s\n' "$*" >>"$FAKE_DOCKER_LOG"
|
|
command_name="${1:-}"
|
|
shift || true
|
|
|
|
case "$command_name" in
|
|
pull)
|
|
[[ -n "${1:-}" ]]
|
|
;;
|
|
image)
|
|
[[ "${1:-}" == "inspect" ]] || exit 91
|
|
shift
|
|
reference="${!#}"
|
|
if [[ "$*" == *'org.opencontainers.image.revision'* ]]; then
|
|
if [[ "$reference" == "$FAKE_RELEASE_POINTER" ]]; then
|
|
printf '%s\n' "$FAKE_POINTER_REVISION"
|
|
else
|
|
printf '%s\n' "$FAKE_REVISION"
|
|
fi
|
|
elif [[ "$*" == *'{{.Id}}'* ]]; then
|
|
if [[ "$reference" == "$FAKE_RELEASE_POINTER" ]]; then
|
|
printf '%s\n' "$FAKE_POINTER_ID"
|
|
else
|
|
printf '%s\n' "$FAKE_IMMUTABLE_ID"
|
|
fi
|
|
elif [[ "$*" == *'.RepoDigests'* ]]; then
|
|
[[ "$reference" == *@sha256:* ]] || exit 92
|
|
printf '%s\n' "$reference"
|
|
else
|
|
exit 93
|
|
fi
|
|
;;
|
|
create)
|
|
printf 'fake-release-container\n'
|
|
;;
|
|
cp)
|
|
[[ "${1:-}" == "fake-release-container:/release/." ]] || exit 94
|
|
destination="${2%/}"
|
|
fake-populate-release "$destination" "$FAKE_REVISION"
|
|
;;
|
|
container)
|
|
[[ "${1:-}" == "rm" ]] || exit 95
|
|
;;
|
|
*)
|
|
printf 'Unerwarteter Docker-Aufruf: %s %s\n' "$command_name" "$*" >&2
|
|
exit 96
|
|
;;
|
|
esac
|
|
SH
|
|
|
|
cat >"$FAKE_BIN/install" <<'SH'
|
|
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
|
|
if [[ "${1:-}" == "-d" ]]; then
|
|
exec /usr/bin/install "$@"
|
|
fi
|
|
|
|
destination="${!#}"
|
|
if [[ "$destination" == "/usr/local/sbin/watermaps-auto-deploy" ]]; then
|
|
source_file="${@: -2:1}"
|
|
[[ -x "$source_file" ]]
|
|
printf '%s\n' "watermaps-auto-deploy-entrypoint" >>"$FAKE_INSTALL_LOG"
|
|
exit 0
|
|
fi
|
|
if [[ "$destination" == "/etc/systemd/system/" ]]; then
|
|
source_file="${@: -2:1}"
|
|
[[ -f "$source_file" ]]
|
|
printf '%s\n' "$(basename "$source_file")" >>"$FAKE_INSTALL_LOG"
|
|
exit 0
|
|
fi
|
|
|
|
exec /usr/bin/install "$@"
|
|
SH
|
|
|
|
cat >"$FAKE_BIN/systemctl" <<'SH'
|
|
#!/usr/bin/env bash
|
|
printf '%s\n' "$*" >>"$FAKE_SYSTEMCTL_LOG"
|
|
SH
|
|
|
|
chmod 0755 "$FAKE_BIN/"*
|
|
export PATH="$FAKE_BIN:/usr/bin:/bin"
|
|
|
|
new_case() {
|
|
local name="$1"
|
|
|
|
CASE_DIR="$TEST_ROOT/$name"
|
|
CASE_DATA="$CASE_DIR/data"
|
|
CASE_RUNTIME="$CASE_DIR/runtime"
|
|
CASE_INSTALL="$CASE_DIR/install"
|
|
CASE_ENV="$CASE_DIR/production.env"
|
|
mkdir -p \
|
|
"$CASE_DATA" \
|
|
"$CASE_RUNTIME" \
|
|
"$CASE_INSTALL/deploy"
|
|
|
|
FAKE_DOCKER_LOG="$CASE_DIR/docker.log"
|
|
FAKE_DEPLOY_LOG="$CASE_DIR/deploy.log"
|
|
FAKE_SYSTEMCTL_LOG="$CASE_DIR/systemctl.log"
|
|
FAKE_INSTALL_LOG="$CASE_DIR/install.log"
|
|
FAKE_CURL_LOG="$CASE_DIR/curl.log"
|
|
: >"$FAKE_DOCKER_LOG"
|
|
: >"$FAKE_DEPLOY_LOG"
|
|
: >"$FAKE_SYSTEMCTL_LOG"
|
|
: >"$FAKE_INSTALL_LOG"
|
|
: >"$FAKE_CURL_LOG"
|
|
|
|
cat >"$CASE_ENV" <<EOF
|
|
WATERMAPS_DATA_DIR=$CASE_DATA
|
|
WATERMAPS_RUNTIME_DIR=$CASE_RUNTIME
|
|
WATERMAPS_POSTGRES_PASSWORD=0123456789abcdef0123456789abcdef
|
|
WATERMAPS_REGISTRY=registry.example
|
|
WATERMAPS_REGISTRY_OWNER=team
|
|
WATERMAPS_GITEA_REPOSITORY=team/watermaps
|
|
EOF
|
|
|
|
export CASE_DIR CASE_DATA CASE_RUNTIME CASE_INSTALL
|
|
export FAKE_DOCKER_LOG FAKE_DEPLOY_LOG FAKE_SYSTEMCTL_LOG
|
|
export FAKE_INSTALL_LOG FAKE_CURL_LOG
|
|
export FAKE_REVISION="$REVISION"
|
|
export FAKE_MAIN_REVISION="$REVISION"
|
|
export FAKE_POINTER_REVISION="$REVISION"
|
|
export FAKE_POINTER_ID="sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
|
|
export FAKE_IMMUTABLE_ID="$FAKE_POINTER_ID"
|
|
export FAKE_RELEASE_POINTER="$RELEASE_POINTER"
|
|
export FAKE_APP_IMAGE="$APP_IMAGE"
|
|
export FAKE_ROUTE_IMAGE="$ROUTE_IMAGE"
|
|
export FAKE_DEPLOY_STATUS=0
|
|
export FAKE_CORRUPT_BUNDLE=0
|
|
export WATERMAPS_ENV_FILE="$CASE_ENV"
|
|
export WATERMAPS_INSTALL_DIR="$CASE_INSTALL"
|
|
}
|
|
|
|
populate_release() {
|
|
local revision="$1"
|
|
local destination="$CASE_DATA/releases/$revision"
|
|
|
|
mkdir -p "$destination"
|
|
fake-populate-release "$destination" "$revision"
|
|
}
|
|
|
|
run_auto_deploy() {
|
|
set +e
|
|
RUN_OUTPUT="$("$AUTO_DEPLOY" 2>&1)"
|
|
RUN_STATUS=$?
|
|
set -e
|
|
}
|
|
|
|
assert_systemd_installed() {
|
|
local expected_units=(
|
|
watermaps-auto-deploy.service
|
|
watermaps-auto-deploy.timer
|
|
watermaps-route-update.service
|
|
watermaps-route-update.timer
|
|
watermaps-certbot-renew.service
|
|
watermaps-certbot-renew.timer
|
|
)
|
|
local unit
|
|
|
|
[[ "$(wc -l <"$FAKE_INSTALL_LOG")" -eq "$((${#expected_units[@]} + 1))" ]] ||
|
|
fail "Es wurden nicht exakt sechs Systemd-Units und der stabile Einstiegspunkt installiert."
|
|
grep -Fxq "watermaps-auto-deploy-entrypoint" "$FAKE_INSTALL_LOG" ||
|
|
fail "Stabiler Auto-Deploy-Einstiegspunkt wurde nicht installiert."
|
|
for unit in "${expected_units[@]}"; do
|
|
grep -Fxq "$unit" "$FAKE_INSTALL_LOG" ||
|
|
fail "Systemd-Unit wurde nicht installiert: $unit"
|
|
done
|
|
grep -Fxq 'daemon-reload' "$FAKE_SYSTEMCTL_LOG" ||
|
|
fail "systemctl daemon-reload fehlt."
|
|
assert_file_contains \
|
|
'enable --now watermaps-auto-deploy.timer watermaps-route-update.timer watermaps-certbot-renew.timer' \
|
|
"$FAKE_SYSTEMCTL_LOG"
|
|
}
|
|
|
|
test_already_active_repairs_current_link() {
|
|
new_case already-active
|
|
mkdir -p "$CASE_DATA/releases"
|
|
populate_release "$REVISION"
|
|
write_images_file \
|
|
"$CASE_INSTALL/deploy/.env.images" \
|
|
"$REVISION" \
|
|
"$APP_IMAGE" \
|
|
"$ROUTE_IMAGE"
|
|
mkdir -p "$CASE_DIR/wrong-release"
|
|
ln -s "$CASE_DIR/wrong-release" "$CASE_INSTALL/current"
|
|
|
|
run_auto_deploy
|
|
|
|
[[ "$RUN_STATUS" -eq 0 ]] || fail "Bereits aktives Release schlug fehl: $RUN_OUTPUT"
|
|
assert_contains "Der atomare Current-Link für Commit $REVISION wurde repariert." "$RUN_OUTPUT"
|
|
assert_contains "Commit $REVISION ist bereits aktiv." "$RUN_OUTPUT"
|
|
[[ "$(readlink --canonicalize "$CASE_INSTALL/current")" == \
|
|
"$(readlink --canonicalize "$CASE_DATA/releases/$REVISION")" ]] ||
|
|
fail "Current-Link wurde für das aktive Release nicht repariert."
|
|
[[ ! -s "$FAKE_DEPLOY_LOG" ]] ||
|
|
fail "Ein bereits aktives Release darf deploy.sh nicht erneut ausführen."
|
|
assert_file_not_contains 'docker|create ' "$FAKE_DOCKER_LOG"
|
|
assert_file_not_contains 'docker|cp ' "$FAKE_DOCKER_LOG"
|
|
assert_systemd_installed
|
|
assert_no_temporary_artifacts
|
|
}
|
|
|
|
test_successful_release_uses_manifest_digests() {
|
|
new_case successful-release
|
|
|
|
run_auto_deploy
|
|
|
|
[[ "$RUN_STATUS" -eq 0 ]] || fail "Neues Release schlug fehl: $RUN_OUTPUT"
|
|
assert_contains "Automatisches Deployment von Commit $REVISION abgeschlossen." "$RUN_OUTPUT"
|
|
[[ -f "$CASE_DATA/releases/$REVISION/SHA256SUMS" ]] ||
|
|
fail "Validiertes Release-Bundle wurde nicht atomar installiert."
|
|
(
|
|
cd "$CASE_DATA/releases/$REVISION"
|
|
sha256sum --check --strict SHA256SUMS >/dev/null
|
|
) || fail "Installiertes Release-Bundle besteht seine SHA256-Prüfung nicht."
|
|
[[ "$(readlink --canonicalize "$CASE_INSTALL/current")" == \
|
|
"$(readlink --canonicalize "$CASE_DATA/releases/$REVISION")" ]] ||
|
|
fail "Current-Link zeigt nicht auf das neue Release."
|
|
|
|
[[ "$(wc -l <"$FAKE_DEPLOY_LOG")" -eq 1 ]] ||
|
|
fail "Das neue Release muss deploy.sh exakt einmal ausführen."
|
|
IFS='|' read -r invoked_script deployed_revision rollback_disabled \
|
|
images_file deployed_app deployed_route <"$FAKE_DEPLOY_LOG"
|
|
[[ "$invoked_script" == \
|
|
"$CASE_DATA/releases/$REVISION/deploy/scripts/deploy.sh" ]] ||
|
|
fail "deploy.sh wurde nicht aus dem unveränderlichen Release-Bundle ausgeführt."
|
|
[[ "$deployed_revision" == "$REVISION" ]] ||
|
|
fail "deploy.sh erhielt die falsche Revision."
|
|
[[ "$rollback_disabled" == "true" ]] ||
|
|
fail "Der interne Rollback muss beim orchestrierten Deployment deaktiviert sein."
|
|
[[ "$images_file" == "$CASE_RUNTIME"/.auto-deploy-images.* ]] ||
|
|
fail "deploy.sh erhielt keine temporäre, validierte Image-Datei."
|
|
[[ "$deployed_app" == "$APP_IMAGE" && "$deployed_route" == "$ROUTE_IMAGE" ]] ||
|
|
fail "deploy.sh erhielt nicht die im Release-Manifest gebundenen Digests."
|
|
[[ ! -e "$images_file" ]] ||
|
|
fail "Temporäre Image-Datei blieb nach dem Deployment bestehen."
|
|
|
|
assert_file_contains "docker|pull $APP_IMAGE" "$FAKE_DOCKER_LOG"
|
|
assert_file_contains "docker|pull $ROUTE_IMAGE" "$FAKE_DOCKER_LOG"
|
|
assert_file_not_contains "docker|pull registry.example/team/watermaps:$REVISION" \
|
|
"$FAKE_DOCKER_LOG"
|
|
assert_file_contains "WATERMAPS_DEPLOY_REVISION=$REVISION" \
|
|
"$CASE_INSTALL/deploy/.env.images"
|
|
assert_file_contains "WATERMAPS_APP_IMAGE=$APP_IMAGE" \
|
|
"$CASE_INSTALL/deploy/.env.images"
|
|
assert_file_contains "WATERMAPS_ROUTE_DATA_IMAGE=$ROUTE_IMAGE" \
|
|
"$CASE_INSTALL/deploy/.env.images"
|
|
assert_systemd_installed
|
|
assert_no_temporary_artifacts
|
|
}
|
|
|
|
test_pointer_main_mismatch_does_not_deploy() {
|
|
new_case pointer-main-mismatch
|
|
export FAKE_MAIN_REVISION="$PREVIOUS_REVISION"
|
|
|
|
run_auto_deploy
|
|
|
|
[[ "$RUN_STATUS" -eq 0 ]] ||
|
|
fail "Pointer/main-Abweichung soll sauber warten: $RUN_OUTPUT"
|
|
assert_contains \
|
|
"Release $REVISION ist nicht der aktuelle main-Commit $PREVIOUS_REVISION" \
|
|
"$RUN_OUTPUT"
|
|
[[ ! -e "$CASE_DATA/releases/$REVISION" ]] ||
|
|
fail "Bei Pointer/main-Abweichung darf kein Bundle extrahiert werden."
|
|
[[ ! -L "$CASE_INSTALL/current" ]] ||
|
|
fail "Bei Pointer/main-Abweichung darf Current nicht verändert werden."
|
|
[[ ! -s "$FAKE_DEPLOY_LOG" ]] ||
|
|
fail "Bei Pointer/main-Abweichung darf deploy.sh nicht laufen."
|
|
[[ ! -s "$FAKE_SYSTEMCTL_LOG" && ! -s "$FAKE_INSTALL_LOG" ]] ||
|
|
fail "Bei Pointer/main-Abweichung darf Systemd nicht verändert werden."
|
|
assert_file_contains "docker|pull $RELEASE_POINTER" "$FAKE_DOCKER_LOG"
|
|
assert_file_not_contains "docker|pull $RELEASE_IMAGE" "$FAKE_DOCKER_LOG"
|
|
assert_file_not_contains 'docker|create ' "$FAKE_DOCKER_LOG"
|
|
assert_no_temporary_artifacts
|
|
}
|
|
|
|
test_failed_release_restores_previous_immutable_release() {
|
|
new_case failed-release
|
|
mkdir -p "$CASE_DATA/releases" "$CASE_RUNTIME/deployments"
|
|
populate_release "$PREVIOUS_REVISION"
|
|
write_images_file \
|
|
"$CASE_INSTALL/deploy/.env.images" \
|
|
"$PREVIOUS_REVISION" \
|
|
"$PREVIOUS_APP_IMAGE" \
|
|
"$PREVIOUS_ROUTE_IMAGE"
|
|
cp \
|
|
"$CASE_INSTALL/deploy/.env.images" \
|
|
"$CASE_RUNTIME/deployments/$PREVIOUS_REVISION.env"
|
|
ln -s "$CASE_DATA/releases/$PREVIOUS_REVISION" "$CASE_INSTALL/current"
|
|
export FAKE_DEPLOY_STATUS=42
|
|
|
|
run_auto_deploy
|
|
|
|
[[ "$RUN_STATUS" -eq 42 ]] ||
|
|
fail "Fehlgeschlagenes Release muss seinen Status weitergeben: $RUN_OUTPUT"
|
|
assert_contains \
|
|
"Das vorherige Release $PREVIOUS_REVISION wird aus seinem unveränderlichen Bundle wiederhergestellt." \
|
|
"$RUN_OUTPUT"
|
|
assert_contains \
|
|
"Vorheriges Release $PREVIOUS_REVISION wurde erneut geprüft und ist aktiv." \
|
|
"$RUN_OUTPUT"
|
|
[[ "$(wc -l <"$FAKE_DEPLOY_LOG")" -eq 2 ]] ||
|
|
fail "Nach einem Fehler müssen Kandidat und vorheriges Release genau einmal laufen."
|
|
|
|
candidate_log="$(sed -n '1p' "$FAKE_DEPLOY_LOG")"
|
|
previous_log="$(sed -n '2p' "$FAKE_DEPLOY_LOG")"
|
|
assert_contains \
|
|
"$CASE_DATA/releases/$REVISION/deploy/scripts/deploy.sh|$REVISION|true|" \
|
|
"$candidate_log"
|
|
assert_contains "|$APP_IMAGE|$ROUTE_IMAGE" "$candidate_log"
|
|
assert_contains \
|
|
"$CASE_DATA/releases/$PREVIOUS_REVISION/deploy/scripts/deploy.sh|$PREVIOUS_REVISION|true|$CASE_RUNTIME/deployments/$PREVIOUS_REVISION.env|" \
|
|
"$previous_log"
|
|
assert_contains "|$PREVIOUS_APP_IMAGE|$PREVIOUS_ROUTE_IMAGE" "$previous_log"
|
|
|
|
[[ "$(readlink --canonicalize "$CASE_INSTALL/current")" == \
|
|
"$(readlink --canonicalize "$CASE_DATA/releases/$PREVIOUS_REVISION")" ]] ||
|
|
fail "Current-Link wurde nach dem Fehler nicht auf das vorherige Release zurückgesetzt."
|
|
assert_file_contains "WATERMAPS_DEPLOY_REVISION=$PREVIOUS_REVISION" \
|
|
"$CASE_INSTALL/deploy/.env.images"
|
|
[[ ! -s "$FAKE_SYSTEMCTL_LOG" && ! -s "$FAKE_INSTALL_LOG" ]] ||
|
|
fail "Ein fehlgeschlagener Kandidat darf keine neuen Systemd-Units aktivieren."
|
|
assert_no_temporary_artifacts
|
|
}
|
|
|
|
test_invalid_bundle_is_rejected_and_cleaned() {
|
|
new_case corrupt-release
|
|
export FAKE_CORRUPT_BUNDLE=1
|
|
|
|
run_auto_deploy
|
|
|
|
[[ "$RUN_STATUS" -ne 0 ]] ||
|
|
fail "Ein manipuliertes Release-Bundle wurde akzeptiert."
|
|
assert_contains "Release-Bundle hat die Integritätsprüfung nicht bestanden." "$RUN_OUTPUT"
|
|
[[ ! -e "$CASE_DATA/releases/$REVISION" ]] ||
|
|
fail "Manipuliertes Bundle wurde in den Release-Store verschoben."
|
|
[[ ! -s "$FAKE_DEPLOY_LOG" ]] ||
|
|
fail "Manipuliertes Bundle darf deploy.sh nicht ausführen."
|
|
[[ ! -s "$FAKE_SYSTEMCTL_LOG" && ! -s "$FAKE_INSTALL_LOG" ]] ||
|
|
fail "Manipuliertes Bundle darf Systemd nicht verändern."
|
|
assert_file_contains 'docker|container rm fake-release-container' "$FAKE_DOCKER_LOG"
|
|
assert_no_temporary_artifacts
|
|
}
|
|
|
|
test_stable_entrypoint_bootstraps_and_then_uses_current() {
|
|
local entrypoint_root="$TEST_ROOT/stable-entrypoint"
|
|
local output
|
|
|
|
mkdir -p "$entrypoint_root/deploy/scripts"
|
|
cat >"$entrypoint_root/deploy/scripts/auto-deploy.sh" <<'SH'
|
|
#!/usr/bin/env bash
|
|
printf 'bootstrap\n'
|
|
SH
|
|
chmod 0755 "$entrypoint_root/deploy/scripts/auto-deploy.sh"
|
|
|
|
output="$(
|
|
WATERMAPS_INSTALL_DIR="$entrypoint_root" \
|
|
"$ROOT_DIR/deploy/scripts/auto-deploy-entrypoint.sh"
|
|
)"
|
|
[[ "$output" == "bootstrap" ]] ||
|
|
fail "Stabiler Einstiegspunkt verwendet vor dem ersten Release nicht das Bootstrap-Skript."
|
|
|
|
mkdir -p "$entrypoint_root/current/deploy/scripts"
|
|
cat >"$entrypoint_root/current/deploy/scripts/auto-deploy.sh" <<'SH'
|
|
#!/usr/bin/env bash
|
|
printf 'current\n'
|
|
SH
|
|
chmod 0755 "$entrypoint_root/current/deploy/scripts/auto-deploy.sh"
|
|
|
|
output="$(
|
|
WATERMAPS_INSTALL_DIR="$entrypoint_root" \
|
|
"$ROOT_DIR/deploy/scripts/auto-deploy-entrypoint.sh"
|
|
)"
|
|
[[ "$output" == "current" ]] ||
|
|
fail "Stabiler Einstiegspunkt wechselt nach der Aktivierung nicht auf Current."
|
|
}
|
|
|
|
test_stable_entrypoint_bootstraps_and_then_uses_current
|
|
test_already_active_repairs_current_link
|
|
test_successful_release_uses_manifest_digests
|
|
test_pointer_main_mismatch_does_not_deploy
|
|
test_failed_release_restores_previous_immutable_release
|
|
test_invalid_bundle_is_rejected_and_cleaned
|
|
|
|
printf 'Auto-Deploy: Manifest-Digests, atomare Aktivierung und Rollback: OK\n'
|