Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

45 lines · 3.0 KB · executableRaw History
1#!/usr/bin/env bash
2# Waits after `wrangler deploy` until the container rollouts it started reach their last step,
3# when every new container starts on the new image. `wrangler deploy` returns as soon as a
4# rollout starts, and a job started before its last step can land on the old image and be
5# replaced (with its steps) moments later. Running containers keep the old image until they
6# exit (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.
11set -euo pipefail
12: "${CLOUDFLARE_API_TOKEN:?}" "${CLOUDFLARE_ACCOUNT_ID:?}" "${RUNNER_IMAGE:?RUNNER_IMAGE is required (scripts/runner-image.sh)}"
13api() { 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"; }
14deadline=$(( $(date +%s) + ${ROLLOUT_WAIT_MINUTES:-10} * 60 ))
15last=""
16while :; 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 state=$(jq -r '"\(.status), step \((.progress.current_step // 0) + 1) of \(.progress.total_steps // (.steps | length)) \((.steps // []) | last | .status // ""), \(.progress.updated_instances // "?")/\(.progress.total_instances // "?") instances updated"' <<<"$r")
29 states+="$name: $state; "
30 # Done once the rollout ended, or its last step is under way (new containers get the new image).
31 jq -e '(.status | IN("completed", "reverted", "replaced")) or ((.steps // []) | length > 0 and (last | .status | IN("progressing", "completed")))' <<<"$r" >/dev/null || pending+="$name "
32 done
33 [ "$states" = "$last" ] || { echo "${states:-no container rollout for $RUNNER_IMAGE}"; last=$states; }
34 if [ -z "$pending" ]; then
35 # This job's container started on the old image before the deploy; it is still running,
36 # which shows a rollout leaves running jobs alone (rollout_active_grace_period).
37 [ -z "$states" ] || echo "the rollout reached its last step and this job, on an old-image container, is still running"
38 exit 0
39 fi
40 if [ "$(date +%s)" -ge "$deadline" ]; then
41 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"
42 exit 0
43 fi
44 sleep 10
45done