164 lines
5.8 KiB
YAML
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
|