Verify production runtime image after release

This commit is contained in:
SimpleTest 2026-07-27 02:55:40 +03:00
parent ca715e2876
commit a0e45707a3
2 changed files with 85 additions and 6 deletions

View File

@ -95,7 +95,7 @@ case "$*" in
printf 'compose:%s\n' "$*" >>"$MOCK_COMMAND_LOG" printf 'compose:%s\n' "$*" >>"$MOCK_COMMAND_LOG"
exit 0 exit 0
;; ;;
'up -d --no-deps --no-build --wait app') 'up -d --no-deps --no-build --force-recreate --wait app')
printf 'compose:%s\n' "$*" >>"$MOCK_COMMAND_LOG" printf 'compose:%s\n' "$*" >>"$MOCK_COMMAND_LOG"
if [ "${MOCK_FAIL_APP_UP:-}" = once ] && if [ "${MOCK_FAIL_APP_UP:-}" = once ] &&
[ ! -e "$MOCK_FAIL_APP_MARKER" ]; then [ ! -e "$MOCK_FAIL_APP_MARKER" ]; then
@ -153,10 +153,24 @@ if [ "$1" = inspect ]; then
case "$3" in case "$3" in
'{{.State.Status}}') printf 'running\n' ;; '{{.State.Status}}') printf 'running\n' ;;
'{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}') printf 'healthy\n' ;; '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}') printf 'healthy\n' ;;
'{{.Config.Image}}')
if [ "${MOCK_WRONG_RUNTIME_IMAGE:-}" = true ]; then
printf 'who-need-help:production-wrong\n'
else
awk -F= '$1 == "APP_IMAGE" {print substr($0, index($0, "=") + 1)}' \
"$MOCK_ENV_FILE"
fi
;;
'{{.Image}}') printf 'sha256:mock-candidate\n' ;;
*) exit 1 ;; *) exit 1 ;;
esac esac
exit 0 exit 0
fi fi
if [ "$1" = image ] && [ "$2" = inspect ] &&
[ "$3" = --format ] && [ "$4" = '{{.Id}}' ]; then
printf 'sha256:mock-candidate\n'
exit 0
fi
printf 'Unexpected docker invocation: %s\n' "$*" >&2 printf 'Unexpected docker invocation: %s\n' "$*" >&2
exit 1 exit 1
EOF EOF
@ -173,6 +187,7 @@ container_env=(
--env "MOCK_TARGET_COMMIT=$target_commit" --env "MOCK_TARGET_COMMIT=$target_commit"
--env "MOCK_GIT_STATE=$remote_root/git-state" --env "MOCK_GIT_STATE=$remote_root/git-state"
--env "MOCK_COMMAND_LOG=$remote_root/mock-commands.log" --env "MOCK_COMMAND_LOG=$remote_root/mock-commands.log"
--env "MOCK_ENV_FILE=$remote_root/.env"
--env "PATH=/mock-bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" --env "PATH=/mock-bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
) )
container_mounts=( container_mounts=(
@ -215,7 +230,7 @@ grep -F 'compose:build migrate' "$fixture/mock-commands.log" >/dev/null
grep -F 'compose:stop app' "$fixture/mock-commands.log" >/dev/null grep -F 'compose:stop app' "$fixture/mock-commands.log" >/dev/null
grep -F 'compose:run --rm --no-deps migrate' \ grep -F 'compose:run --rm --no-deps migrate' \
"$fixture/mock-commands.log" >/dev/null "$fixture/mock-commands.log" >/dev/null
grep -F 'compose:up -d --no-deps --no-build --wait app' \ grep -F 'compose:up -d --no-deps --no-build --force-recreate --wait app' \
"$fixture/mock-commands.log" >/dev/null "$fixture/mock-commands.log" >/dev/null
grep -R -F 'migration_policy=forward_only' \ grep -R -F 'migration_policy=forward_only' \
"$fixture/output/releases" --include rollback-manifest.txt >/dev/null "$fixture/output/releases" --include rollback-manifest.txt >/dev/null
@ -231,6 +246,26 @@ if grep -F 'edge:' "$fixture/mock-commands.log" >/dev/null; then
exit 1 exit 1
fi fi
write_old_env
printf '%s\n' "$current_commit" >"$fixture/git-state"
: >"$fixture/mock-commands.log"
set +e
run_release forward_only \
--env MOCK_WRONG_RUNTIME_IMAGE=true \
>"$run_dir/wrong-runtime-image.out" 2>&1
wrong_runtime_status=$?
set -e
if [[ "$wrong_runtime_status" -eq 0 ]]; then
echo "Release drill accepted a container created from the wrong image." >&2
exit 1
fi
grep -F 'Candidate runtime selected an unexpected image' \
"$run_dir/wrong-runtime-image.out" >/dev/null
grep -F 'previous application will not be restarted' \
"$run_dir/wrong-runtime-image.out" >/dev/null
grep -Fx "APP_IMAGE=who-need-help:production-${target_commit:0:12}" \
"$fixture/.env" >/dev/null
write_old_env write_old_env
printf '%s\n' "$current_commit" >"$fixture/git-state" printf '%s\n' "$current_commit" >"$fixture/git-state"
: >"$fixture/mock-commands.log" : >"$fixture/mock-commands.log"
@ -252,7 +287,7 @@ grep -Fx "APP_IMAGE=who-need-help:production-${target_commit:0:12}" \
"$fixture/.env" >/dev/null "$fixture/.env" >/dev/null
grep -Fx "CADDY_IMAGE=who-need-help:caddy-production-${current_commit:0:12}" \ grep -Fx "CADDY_IMAGE=who-need-help:caddy-production-${current_commit:0:12}" \
"$fixture/.env" >/dev/null "$fixture/.env" >/dev/null
test "$(grep -Fc 'compose:up -d --no-deps --no-build --wait app' \ test "$(grep -Fc 'compose:up -d --no-deps --no-build --force-recreate --wait app' \
"$fixture/mock-commands.log")" = 1 "$fixture/mock-commands.log")" = 1
grep -R -F 'status=forward-only-release-failed' \ grep -R -F 'status=forward-only-release-failed' \
"$fixture/output/releases" --include rollback-manifest.txt >/dev/null "$fixture/output/releases" --include rollback-manifest.txt >/dev/null
@ -278,7 +313,7 @@ grep -Fx "APP_IMAGE=who-need-help:production-${current_commit:0:12}" \
"$fixture/.env" >/dev/null "$fixture/.env" >/dev/null
grep -Fx "CADDY_IMAGE=who-need-help:caddy-production-${current_commit:0:12}" \ grep -Fx "CADDY_IMAGE=who-need-help:caddy-production-${current_commit:0:12}" \
"$fixture/.env" >/dev/null "$fixture/.env" >/dev/null
test "$(grep -Fc 'compose:up -d --no-deps --no-build --wait app' \ test "$(grep -Fc 'compose:up -d --no-deps --no-build --force-recreate --wait app' \
"$fixture/mock-commands.log")" = 2 "$fixture/mock-commands.log")" = 2
echo "Isolated production release success/forward-only/safe-recovery drill passed." echo "Isolated production release success/forward-only/safe-recovery drill passed."

View File

@ -261,7 +261,8 @@ rollback_runtime() {
echo "Release failed after selecting new images; restoring the previous immutable image tags." >&2 echo "Release failed after selecting new images; restoring the previous immutable image tags." >&2
restore_image_revision restore_image_revision
"$root/scripts/compose.sh" "$env_file" \ "$root/scripts/compose.sh" "$env_file" \
up -d --no-deps --no-build --wait "${runtime_services[@]}" || true up -d --no-deps --no-build --force-recreate --wait \
"${runtime_services[@]}" || true
curl --fail --silent --show-error --max-time 15 \ curl --fail --silent --show-error --max-time 15 \
"https://$expected_domain/healthz/ready" >/dev/null || true "https://$expected_domain/healthz/ready" >/dev/null || true
@ -286,6 +287,48 @@ case "$app_topology" in
;; ;;
esac esac
verify_candidate_runtime() {
local expected_app_image expected_app_image_id service container
local configured_image running_image_id state health
expected_app_image=$(read_value APP_IMAGE)
expected_app_image_id=$(
docker image inspect --format '{{.Id}}' "$expected_app_image"
)
for service in "${expected_services[@]}"; do
mapfile -t containers < <("$root/scripts/compose.sh" "$env_file" ps -q "$service")
[[ ${#containers[@]} -gt 0 ]] || {
echo "Candidate runtime service has no container: $service" >&2
return 1
}
for container in "${containers[@]}"; do
state=$(docker inspect --format '{{.State.Status}}' "$container")
health=$(
docker inspect \
--format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' \
"$container"
)
configured_image=$(docker inspect --format '{{.Config.Image}}' "$container")
running_image_id=$(docker inspect --format '{{.Image}}' "$container")
[[ "$state" == "running" && "$health" == "healthy" ]] || {
echo "Candidate runtime container is not healthy: $service" >&2
return 1
}
[[ "$configured_image" == "$expected_app_image" ]] || {
echo "Candidate runtime selected an unexpected image: $service" >&2
return 1
}
[[ "$running_image_id" == "$expected_app_image_id" ]] || {
echo "Candidate runtime image ID does not match the selected immutable image: $service" >&2
return 1
}
done
done
}
trap rollback_runtime EXIT HUP INT TERM trap rollback_runtime EXIT HUP INT TERM
if [[ "$branch" == "main" ]]; then if [[ "$branch" == "main" ]]; then
@ -312,7 +355,8 @@ migration_started=true
"$root/scripts/compose.sh" "$env_file" run --rm --no-deps migrate "$root/scripts/compose.sh" "$env_file" run --rm --no-deps migrate
"$root/scripts/check-database.sh" "$env_file" "$root/scripts/check-database.sh" "$env_file"
"$root/scripts/compose.sh" "$env_file" \ "$root/scripts/compose.sh" "$env_file" \
up -d --no-deps --no-build --wait "${runtime_services[@]}" up -d --no-deps --no-build --force-recreate --wait "${runtime_services[@]}"
verify_candidate_runtime
COMPOSE_PROJECT_NAME="$compose_project" \ COMPOSE_PROJECT_NAME="$compose_project" \
"$root/scripts/verify-realtime-cluster.sh" compose "$root/scripts/verify-realtime-cluster.sh" compose