Files
PKGBUILDS/.github/workflows/notify.yml
T

164 lines
5.8 KiB
YAML

name: Notify
# Opens an issue when a package fails to update on main, so it shows up in the
# notifications on git.felo.gg. One issue per package: a broken package must
# not hide behind another one, and a run of only some packages must not close
# the issues of the others. A later failure refreshes the issue instead of
# commenting, as the schedule would otherwise add one every 6 hours. The next
# successful update of that package closes it again.
#
# Talks to the Gitea API with curl: gh only knows GitHub.
on:
workflow_run:
workflows: [Update AUR packages]
types: [completed]
permissions:
actions: read
issues: write
concurrency:
group: notify
cancel-in-progress: false
jobs:
notify:
name: Notify
runs-on: ubuntu-latest
if: >-
github.event.workflow_run.event != 'pull_request' &&
github.event.workflow_run.head_branch == github.event.repository.default_branch
steps:
- name: Open, update or close the failure issues
env:
TOKEN: ${{ github.token }}
API: ${{ github.api_url }}/repos/${{ github.repository }}
OWNER: ${{ github.repository_owner }}
WORKFLOW: ${{ github.event.workflow_run.name || 'Update AUR packages' }}
CONCLUSION: ${{ github.event.workflow_run.conclusion }}
EVENT: ${{ github.event.workflow_run.event }}
RUN_ID: ${{ github.event.workflow_run.id }}
RUN_URL: ${{ github.event.workflow_run.html_url }}
SHA: ${{ github.event.workflow_run.head_sha }}
run: |
set -euo pipefail
label=ci-failure
api() {
local method=$1 path=$2
shift 2
curl -fsSL -X "$method" -H "Authorization: token $TOKEN" \
-H 'Content-Type: application/json' "$API$path" "$@"
}
issues="$(api GET "/issues?state=open&type=issues&labels=$label&limit=50")"
label_id() {
local id
id="$(api GET '/labels?limit=50' | jq -r --arg l "$label" '.[] | select(.name == $l) | .id')"
if [ -z "$id" ]; then
id="$(api POST /labels -d "$(jq -cn --arg l "$label" \
'{name: $l, color: "#d73a4a", description: "A package fails to update"}')" | jq -r .id)"
fi
echo "$id"
}
# The issue title for a job: the package for "update (<package>)",
# the workflow for anything else.
title_for() {
if [[ $1 =~ ^update\ \((.+)\)$ ]]; then
echo "${BASH_REMATCH[1]} fails to update"
else
echo "$WORKFLOW is failing"
fi
}
issue_for() {
jq -r --arg title "$1" '.[] | select(.title == $title) | .number' <<<"$issues" | head -1
}
# The lines of the failed step leading up to its first error, enough
# to see what broke without opening the log. Falls back to the end
# of the log.
excerpt_for() {
local log
log="$(api GET "/actions/jobs/$1/logs" |
sed -E 's/^[0-9TZ:.-]+ //; s/\x1b\[[0-9;]*m//g')" || return 0
awk '/^(##\[group\]|::group::)Run / { out = "" } { out = out $0 "\n" }
/^(##\[error\]|::error::)/ { printf "%s", out; found = 1; exit }
END { if (!found) exit 1 }' <<<"$log" | tail -40 ||
tail -40 <<<"$log"
}
# report <title> <job json, or empty if the run never started>
report() {
local title=$1 job=$2 line excerpt="" issue body
if [ -n "$job" ]; then
line="$(jq -r '"- [\(.name)](\(.html_url))" +
([.steps[]? | select(.conclusion == "failure") | .name] |
if length > 0 then ": " + join(", ") else "" end)' <<<"$job")"
excerpt="$(excerpt_for "$(jq -r .id <<<"$job")")"
else
line="The run did not start ($CONCLUSION)."
fi
body="$(cat <<EOF
[$WORKFLOW]($RUN_URL) failed on \`${SHA:0:7}\`, started by \`$EVENT\`.
$line
EOF
)"
if [ -n "$excerpt" ]; then
body="$body
<details>
<summary>Log</summary>
\`\`\`
$excerpt
\`\`\`
</details>"
fi
issue="$(issue_for "$title")"
if [ -n "$issue" ]; then
api PATCH "/issues/$issue" -d "$(jq -cn --arg b "$body" '{body: $b}')" >/dev/null
else
api POST /issues -d "$(jq -cn --arg t "$title" --arg b "@$OWNER $body" \
--argjson l "$(label_id)" '{title: $t, body: $b, labels: [$l]}')" >/dev/null
fi
}
close() {
local issue
issue="$(issue_for "$1")"
if [ -n "$issue" ]; then
api POST "/issues/$issue/comments" \
-d "$(jq -cn --arg b "Passing again: $RUN_URL" '{body: $b}')" >/dev/null
api PATCH "/issues/$issue" -d '{"state": "closed"}' >/dev/null
fi
}
if [ "$CONCLUSION" = startup_failure ]; then
report "$WORKFLOW is failing" ""
exit 0
fi
# Cancelled and skipped jobs say nothing about a package.
page=1
while :; do
jobs="$(api GET "/actions/runs/$RUN_ID/jobs?limit=50&page=$page" | jq -c '.jobs[]?')"
[ -n "$jobs" ] || break
while IFS= read -r -u 3 job; do
title="$(title_for "$(jq -r .name <<<"$job")")"
case "$(jq -r .conclusion <<<"$job")" in
success) close "$title" ;;
failure | timed_out) report "$title" "$job" ;;
esac
done 3<<<"$jobs"
[ "$(wc -l <<<"$jobs")" -ge 50 ] || break
page=$((page + 1))
done