The runner starts `shell: bash` steps as `bash -e -o pipefail`, so run 453 stopped at the first TIMEOUT instead of probing the other addresses. Each probe now swallows its own exit code, and the env listing and the forge lookup no longer abort the step when they come back empty. Refs #234 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
146 lines
6 KiB
YAML
146 lines
6 KiB
YAML
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.
|
|
# The runner starts those steps as `bash -e -o pipefail`, so every probe that
|
|
# is expected to fail must swallow its own exit code (run 453 stopped at the
|
|
# first TIMEOUT for that reason).
|
|
|
|
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/^/ /' || true
|
|
compgen -e | grep -E '_RUNNER_VERSION$' | while read -r v; do echo " $v=${!v}"; done || true
|
|
|
|
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 }') || true
|
|
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=0 || 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:-$FORGEJO_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
|