| 1 | #!/usr/bin/env bash |
| 2 | # Waits after `wrangler deploy` until the container rollouts it started reach their last step. |
| 3 | # `wrangler deploy` returns as soon as a rollout starts. Even in the last step, an instance the |
| 4 | # rollout hasn't updated yet starts jobs on the old image, so the Live test waits until jobs |
| 5 | # report the deployed image. Running containers keep the old image until they exit |
| 6 | # (rollout_active_grace_period), so the rollouts don't need to complete. |
| 7 | # |
| 8 | # Run from the repository root with CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID and |
| 9 | # RUNNER_IMAGE (scripts/runner-image.sh) set. Gives up with a warning after |
| 10 | # ROLLOUT_WAIT_MINUTES (default 10) rather than failing the deploy. |
| 11 | set -euo pipefail |
| 12 | : "${CLOUDFLARE_API_TOKEN:?}" "${CLOUDFLARE_ACCOUNT_ID:?}" "${RUNNER_IMAGE:?RUNNER_IMAGE is required (scripts/runner-image.sh)}" |
| 13 | api() { curl -fsS --retry 4 --retry-all-errors --retry-delay 3 "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/containers$1" -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"; } |
| 14 | deadline=$(( $(date +%s) + ${ROLLOUT_WAIT_MINUTES:-10} * 60 )) |
| 15 | last="" |
| 16 | while :; do |
| 17 | if ! apps=$(api /applications); then |
| 18 | echo "::warning::could not list the container applications, so the deploy doesn't wait for the rollout" |
| 19 | exit 0 |
| 20 | fi |
| 21 | pending="" |
| 22 | states="" |
| 23 | for row in $(jq -r '.result[] | select(.active_rollout_id != null) | "\(.id)/\(.active_rollout_id)/\(.name)"' <<<"$apps"); do |
| 24 | IFS=/ read -r app rollout name <<<"$row" |
| 25 | r=$(api "/applications/$app/rollouts/$rollout" | jq -c .result) || continue |
| 26 | # Only the rollouts of this deploy's image (matched by its tag, a hash of the runner). |
| 27 | [[ "$(jq -r '.target_configuration.image // ""' <<<"$r")" == *":${RUNNER_IMAGE##*:}" ]] || continue |
| 28 | # Steps started out of all steps, from the steps themselves (progress.current_step is 1-based in practice). |
| 29 | state=$(jq -r '(.steps // []) as $s | "\(.status), step \([$s[] | select(.status != "pending")] | length) of \($s | length) \($s | last | .status // ""), \(.progress.updated_instances // "?")/\(.progress.total_instances // "?") instances updated"' <<<"$r") |
| 30 | states+="$name: $state; " |
| 31 | # Done once the rollout ended, or its last step is under way. |
| 32 | jq -e '(.status | IN("completed", "reverted", "replaced")) or ((.steps // []) | length > 0 and (last | .status | IN("progressing", "completed")))' <<<"$r" >/dev/null || pending+="$name " |
| 33 | done |
| 34 | [ "$states" = "$last" ] || { echo "${states:-no container rollout for $RUNNER_IMAGE}"; last=$states; } |
| 35 | if [ -z "$pending" ]; then |
| 36 | # This job's container started on the old image before the deploy; it is still running, |
| 37 | # which shows a rollout leaves running jobs alone (rollout_active_grace_period). |
| 38 | [ -z "$states" ] || echo "the rollout reached its last step and this job, on an old-image container, is still running" |
| 39 | exit 0 |
| 40 | fi |
| 41 | if [ "$(date +%s)" -ge "$deadline" ]; then |
| 42 | echo "::warning::the container rollout of ${pending% } has not reached its last step after ${ROLLOUT_WAIT_MINUTES:-10} minutes; new jobs may still start on the old image" |
| 43 | exit 0 |
| 44 | fi |
| 45 | sleep 10 |
| 46 | done |