From cb494a4290220fe06dfe825c52c1b8ee25ef2858 Mon Sep 17 00:00:00 2001 From: gnezim Date: Sat, 25 Apr 2026 02:41:51 +0300 Subject: [PATCH] =?UTF-8?q?ci:=20deploy-container.sh=20=E2=80=94=20swap/ro?= =?UTF-8?q?llback=20with=20dry-run=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/ci/deploy-container.sh | 78 +++++++++++++++++++++++++++++++ tests/ci/test-deploy-container.sh | 53 +++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100755 scripts/ci/deploy-container.sh create mode 100755 tests/ci/test-deploy-container.sh diff --git a/scripts/ci/deploy-container.sh b/scripts/ci/deploy-container.sh new file mode 100755 index 00000000..d7494283 --- /dev/null +++ b/scripts/ci/deploy-container.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# deploy-container.sh — swap or rollback the flights-web container on the host. +# +# Usage: deploy-container.sh [--dry-run] +# +# `swap` — assumes the new image is tagged flights-web:${GITHUB_SHA}. +# Tags :current → :previous, :sha → :current, restarts container. +# `rollback` — runs flights-web:previous in place of :current, repoints :current. +# +# Env: +# GITHUB_SHA (required for swap) +# FLIGHTS_WEB_PORT (default 8081 — host port that nginx proxies to) +# IMAGE_NAME (default flights-web — set this to point at a registry later) +set -euo pipefail + +DRY_RUN=0 +if [ "${1:-}" = "--dry-run" ]; then + DRY_RUN=1 + shift +fi + +CMD="${1:-}" +PORT="${FLIGHTS_WEB_PORT:-8081}" +IMAGE="${IMAGE_NAME:-flights-web}" + +run() { + if [ "$DRY_RUN" -eq 1 ]; then + printf 'docker %s\n' "$*" + else + docker "$@" + fi +} + +run_or_skip() { + # Same as run, but doesn't fail in real mode if the docker call fails. + if [ "$DRY_RUN" -eq 1 ]; then + printf 'docker %s\n' "$*" + else + docker "$@" || true + fi +} + +case "$CMD" in + swap) + : "${GITHUB_SHA:?GITHUB_SHA required for swap}" + SHORT_SHA="${GITHUB_SHA:0:7}" + # 1. Tag the currently-live image as :previous (skip if first deploy). + if [ "$DRY_RUN" -eq 1 ] || docker image inspect "${IMAGE}:current" >/dev/null 2>&1; then + run tag "${IMAGE}:current" "${IMAGE}:previous" + fi + # 2. Tag the new SHA as :current. + run tag "${IMAGE}:${SHORT_SHA}" "${IMAGE}:current" + # 3. Stop + remove existing container if present. + run_or_skip stop flights-web + run_or_skip rm flights-web + # 4. Run new container. + run run -d --name flights-web --restart unless-stopped \ + -p "127.0.0.1:${PORT}:8080" \ + "${IMAGE}:current" + ;; + rollback) + if [ "$DRY_RUN" -eq 0 ] && ! docker image inspect "${IMAGE}:previous" >/dev/null 2>&1; then + echo "fatal: ${IMAGE}:previous not found — cannot rollback" >&2 + exit 1 + fi + run_or_skip stop flights-web + run_or_skip rm flights-web + run run -d --name flights-web --restart unless-stopped \ + -p "127.0.0.1:${PORT}:8080" \ + "${IMAGE}:previous" + # Repoint :current to :previous so subsequent swaps have a sane baseline. + run tag "${IMAGE}:previous" "${IMAGE}:current" + ;; + *) + echo "usage: $0 [--dry-run] " >&2 + exit 2 + ;; +esac diff --git a/tests/ci/test-deploy-container.sh b/tests/ci/test-deploy-container.sh new file mode 100755 index 00000000..c041e8b7 --- /dev/null +++ b/tests/ci/test-deploy-container.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT="$(cd "$(dirname "$0")/../.." && pwd)/scripts/ci/deploy-container.sh" +[ -x "$SCRIPT" ] || { echo "FAIL: $SCRIPT not executable"; exit 1; } + +assert_contains() { + local haystack="$1" needle="$2" + case "$haystack" in + *"$needle"*) ;; + *) echo "FAIL: expected '$needle' in:"; echo "$haystack"; exit 1 ;; + esac +} + +assert_order() { + local haystack="$1" first="$2" second="$3" + local pos1 pos2 + pos1=$(printf '%s' "$haystack" | grep -nF "$first" | head -1 | cut -d: -f1) + pos2=$(printf '%s' "$haystack" | grep -nF "$second" | head -1 | cut -d: -f1) + if [ -z "$pos1" ] || [ -z "$pos2" ] || [ "$pos1" -ge "$pos2" ]; then + echo "FAIL: expected '$first' (line $pos1) before '$second' (line $pos2)" + echo "$haystack" + exit 1 + fi +} + +export GITHUB_SHA=abcdef1234567890 +export FLIGHTS_WEB_PORT=8081 + +# --- swap --- +out=$("$SCRIPT" --dry-run swap) +# Order matters: tag previous before tagging current; remove old container before run new +assert_order "$out" "tag flights-web:current flights-web:previous" "tag flights-web:abcdef1 flights-web:current" +assert_contains "$out" "stop flights-web" +assert_contains "$out" "rm flights-web" +assert_order "$out" "rm flights-web" "run -d --name flights-web" +assert_contains "$out" "127.0.0.1:8081:8080" +assert_contains "$out" "flights-web:current" + +# --- rollback --- +out=$("$SCRIPT" --dry-run rollback) +assert_contains "$out" "stop flights-web" +assert_contains "$out" "rm flights-web" +assert_contains "$out" "flights-web:previous" +# After running previous, current alias should be repointed +assert_order "$out" "run -d --name flights-web" "tag flights-web:previous flights-web:current" + +# --- bad subcommand --- +if "$SCRIPT" --dry-run foo 2>/dev/null; then + echo "FAIL: expected unknown subcommand to error"; exit 1 +fi + +echo "PASS: deploy-container.sh"