ci(diag): probe the runner cache server from a job container — do not merge (#234) #374

Closed
maximus wants to merge 3 commits from diag/234-cache-probe into main
Showing only changes of commit 1326ba1bcc - Show all commits

View file

@ -0,0 +1,144 @@
name: Diag — cache server reachability (#234)
# THROWAWAY DIAGNOSTIC — never merge. Lives on its own branch for #234.
#
# Since #232 no workflow calls the Actions cache: in July both the restore
# (getCacheEntry) and the save (reserveCache) ended in "Request timeout". The
# suspected cause is that the job container cannot reach the cache address the
# runner hands it. This job checks that from inside a job container, without
# host access:
# 1. which cache address the runner advertises (path redacted: it may carry a
# per-run token, and this repository's job logs are public);
# 2. raw TCP reachability of that address, next to other ports and other host
# addresses, to tell "dropped" from "refused" from "open";
# 3. an end-to-end round trip through actions/cache with a tiny payload.
# The job is green only if the payload comes back from the cache server.
#
# Steps that use bash features set `shell: bash`: inside a container the runner
# defaults to `sh`, and a job-level `defaults` block is refused by the runner.
on:
pull_request:
paths:
- '.forgejo/workflows/diag-cache-probe.yml'
permissions:
contents: read
jobs:
probe:
runs-on: ubuntu
container: ubuntu:22.04
timeout-minutes: 20
steps:
- name: Cache address and TCP reachability
shell: bash
run: |
echo "Runner-provided variables (names only):"
compgen -e | grep -E 'ACTIONS|RUNNER|FORGEJO|GITEA' | sort | sed 's/^/ /'
compgen -e | grep -E '_RUNNER_VERSION$' | while read -r v; do echo " $v=${!v}"; done
url="${ACTIONS_CACHE_URL:-}"
if [ -z "$url" ]; then
echo "ACTIONS_CACHE_URL is empty: the runner gives this job no cache server."
exit 0
fi
base=$(printf '%s' "$url" | sed -E 's#^(https?://[^/]+).*#\1#')
scheme=${base%%://*}
hostport=${base#*://}
case "$hostport" in
\[*\]:*) host=${hostport%]:*}; host=${host#[}; port=${hostport##*]:} ;;
*:*) host=${hostport%:*}; port=${hostport##*:} ;;
*) host=$hostport; port="" ;;
esac
if [ -z "$port" ]; then
if [ "$scheme" = https ]; then port=443; else port=80; fi
fi
shown=$host
case "$host" in *:*) shown="[$host]" ;; esac
echo
echo "Cache address advertised by the runner: $scheme://$shown:$port/<path redacted>"
forge=$(getent ahostsv4 git.lacompagniemaximus.com | awk '{ print $1; exit }')
echo "git.lacompagniemaximus.com resolves to (IPv4): ${forge:-unresolved}"
echo "Container addresses: $(hostname -I 2>/dev/null || echo unknown)"
gw_hex=$(awk '$2 == "00000000" { print $3; exit }' /proc/net/route)
gw=""
if [ -n "$gw_hex" ]; then
gw=$(printf '%d.%d.%d.%d' "0x${gw_hex:6:2}" "0x${gw_hex:4:2}" "0x${gw_hex:2:2}" "0x${gw_hex:0:2}")
fi
echo "Default gateway (the host, seen from this job network): ${gw:-unknown}"
probe() {
local label=$1 h=$2 p=$3 start ms rc err verdict
start=$(date +%s%N)
err=$(timeout 10 bash -c "exec 3<>/dev/tcp/$h/$p" 2>&1)
rc=$?
ms=$(( ($(date +%s%N) - start) / 1000000 ))
case $rc in
0) verdict="OPEN" ;;
124) verdict="TIMEOUT" ;;
*) case "$err" in
*refused*) verdict="REFUSED" ;;
*) verdict="ERROR (${err##*: })" ;;
esac ;;
esac
printf ' %-36s %-26s %-8s %6d ms\n' "$label" "$h:$p" "$verdict" "$ms"
}
echo
echo "TCP probes, 10 s each. OPEN = handshake done; REFUSED = the host answered"
echo "but nothing listens there; TIMEOUT = no answer (packets dropped, or the"
echo "address is unreachable from this network)."
probe "advertised cache address" "$host" "$port"
probe "same host, port 22 (sshd)" "$host" 22
probe "same host, port 443 (Traefik)" "$host" 443
if [ -n "$gw" ]; then
probe "job network gateway, cache port" "$gw" "$port"
probe "job network gateway, port 22" "$gw" 22
fi
probe "docker0 172.17.0.1, cache port" 172.17.0.1 "$port"
- name: Install Node.js 20
run: |
apt-get update
apt-get install -y --no-install-recommends curl ca-certificates
# actions/cache is a JavaScript action: it needs `node` in the container PATH.
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt-get install -y nodejs
node --version
- name: Write a tiny payload
id: payload
shell: bash
run: |
key="diag-234-$(date -u +%Y%m%dT%H%M%SZ)-$RANDOM"
mkdir -p probe-payload
printf '%s' "$key" > probe-payload/stamp
echo "key=$key" >> "$GITHUB_OUTPUT"
echo "payload key: $key"
- name: Save it (actions/cache/save)
uses: https://github.com/actions/cache/save@v4
with:
path: probe-payload
key: ${{ steps.payload.outputs.key }}
- name: Delete the local copy
run: rm -rf probe-payload
- name: Restore it (actions/cache/restore, fails on a miss)
uses: https://github.com/actions/cache/restore@v4
with:
path: probe-payload
key: ${{ steps.payload.outputs.key }}
fail-on-cache-miss: true
- name: Check the payload made the round trip
shell: bash
run: |
if [ "$(cat probe-payload/stamp 2>/dev/null)" = "${{ steps.payload.outputs.key }}" ]; then
echo "ROUND TRIP OK: the payload came back from the cache server."
else
echo "ROUND TRIP FAILED: the payload is missing or different."
exit 1
fi