#!/usr/bin/env bash # Fire the env's SSM deploy document (tag-targeted) and wait for it to finish, # gating on the AGGREGATE command status. Shared by publish-and-deploy.sh (forward # roll) and rollback.sh (backward roll) so the fire/wait/gate logic lives in ONE # place. Requires ENV + DEPLOY_DOC in the environment and aws creds already set. # # Tag-targeting (project=open-swe,env=) is exactly what the app deploy role's # tag-scoped ssm:SendCommand allows — no ec2:DescribeInstances, no instance id. set -euo pipefail : "${ENV:?}" "${DEPLOY_DOC:?}" COMMENT="${ROLL_COMMENT:-roll ${ENV}}" echo "==> fire ${DEPLOY_DOC} via SSM (tag-targeted: project=open-swe, env=${ENV})" CMD_ID="$(aws ssm send-command \ --document-name "${DEPLOY_DOC}" \ --targets "Key=tag:project,Values=open-swe" "Key=tag:env,Values=${ENV}" \ --comment "${COMMENT}" \ --query 'Command.CommandId' --output text)" echo "command: ${CMD_ID}" echo "==> wait for the deploy to finish" IID="" for _ in $(seq 1 60); do sleep 10 IID="$(aws ssm list-command-invocations --command-id "${CMD_ID}" \ --query 'CommandInvocations[0].InstanceId' --output text 2>/dev/null || echo None)" [ -z "${IID}" ] || [ "${IID}" = "None" ] && continue STATUS="$(aws ssm list-command-invocations --command-id "${CMD_ID}" \ --query 'CommandInvocations[0].Status' --output text 2>/dev/null || echo Pending)" case "${STATUS}" in Success | Failed | Cancelled | TimedOut) break ;; esac done if [ -z "${IID}" ] || [ "${IID}" = "None" ]; then echo "ERROR: no box picked up the deploy command (is a running open-swe ${ENV} box registered with SSM?)" >&2 exit 1 fi echo "==> deploy.sh output from ${IID}:" echo "----- stdout -----" aws ssm get-command-invocation --command-id "${CMD_ID}" --instance-id "${IID}" \ --query 'StandardOutputContent' --output text || true echo "----- stderr -----" aws ssm get-command-invocation --command-id "${CMD_ID}" --instance-id "${IID}" \ --query 'StandardErrorContent' --output text || true # Gate on the AGGREGATE command status (Success only if EVERY targeted invocation # succeeded), not CommandInvocations[0] — during a userDataCausesReplacement window # two instances can briefly share the project/env tags, and a partial failure on the # other instance must not be reported as success. TARGETS="$(aws ssm list-commands --command-id "${CMD_ID}" \ --query 'Commands[0].TargetCount' --output text 2>/dev/null || echo 1)" [ "${TARGETS}" = "1" ] || echo "WARNING: deploy fanned out to ${TARGETS} instances (expected 1)" AGG="$(aws ssm list-commands --command-id "${CMD_ID}" \ --query 'Commands[0].Status' --output text 2>/dev/null || echo Failed)" echo "==> aggregate deploy status: ${AGG} (across ${TARGETS} target(s))" [ "${AGG}" = "Success" ] || { echo "ERROR: deploy did not succeed (${AGG})" >&2; exit 1; }