Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,47 @@ jobs:
--title "$COMMIT_MESSAGE" \
--body "$COMMIT_MESSAGE"$'\n\n'"Generated from https://github.com/${GITHUB_REPOSITORY}/commit/${HEAD_SHA}."
fi

# surface failures in Slack — this workflow runs unattended on
# repository_dispatch, so a broken run would otherwise go unnoticed.
# configure the SLACK_WEBHOOK_URL secret (an incoming webhook) to enable;
# the step no-ops when it is unset
- name: Notify Slack on failure
if: failure() && env.SLACK_WEBHOOK_URL != ''
# avoid -x here so the webhook URL is not echoed into the logs
shell: 'bash -Eeuo pipefail {0}'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
RUN_NUMBER: ${{ github.run_number }}
EVENT_NAME: ${{ github.event_name }}
REPOSITORY: ${{ github.repository }}
# empty when the run failed before the message was computed
COMMIT_MESSAGE: ${{ steps.message.outputs.message }}
run: |
# a webhook secret set to a plaintext URL would put the credential on the wire
if [ "${SLACK_WEBHOOK_URL#https://}" = "$SLACK_WEBHOOK_URL" ]; then
echo >&2 'error: SLACK_WEBHOOK_URL must be an https:// URL'
exit 1
fi

jq --null-input \
--arg run_url "$RUN_URL" \
--arg run_number "$RUN_NUMBER" \
--arg event "$EVENT_NAME" \
--arg repo "$REPOSITORY" \
--arg commit_message "${COMMIT_MESSAGE:-}" '
":x: *<\($run_url)|Update Dockerfiles failed>* in `\($repo)`"
+ "\n_event_ `\($event)` · _run_ `#\($run_number)`"
+ (if $commit_message == "" then "" else "\n_update_ `\($commit_message)`" end)
| {
text: "Update Dockerfiles failed in \($repo) (run #\($run_number))",
blocks: [ { type: "section", text: { type: "mrkdwn", text: . } } ]
}
' \
| curl --silent --show-error --fail-with-body \
--connect-timeout 10 \
--max-time 30 \
--header 'Content-Type: application/json' \
--data @- \
"$SLACK_WEBHOOK_URL"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
49 changes: 43 additions & 6 deletions versions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,37 @@ set -Eeuo pipefail

cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"

retryAttempts=6

# the ghost-version-publish dispatch fires as soon as the tag lands upstream, which is seconds
# before raw.githubusercontent.com serves that tag and before the GitHub release (and its
# assets) are published; retry these fetches instead of failing the whole update run on the race
#
# $1: url
# $2: optional jq filter that must produce output before the response counts as ready -- a
# release goes live seconds before its assets finish uploading, so HTTP 200 is not on its
# own proof that what we came for is in the body
fetch() {
local url="$1" filter="${2-}" attempt body reason
for (( attempt = 1; attempt <= retryAttempts; attempt++ )); do
# capture per attempt so a partial body from a failed one is never emitted
# without a deadline a stalled connection would hang here instead of retrying
if ! body="$(curl -fsSL --connect-timeout 10 --max-time 120 "$url")"; then
reason='request failed'
elif [ -n "$filter" ] && [ -z "$(jq --raw-output "$filter" <<<"$body")" ]; then
reason='response is missing what we need'
else
printf '%s\n' "$body"
return 0
fi
echo >&2 "warning: $url: $reason (attempt $attempt/$retryAttempts)"
if [ "$attempt" -lt "$retryAttempts" ]; then
sleep "${attempt}0"
fi
done
return 1
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
versions=( "$@" )
if [ ${#versions[@]} -eq 0 ]; then
versions=( */ )
Expand Down Expand Up @@ -86,7 +117,7 @@ for version in "${versions[@]}"; do

# get a list of architectures supported by the sharp module's prebuilt libraries
# we cannot build it on other arches since the dep, libvips, is usually too old in Debian and Alpine
doc="$(curl -fsSL "https://raw.githubusercontent.com/TryGhost/Ghost/refs/tags/v$fullVersion/pnpm-lock.yaml" \
doc="$(fetch "https://raw.githubusercontent.com/TryGhost/Ghost/refs/tags/v$fullVersion/pnpm-lock.yaml" \
| jq --compact-output --raw-input --null-input '
reduce (
inputs
Expand Down Expand Up @@ -125,17 +156,23 @@ for version in "${versions[@]}"; do
# These assets start at 6.60.0; anything older has no tarball to install from.
if [ -n "$isNext" ]; then
tarballName="ghost-$fullVersion.tgz"
releaseJson="$(curl -fsSL "https://api.github.com/repos/TryGhost/Ghost/releases/tags/v$fullVersion")"
# read by the readiness filter below; passing it through the environment keeps the asset
# name out of the jq program text
export tarballName
if ! releaseJson="$(fetch \
"https://api.github.com/repos/TryGhost/Ghost/releases/tags/v$fullVersion" \
'.assets[]? | select(.name == env.tarballName) | .browser_download_url // empty' \
)"; then
echo >&2 "error: the GitHub release for 'v$fullVersion' has no '$tarballName' asset (these start at 6.60.0)"
exit 1
fi
# guaranteed non-empty: fetch only returns once the filter above matches
tarballUrl="$(jq <<<"$releaseJson" --raw-output --arg name "$tarballName" '
.assets[]? | select(.name == $name) | .browser_download_url // empty
')"
tarballDigest="$(jq <<<"$releaseJson" --raw-output --arg name "$tarballName" '
.assets[]? | select(.name == $name) | .digest // empty
')"
if [ -z "$tarballUrl" ]; then
echo >&2 "error: the GitHub release for 'v$fullVersion' has no '$tarballName' asset (these start at 6.60.0)"
exit 1
fi

# GitHub reports the asset digest as "sha256:<hex>"; refuse anything else rather than
# writing a hash the Dockerfile would then check with the wrong algorithm
Expand Down
Loading