| 1 | #!/usr/bin/env bash |
| 2 | # Waits for every Diffinery Actions run that pushing a commit started (push and |
| 3 | # pull_request runs, and the workflow_run followers they start) and prints each run and |
| 4 | # job. Prints the logs of failed jobs and exits non-zero if any run did not succeed. |
| 5 | # Runs that someone started by hand or on a schedule while that commit was the branch |
| 6 | # head (workflow_dispatch, schedule, repository_dispatch) share its sha but not the push, |
| 7 | # so they are listed as skipped and not waited for. |
| 8 | # |
| 9 | # Usage: DIFFINERY=https://diffinery.com TOKEN=... diffinery-wait-runs.sh owner/repo sha [minutes] |
| 10 | set -euo pipefail |
| 11 | repo=$1 sha=$2 minutes=${3:-60} |
| 12 | api() { curl -fsS --retry 3 "$DIFFINERY/api/v3/repos/$repo$1" -H "Authorization: Bearer $TOKEN"; } |
| 13 | unrelated='["workflow_dispatch", "schedule", "repository_dispatch"]' |
| 14 | |
| 15 | deadline=$(( $(date +%s) + minutes * 60 )) |
| 16 | settled=0 |
| 17 | while :; do |
| 18 | all=$(api "/actions/runs?head_sha=$sha&per_page=100") |
| 19 | runs=$(jq --argjson u "$unrelated" '.workflow_runs |= map(select(.event as $e | $u | index($e) | not))' <<<"$all") |
| 20 | total=$(jq '.workflow_runs | length' <<<"$runs") |
| 21 | pending=$(jq '[.workflow_runs[] | select(.status != "completed")] | length' <<<"$runs") |
| 22 | if [ "$total" -gt 0 ] && [ "$pending" = 0 ]; then |
| 23 | # Finished runs can start followers (workflow_run), so make sure nothing new appears. |
| 24 | settled=$((settled + 1)) |
| 25 | [ "$settled" -ge 3 ] && break |
| 26 | else |
| 27 | settled=0 |
| 28 | fi |
| 29 | if [ "$(date +%s)" -ge "$deadline" ]; then |
| 30 | echo "::error::Diffinery runs for $sha were not finished after $minutes minutes" |
| 31 | jq -r '.workflow_runs[] | " \(.name) [\(.event)]: \(.status)"' <<<"$runs" |
| 32 | exit 1 |
| 33 | fi |
| 34 | sleep 15 |
| 35 | done |
| 36 | |
| 37 | jq -r --argjson u "$unrelated" '.workflow_runs[] | select(.event as $e | $u | index($e)) | "skipped \(.name) [\(.event)]: \(.status) \(.html_url)"' <<<"$all" |
| 38 | failed=0 |
| 39 | for id in $(jq -r '.workflow_runs[].id' <<<"$runs"); do |
| 40 | run=$(api "/actions/runs/$id") |
| 41 | jq -r '"\(.name) [\(.event)]: \(.conclusion)\(try " in \((.updated_at | fromdateiso8601) - (.created_at | fromdateiso8601))s" catch "") \(.html_url)"' <<<"$run" |
| 42 | jobs=$(api "/actions/runs/$id/jobs") |
| 43 | # Each job's time, and its steps that took over 20 seconds, to see where a sync spends it. |
| 44 | jq -r 'def secs: try ((.completed_at | fromdateiso8601) - (.started_at | fromdateiso8601)) catch null; |
| 45 | .jobs[] | " \(.name): \(.conclusion)\(if .failure then " - " + (.failure.message // "") else "" end)\(if secs then " (\(secs)s)" else "" end)", |
| 46 | (.steps[]? | select((secs // 0) > 20) | " \(secs)s \(.name)")' <<<"$jobs" |
| 47 | if [ "$(jq -r .conclusion <<<"$run")" != success ]; then |
| 48 | failed=1 |
| 49 | for job in $(jq -r '.jobs[] | select(.conclusion == "failure" or .conclusion == "timed_out") | .id' <<<"$jobs"); do |
| 50 | echo "::group::Last log lines of job $job" |
| 51 | api "/actions/jobs/$job/logs" | tail -n 120 |
| 52 | echo "::endgroup::" |
| 53 | done |
| 54 | fi |
| 55 | done |
| 56 | [ "$failed" = 0 ] && echo "every Diffinery run on $sha succeeded" |
| 57 | exit "$failed" |