name: Notify # Opens an issue when a package fails to update on main, so it reaches GitHub's # notifications, the mobile app and email. 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. 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: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} OWNER: ${{ github.repository_owner }} WORKFLOW: ${{ github.event.workflow_run.name }} 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 issues="$(gh issue list --state open --label "$label" --limit 200 --json number,title)" # The issue title for a job: the package for "update ()", # 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. excerpt_for() { curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \ "$GITHUB_API_URL/repos/$GH_REPO/actions/jobs/$1/logs" | sed -E 's/^[0-9TZ:.-]+ //; s/\x1b\[[0-9;]*m//g' | awk '/^##\[group\]Run / { out = "" } { out = out $0 "\n" } /^##\[error\]/ { printf "%s", out; exit }' | tail -40 || true } # report <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 gh issue edit "$issue" --body "$body" else gh label create "$label" --color d73a4a \ --description "A package fails to update" --force gh issue create --title "$title" --label "$label" --body "@$OWNER $body" fi } close() { local issue issue="$(issue_for "$1")" if [ -n "$issue" ]; then gh issue close "$issue" --comment "Passing again: $RUN_URL" fi } if [ "$CONCLUSION" = startup_failure ]; then report "$WORKFLOW is failing" "" exit 0 fi # Cancelled and skipped jobs say nothing about a package. 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< <(gh api --paginate "repos/$GH_REPO/actions/runs/$RUN_ID/jobs?per_page=100" --jq '.jobs[]')