Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

59 lines · 3.1 KB · executableRaw History
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]
10set -euo pipefail
11repo=$1 sha=$2 minutes=${3:-60}
12api() { curl -fsS --retry 3 "$DIFFINERY/api/v3/repos/$repo$1" -H "Authorization: Bearer $TOKEN"; }
13unrelated='["workflow_dispatch", "schedule", "repository_dispatch"]'
14
15deadline=$(( $(date +%s) + minutes * 60 ))
16settled=0
17while :; 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
35done
36
37jq -r --argjson u "$unrelated" '.workflow_runs[] | select(.event as $e | $u | index($e)) | "skipped \(.name) [\(.event)]: \(.status) \(.html_url)"' <<<"$all"
38failed=0
39for 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 # A skipped run (every job's `if` was false) is fine; a skipped Deploy after a failed CI
48 # still fails the sync through CI.
49 if ! grep -qxE 'success|skipped' <<<"$(jq -r .conclusion <<<"$run")"; then
50 failed=1
51 for job in $(jq -r '.jobs[] | select(.conclusion == "failure" or .conclusion == "timed_out") | .id' <<<"$jobs"); do
52 echo "::group::Last log lines of job $job"
53 api "/actions/jobs/$job/logs" | tail -n 120
54 echo "::endgroup::"
55 done
56 fi
57done
58[ "$failed" = 0 ] && echo "every Diffinery run on $sha succeeded"
59exit "$failed"