#!/usr/bin/env bash set -euo pipefail if [[ $# -lt 4 ]]; then echo "Usage: $0 [expected-replicas]" >&2 exit 1 fi STACK_NAME="$1" SERVICE_NAME="$2" STACK_FILE="$3" HEALTH_URL="$4" EXPECTED_REPLICAS="${5:-4}" SERVICE_FQDN="${STACK_NAME}_${SERVICE_NAME}" LOG_PREFIX="[swarm-deploy]" log() { printf '%s %s %s\n' "$(date --iso-8601=seconds)" "$LOG_PREFIX" "$*" } retry() { local attempts=$1; shift local delay=$1; shift local n=1 while true; do if "$@"; then return 0 fi if (( n == attempts )); then return 1 fi ((n++)) sleep "$delay" done } log "Deploying stack '${STACK_NAME}' using ${STACK_FILE}" docker stack deploy --compose-file "$STACK_FILE" "$STACK_NAME" log "Waiting for service ${SERVICE_FQDN} to reach ${EXPECTED_REPLICAS} replicas" retries=24 while (( retries > 0 )); do replicas_raw=$(docker service ls --filter "name=${SERVICE_FQDN}" --format '{{.Replicas}}' || true) if [[ -z "$replicas_raw" ]]; then log "Service ${SERVICE_FQDN} not found yet; retrying" sleep 5 ((retries--)) continue fi replicas_clean=${replicas_raw%% (*} running=${replicas_clean%%/*} desired=${replicas_clean##*/} if [[ "$running" == "$desired" && "$running" == "$EXPECTED_REPLICAS" ]]; then log "Service reached desired replica count: ${running}/${desired}" break fi log "Current replicas ${running}/${desired}; waiting..." sleep 5 ((retries--)) done if (( retries == 0 )); then log "Timed out waiting for replicas" docker service ps "$SERVICE_FQDN" exit 1 fi log "Checking task states" if ! docker service ps "$SERVICE_FQDN" --no-trunc --filter 'desired-state=Running' --format '{{.CurrentState}}' | grep -q '^Running '; then log "Some tasks are not running" docker service ps "$SERVICE_FQDN" exit 1 fi log "Running health check against ${HEALTH_URL}" if ! retry 3 5 curl -fsS "$HEALTH_URL"; then log "Health check failed; rolling back service" docker service update --rollback "$SERVICE_FQDN" || true exit 1 fi log "Health check succeeded" log "Deployment finished successfully"