ci: make split workflows fail visibly
Read Forge through the credential helper and public authority endpoint, treat API failures as failures, and evaluate every workflow attached to the newest candidate SHA. A green source gate can no longer hide a red package rail. Include RedFlag in the deliberate repository inventory.
This commit is contained in:
parent
23ccdf5e51
commit
39dfb45925
1 changed files with 35 additions and 14 deletions
|
|
@ -6,8 +6,10 @@
|
||||||
# ci-status.sh souveraine 10 # last 10 runs instead of 3
|
# ci-status.sh souveraine 10 # last 10 runs instead of 3
|
||||||
# ci-status.sh --all # newest run for every repo we own
|
# ci-status.sh --all # newest run for every repo we own
|
||||||
#
|
#
|
||||||
# Exit 0 when the newest run is green or still going, 1 when it failed, so this
|
# Exit 0 when the newest run of every workflow is green or still going, 1 when
|
||||||
# is usable as a gate as well as a report.
|
# any workflow's newest run failed, so this is usable as a gate as well as a
|
||||||
|
# report. A green source workflow must not hide a red package workflow from the
|
||||||
|
# same push.
|
||||||
#
|
#
|
||||||
# WHY THIS EXISTS. On 2026-07-28 a pinned action SHA rotted, `rust-test` died
|
# WHY THIS EXISTS. On 2026-07-28 a pinned action SHA rotted, `rust-test` died
|
||||||
# before compiling anything, and `aarch64-artifact` was therefore SKIPPED rather
|
# before compiling anything, and `aarch64-artifact` was therefore SKIPPED rather
|
||||||
|
|
@ -22,14 +24,15 @@
|
||||||
# See docs/tasks/42-build-outcomes-are-invisible.md.
|
# See docs/tasks/42-build-outcomes-are-invisible.md.
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
GITEA="${GITEA_URL:-http://10.10.20.120:4455}"
|
GITEA="${GITEA_URL:-https://gitea.wiuf.net}"
|
||||||
OWNER="${GITEA_OWNER:-Fimeg}"
|
OWNER="${GITEA_OWNER:-Fimeg}"
|
||||||
|
|
||||||
# Token, in order of preference: the environment, then the credential store
|
# Token, in order of preference: the environment, then the credential helper
|
||||||
# git already uses for this host. No third copy of a secret in the tree.
|
# git already uses for this host. No third copy of a secret in the tree.
|
||||||
if [ -z "${GITEA_TOKEN:-}" ] && [ -r "$HOME/.git-credentials" ]; then
|
if [ -z "${GITEA_TOKEN:-}" ]; then
|
||||||
GITEA_TOKEN=$(sed -n 's|^http://\([0-9a-f]\{40\}\)@10\.10\.20\.120.*|\1|p' \
|
GITEA_TOKEN=$(printf 'protocol=https\nhost=gitea.wiuf.net\n\n' \
|
||||||
"$HOME/.git-credentials" | head -1)
|
| git credential fill 2>/dev/null \
|
||||||
|
| sed -n 's/^password=//p' | head -1)
|
||||||
fi
|
fi
|
||||||
if [ -z "${GITEA_TOKEN:-}" ]; then
|
if [ -z "${GITEA_TOKEN:-}" ]; then
|
||||||
GITEA_TOKEN=$(git -C "$HOME/Projects/Pixel3Arch" remote -v 2>/dev/null \
|
GITEA_TOKEN=$(git -C "$HOME/Projects/Pixel3Arch" remote -v 2>/dev/null \
|
||||||
|
|
@ -55,9 +58,26 @@ if not runs:
|
||||||
for r in runs[:limit]:
|
for r in runs[:limit]:
|
||||||
c = r.get("conclusion")
|
c = r.get("conclusion")
|
||||||
mark = "ok" if c == "success" else ("FAILED" if c == "failure" else (c or "running"))
|
mark = "ok" if c == "success" else ("FAILED" if c == "failure" else (c or "running"))
|
||||||
print("DETAIL", r.get("id"), r.get("head_branch"), (r.get("head_sha") or "")[:8], mark)
|
workflow = (r.get("path") or r.get("name") or "workflow").split("@", 1)[0]
|
||||||
n = runs[0]
|
print("DETAIL", r.get("id"), workflow, r.get("head_branch"),
|
||||||
print("TOP", n.get("id") if n.get("conclusion") == "failure" else "-")
|
(r.get("head_sha") or "")[:8], mark)
|
||||||
|
|
||||||
|
# Gitea returns one interleaved stream for every workflow. Inspect every
|
||||||
|
# workflow that ran for the newest candidate SHA; looking only at runs[0] lets
|
||||||
|
# a successful ci.yml conceal a failed package.yml from the same push. Limiting
|
||||||
|
# this to that SHA avoids resurrecting an old failure from a workflow that
|
||||||
|
# does not run on the candidate branch or event.
|
||||||
|
candidate = runs[0].get("head_sha")
|
||||||
|
seen = set()
|
||||||
|
for r in runs:
|
||||||
|
if r.get("head_sha") != candidate:
|
||||||
|
continue
|
||||||
|
workflow = (r.get("path") or r.get("name") or "workflow").split("@", 1)[0]
|
||||||
|
if workflow in seen:
|
||||||
|
continue
|
||||||
|
seen.add(workflow)
|
||||||
|
if r.get("conclusion") == "failure":
|
||||||
|
print("FAIL", r.get("id"), workflow)
|
||||||
' "$1"
|
' "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,13 +86,14 @@ check_repo() {
|
||||||
|
|
||||||
out=$(api "/repos/$OWNER/$repo/actions/runs" 2>/dev/null | summarize "$limit")
|
out=$(api "/repos/$OWNER/$repo/actions/runs" 2>/dev/null | summarize "$limit")
|
||||||
case "$out" in
|
case "$out" in
|
||||||
ERR|NONE|"") printf 'ci-status: %-20s no Actions\n' "$repo"; return 0 ;;
|
ERR|"") printf 'ci-status: %-20s Actions API unavailable\n' "$repo" >&2; return 2 ;;
|
||||||
|
NONE) printf 'ci-status: %-20s no Actions\n' "$repo"; return 0 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
printf '%s\n' "$out" | awk -v r="$repo" '$1=="DETAIL"{
|
printf '%s\n' "$out" | awk -v r="$repo" '$1=="DETAIL"{
|
||||||
printf "ci-status: %-20s run %-5s [%-8s] %s -> %s\n", r, $2, $3, $4, $5 }'
|
printf "ci-status: %-20s run %-5s [%-12s %-8s] %s -> %s\n", r, $2, $3, $4, $5, $6 }'
|
||||||
|
|
||||||
failed_run=$(printf '%s\n' "$out" | awk '$1=="TOP" && $2!="-"{print $2}')
|
failed_run=$(printf '%s\n' "$out" | awk '$1=="FAIL"{print $2; exit}')
|
||||||
[ -n "$failed_run" ] || return 0
|
[ -n "$failed_run" ] || return 0
|
||||||
|
|
||||||
echo "ci-status: --- run $failed_run: which job ---"
|
echo "ci-status: --- run $failed_run: which job ---"
|
||||||
|
|
@ -104,7 +125,7 @@ for j in d.get("jobs", d):
|
||||||
# Listed, not discovered: a new repo should be a deliberate addition here, and
|
# Listed, not discovered: a new repo should be a deliberate addition here, and
|
||||||
# a repo that disappears from the list should be noticed rather than silently
|
# a repo that disappears from the list should be noticed rather than silently
|
||||||
# stop being checked.
|
# stop being checked.
|
||||||
ALL_REPOS="souveraine Pixel3Arch culver souveraine-viewtop souveraine-lens souveraine-player hexagonrpc souveraine-updater"
|
ALL_REPOS="souveraine Pixel3Arch RedFlag culver souveraine-viewtop souveraine-lens souveraine-player hexagonrpc souveraine-updater"
|
||||||
|
|
||||||
if [ "${1:-}" = "--all" ]; then
|
if [ "${1:-}" = "--all" ]; then
|
||||||
worst=0
|
worst=0
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue