diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 39c788eef..00ca90661 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -37,11 +37,22 @@ jobs: INSTANCE_ID: ${{ secrets.STAGING_EC2_INSTANCE_ID }} SECRET_ID: ${{ vars.STAGING_SECRET_ID }} run: | + SECRET_ID=$(echo "$SECRET_ID" | tr -d '[:space:]') + + DEPLOY_CMD="cd /data/kaapi-backend \ + && git fetch --all \ + && git pull origin main \ + && SECRET_ID=$SECRET_ID sh scripts/fetch-secrets.sh \ + && docker compose -f docker-compose.staging.yml build \ + && docker compose -f docker-compose.staging.yml --profile migrate run --rm migrate \ + && docker compose -f docker-compose.staging.yml up -d --wait --remove-orphans \ + && docker image prune -f" + CMD_ID=$(aws ssm send-command \ --instance-ids "$INSTANCE_ID" \ --document-name "AWS-RunShellScript" \ --comment "Deploy kaapi-backend kaapi-staging" \ - --parameters commands='["set -eux","chown -R ubuntu:ubuntu /data/kaapi-backend","sudo -iu ubuntu bash -lc \"cd /data/kaapi-backend && git fetch --all && git pull origin main && SECRET_ID='"$SECRET_ID"' sh scripts/fetch-secrets.sh && docker compose -f docker-compose.staging.yml build && docker compose -f docker-compose.staging.yml --profile migrate run --rm migrate && docker compose -f docker-compose.staging.yml up -d --wait --remove-orphans && docker image prune -f\""]' \ + --parameters commands='["set -eux","chown -R ubuntu:ubuntu /data/kaapi-backend","sudo -iu ubuntu bash -lc \"'"$DEPLOY_CMD"'\""]' \ --cloud-watch-output-config CloudWatchOutputEnabled=true \ --query "Command.CommandId" --output text) echo "cmd_id=$CMD_ID" >> "$GITHUB_OUTPUT" diff --git a/scripts/fetch-secrets.sh b/scripts/fetch-secrets.sh index d573e1074..b50a2d36f 100755 --- a/scripts/fetch-secrets.sh +++ b/scripts/fetch-secrets.sh @@ -1,37 +1,51 @@ #!/usr/bin/env sh # -# Fetch a JSON secret from AWS Secrets Manager and write it as a docker-compose -# env_file (.env.secrets). Run on the EC2 host at deploy time, BEFORE -# `docker compose up`. Never run at image build time — that would bake secrets -# into image layers. +# Fetch one or more JSON secrets from AWS Secrets Manager and write them as a +# docker-compose env_file (.env.secrets). Run on the EC2 host at deploy time, +# BEFORE `docker compose up`. Never run at image build time — that would bake +# secrets into image layers. # # Auth: relies on the EC2 instance IAM role (no static AWS keys). # # Required env: -# SECRET_ID Secret name or ARN, e.g. kaapi/staging +# SECRET_ID Comma-separated secret names or ARNs, e.g. +# "kaapi-staging-rds, kaapi-staging-rabbitmq". +# Secrets are appended in order; on a duplicate key the +# last secret wins (docker compose reads the last line). # Optional env: # AWS_DEFAULT_REGION AWS region (default: ap-south-1) # SECRETS_ENV_FILE Output path (default: .env.secrets) set -e -SECRET_ID=${SECRET_ID:?SECRET_ID not set} +SECRET_IDS=${SECRET_ID:?SECRET_ID not set} AWS_REGION=${AWS_DEFAULT_REGION:-ap-south-1} OUT=${SECRETS_ENV_FILE:-.env.secrets} command -v aws >/dev/null 2>&1 || { echo "[fetch-secrets] aws CLI not found on host" >&2; exit 1; } command -v jq >/dev/null 2>&1 || { echo "[fetch-secrets] jq not found on host" >&2; exit 1; } -echo "[fetch-secrets] Fetching secret | id: ${SECRET_ID} | region: ${AWS_REGION}" +umask 077 +TMP="${OUT}.tmp" +printf '# Generated by fetch-secrets.sh — do not edit, do not commit.\n' > "${TMP}" -SECRET_JSON=$(aws secretsmanager get-secret-value \ - --secret-id "${SECRET_ID}" \ - --region "${AWS_REGION}" \ - --query SecretString --output text) +OLD_IFS=$IFS +IFS=',' +for ID in ${SECRET_IDS}; do + # Secret names and ARNs never contain whitespace, so " a, b" splits cleanly. + ID=$(printf '%s' "${ID}" | tr -d '[:space:]') + [ -n "${ID}" ] || continue -umask 077 -printf '# Generated by fetch-secrets.sh — do not edit, do not commit.\n' > "${OUT}" -echo "${SECRET_JSON}" | jq -r 'to_entries[] | "\(.key)=\(.value)"' >> "${OUT}" + echo "[fetch-secrets] Fetching secret | id: ${ID} | region: ${AWS_REGION}" + SECRET_JSON=$(aws secretsmanager get-secret-value \ + --secret-id "${ID}" \ + --region "${AWS_REGION}" \ + --query SecretString --output text) + echo "${SECRET_JSON}" | jq -r 'to_entries[] | "\(.key)=\(.value)"' >> "${TMP}" +done +IFS=$OLD_IFS + +mv "${TMP}" "${OUT}" -COUNT=$(echo "${SECRET_JSON}" | jq 'length') +COUNT=$(grep -c '=' "${OUT}") echo "[fetch-secrets] Wrote ${COUNT} keys | file: ${OUT}"