mirror of
https://github.com/hanzoai/ci.git
synced 2026-10-20 15:28:05 +00:00
Carry the Go cache between runners
A runner lives one job, so no Go cache was ever restored: 142 Go-gate minutes in 48 hours were 31 downloading modules and 69 compiling, and a warm pass compiled commerce's vet in 15s against 198s cold. The cache lives in the org's own bucket behind cloud's /v1/s3, reached with the IAM token the KMS step already holds, so the server keeps one org out of another's and CI holds no bucket key. Go trusts a restored cache without re-hashing it, so only the default branch and tags write. setup-go's own cache is off: it is GitHub's store, and the forge has none. Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
This commit is contained in:
+34
-16
@@ -119,21 +119,18 @@ on:
|
||||
type: string
|
||||
default: 'true'
|
||||
go-cache:
|
||||
# OFF by default, and that default is the whole point: this workflow is
|
||||
# the shared one, and turning Go caching on for ~2700 repos at once grows
|
||||
# a per-runner cache that is EPHEMERAL and NOT on the runner PVC
|
||||
# (/root/.cache/actcache, while the claim mounts at /data). Measured
|
||||
# today: 3.4 GB on git-runner-0, 5.8 GB on git-runner-3, already. The
|
||||
# runner config records a previous disk exhaustion as "infrastructure
|
||||
# damage wearing a build defect's clothes", so the fleet does not opt in
|
||||
# by accident — a repo that wants it says so.
|
||||
#
|
||||
# A repo that DOES want it: hanzoai/cloud's gate builds 122 app binaries
|
||||
# to regenerate their documents and takes 18-27 minutes cold, which is
|
||||
# wide enough that most runs are cancelled before they finish.
|
||||
description: "Cache Go modules and build output between runs (setup-go)."
|
||||
description: >-
|
||||
Carry GOMODCACHE and GOCACHE between runs through this org's own
|
||||
bucket in cloud's object store (bin/gocache). ON by default: a runner
|
||||
is a pod that lives one job, so without it every Go gate downloads
|
||||
every module and compiles every dependency from nothing. Measured, not
|
||||
one Go cache was restored across 188 job logs, and a warm second pass
|
||||
compiled hanzoai/commerce's vet in 15s instead of 198s. The cache is
|
||||
keyed by go.sum, Go version and architecture, isolated per org by the
|
||||
server, and written only from the default branch or a tag. 'false'
|
||||
turns it off.
|
||||
required: false
|
||||
default: "false"
|
||||
default: "true"
|
||||
type: string
|
||||
spec-ref:
|
||||
# THE SECOND WAY INTO `move`, AND ON HANZO GIT THE ONLY ONE.
|
||||
@@ -389,7 +386,7 @@ jobs:
|
||||
# Naming the list here also makes the release order self-announcing: a
|
||||
# pipeline that has grown a tool refuses at step one, and says which ref
|
||||
# it read and what was missing from it.
|
||||
need="certclaims conflictmarkers gover ignoretracked imgplat imgtags imgver modsize publishable site vendormark"
|
||||
need="certclaims conflictmarkers gocache gover ignoretracked imgplat imgtags imgver modsize publishable site vendormark"
|
||||
miss='<no host answered>'
|
||||
# A sha cannot be a --branch, so it arrives by fetch rather than clone.
|
||||
# Both are wrapped so a host that does not carry the ref is a return
|
||||
@@ -1183,7 +1180,19 @@ jobs:
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: ${{ steps.gomod.outputs.file }}
|
||||
cache: ${{ inputs.go-cache == 'true' }}
|
||||
# GitHub's cache service is a store off our infrastructure, and the
|
||||
# forge has none. The cache is bin/gocache's, in the next step.
|
||||
cache: false
|
||||
|
||||
- name: Restore the Go cache (this org's store)
|
||||
# Before the first gate, so `go vet` and `go test` start from this org's
|
||||
# modules and compiled dependencies instead of the network. A miss, a
|
||||
# refusal or an outage is a cold build and never a red one; the script
|
||||
# says which on the log.
|
||||
id: gocache
|
||||
if: steps.gomod.outputs.file != '' && env.HANZO_CI_ROLE == 'test' && inputs.go-cache != 'false'
|
||||
run: |
|
||||
"$CI_HOME/bin/gocache" restore "$(dirname '${{ steps.gomod.outputs.file }}')"
|
||||
|
||||
- name: Provision C toolchain (cgo test gates)
|
||||
# CGO_ENABLED=1 gates (e.g. go-sqlite3, which bundles the sqlite
|
||||
@@ -1836,6 +1845,15 @@ jobs:
|
||||
printf '%s\t%s\t%s\n' "$n" "$claims" "$name" >> "$receipt"
|
||||
done
|
||||
|
||||
- name: Save the Go cache (this org's store)
|
||||
# After a green gate, so what is saved is what a passing build compiled.
|
||||
# bin/gocache writes only from the default branch or a tag and only a key
|
||||
# the store does not hold: a pull request reads this org's cache and
|
||||
# never writes it, because Go trusts a cache without re-hashing it.
|
||||
if: steps.gomod.outputs.file != '' && env.HANZO_CI_ROLE == 'test' && inputs.go-cache != 'false' && steps.gocache.outputs.hit != 'exact'
|
||||
run: |
|
||||
"$CI_HOME/bin/gocache" save "$(dirname '${{ steps.gomod.outputs.file }}')"
|
||||
|
||||
- name: Tests not run (declared by caller)
|
||||
# The complement of the step above, and the reason opting out cannot be
|
||||
# quiet. `tests: 'false'` asserts THIS EXACT COMMIT was gated before this
|
||||
|
||||
Executable
+155
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env bash
|
||||
# gocache — this org's Go module and build cache, carried between runners.
|
||||
#
|
||||
# gocache restore <module-dir> # prints hit=exact|partial|miss to $GITHUB_OUTPUT
|
||||
# gocache save <module-dir> # writes on a trusted ref, on a miss, and nowhere else
|
||||
#
|
||||
# A runner is a pod that lives for one job, so a cache on its disk dies with it.
|
||||
# Measured across 188 job logs: not one Go cache was restored, and 142 Go-gate
|
||||
# minutes were 31 downloading modules, 69 compiling and 41 testing. A second
|
||||
# pass over a warm cache compiled hanzoai/commerce's vet in 15s instead of 198s.
|
||||
#
|
||||
# WHERE IT LIVES: the org's own bucket in cloud's object store (/v1/s3), reached
|
||||
# with the IAM identity the KMS step already minted (HANZO_API_TOKEN). cloud
|
||||
# derives the bucket's physical name from that identity's org, so one org can
|
||||
# never read or write another's cache: the tenancy boundary is the server's, not
|
||||
# a prefix chosen here. CI holds no bucket credential. Every object moves over a
|
||||
# URL cloud presigns for that one key, for five minutes.
|
||||
#
|
||||
# THE KEY: <owner/repo>/<go version>-<arch>/<sha256 of go.sum>. Modules are a
|
||||
# function of go.sum, and a build cache is only valid for the toolchain and
|
||||
# architecture that wrote it. A key that misses restores the newest key the repo
|
||||
# saved (`latest`), so a go.sum bump still reuses every module that did not move.
|
||||
#
|
||||
# ONLY A TRUSTED REF WRITES. Go trusts GOMODCACHE and GOCACHE without re-hashing
|
||||
# them, so a cache is code. A pull request reads and never writes; the default
|
||||
# branch and tags write, and only on a miss, so a key names one archive for good.
|
||||
#
|
||||
# PARTS: the store's public name sits behind Cloudflare, which refuses a request
|
||||
# body over 100 MB, so an archive travels as 90 MiB parts listed in an index.
|
||||
#
|
||||
# BEST EFFORT: every failure is a notice and exit 0. A cache that cannot be read
|
||||
# is a cold build, never a red one.
|
||||
#
|
||||
# ENV: HANZO_API_TOKEN the org's IAM bearer (the KMS step exports it)
|
||||
# HANZO_API https://api.hanzo.ai
|
||||
# GOCACHE_BUCKET ci-cache
|
||||
# GOCACHE_PART 94371840 (90 MiB), the size of one uploaded part
|
||||
set -uo pipefail
|
||||
|
||||
cmd="${1:-}"; dir="${2:-.}"
|
||||
base="${HANZO_API:-https://api.hanzo.ai}"
|
||||
bucket="${GOCACHE_BUCKET:-ci-cache}"
|
||||
part="${GOCACHE_PART:-94371840}"
|
||||
out="${GITHUB_OUTPUT:-/dev/null}"
|
||||
|
||||
say() { echo "gocache: $*"; }
|
||||
skip() { echo "::notice::gocache: $*"; echo "hit=miss" >> "$out"; exit 0; }
|
||||
case "$cmd" in restore|save) ;; *) echo "usage: gocache restore|save <module-dir>" >&2; exit 2 ;; esac
|
||||
|
||||
[ -n "${HANZO_API_TOKEN:-}" ] || skip "no HANZO_API_TOKEN (the KMS login did not run), so there is no org store to use"
|
||||
for t in go zstd tar curl jq sha256sum; do
|
||||
command -v "$t" >/dev/null 2>&1 || skip "$t is not on this runner"
|
||||
done
|
||||
[ -f "$dir/go.sum" ] || skip "$dir has no go.sum"
|
||||
|
||||
repo="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY}"
|
||||
mod=$(go env GOMODCACHE); build=$(go env GOCACHE)
|
||||
prefix="$repo/$(go env GOVERSION)-$(go env GOARCH)"
|
||||
sum=$(sha256sum "$dir/go.sum" | cut -c1-32)
|
||||
tmp="${RUNNER_TEMP:-$(mktemp -d)}/gocache"; rm -rf "$tmp"; mkdir -p "$tmp"
|
||||
|
||||
# api <METHOD> <path> [json] -> body on stdout when 2xx. A lost answer, a 429 or
|
||||
# a 5xx is asked again; anything else is the store answering.
|
||||
# Parts move four at a time, so each call answers into a file of its own.
|
||||
api() {
|
||||
local code w f
|
||||
f=$(mktemp -p "$tmp" api.XXXXXX)
|
||||
for w in 0 2 5; do
|
||||
[ "$w" = 0 ] || sleep "$w"
|
||||
code=$(curl -sS -m 30 -o "$f" -w '%{http_code}' -X "$1" "$base$2" \
|
||||
-H "Authorization: Bearer $HANZO_API_TOKEN" -H 'Content-Type: application/json' \
|
||||
${3:+-d "$3"}) || code=000
|
||||
case "$code" in 2*) cat "$f"; rm -f "$f"; return 0 ;; 000|429|5*) continue ;; *) break ;; esac
|
||||
done
|
||||
echo "HTTP $code $(head -c 200 "$f")" >&2
|
||||
rm -f "$f"
|
||||
return 1
|
||||
}
|
||||
|
||||
# get <key> <file>: the object's bytes, or non-zero when it is not there.
|
||||
get() {
|
||||
local url
|
||||
url=$(api GET "/v1/s3/buckets/$bucket/objects/$1" | jq -r '.url // empty') && [ -n "$url" ] || return 1
|
||||
curl -fsS -m 600 -o "$2" "$url"
|
||||
}
|
||||
|
||||
# put <key> <file>: presigned just before it is spent, since it lives five minutes.
|
||||
put() {
|
||||
local url
|
||||
url=$(api POST "/v1/s3/buckets/$bucket/objects" "$(jq -nc --arg k "$1" '{key:$k}')" | jq -r '.url // empty') \
|
||||
&& [ -n "$url" ] || return 1
|
||||
curl -fsS -m 600 -o /dev/null -T "$2" "$url"
|
||||
}
|
||||
|
||||
# fetch <sum>: download and verify the archive an index names; leaves the parts in $tmp.
|
||||
fetch() {
|
||||
local idx="$prefix/$1.idx" hash n i
|
||||
get "$idx" "$tmp/idx" || return 1
|
||||
read -r hash n < "$tmp/idx"
|
||||
case "$n" in ''|*[!0-9]*) return 1 ;; esac
|
||||
for i in $(seq 0 $((n - 1))); do printf '%03d\n' "$i"; done \
|
||||
| xargs -P 4 -I{} bash -c 'get "$0.{}" "$1.{}"' "$prefix/$1.tar.zst" "$tmp/part" || return 1
|
||||
[ "$(cat "$tmp"/part.* | sha256sum | cut -d' ' -f1)" = "$hash" ] || { say "archive $1 does not match its index"; return 1; }
|
||||
}
|
||||
|
||||
if [ "$cmd" = restore ]; then
|
||||
export -f get api; export base bucket tmp HANZO_API_TOKEN
|
||||
t0=$(date +%s)
|
||||
hit=exact
|
||||
if ! fetch "$sum"; then
|
||||
hit=partial
|
||||
rm -f "$tmp"/part.*
|
||||
get "$prefix/latest" "$tmp/latest" && last=$(tr -dc 'a-f0-9' < "$tmp/latest") \
|
||||
&& [ -n "$last" ] && [ "$last" != "$sum" ] && fetch "$last" || {
|
||||
say "nothing saved for $prefix yet"; echo "hit=miss" >> "$out"; exit 0; }
|
||||
fi
|
||||
mkdir -p "$mod" "$build"
|
||||
cat "$tmp"/part.* | zstd -dq -T0 | tar -x --no-same-owner -C "$tmp" || skip "the archive did not unpack"
|
||||
# Moved rather than copied: the pod is fresh, so both targets are empty and the
|
||||
# rename is instant on one filesystem.
|
||||
for d in mod build; do
|
||||
t=$mod; [ "$d" = build ] && t=$build
|
||||
rmdir "$t" 2>/dev/null && mv "$tmp/$d" "$t" || { cp -a "$tmp/$d/." "$t/"; chmod -R u+w "$tmp/$d"; }
|
||||
done
|
||||
bytes=$(cat "$tmp"/part.* | wc -c); rm -rf "$tmp"
|
||||
say "$hit hit for $prefix: $((bytes / 1048576)) MiB in $(( $(date +%s) - t0 ))s"
|
||||
echo "hit=$hit" >> "$out"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# save: a trusted ref, and only when the exact key is not already there.
|
||||
case "${GITHUB_EVENT_NAME:-}" in pull_request*) say "a pull request reads the cache and never writes it"; exit 0 ;; esac
|
||||
default=$(jq -r '.repository.default_branch // empty' "${GITHUB_EVENT_PATH:-/dev/null}" 2>/dev/null || true)
|
||||
if [ "${GITHUB_REF_TYPE:-}" != tag ] && { [ -z "$default" ] || [ "${GITHUB_REF_NAME:-}" != "$default" ]; }; then
|
||||
say "${GITHUB_REF_NAME:-this ref} is not the default branch or a tag, so it does not write"; exit 0
|
||||
fi
|
||||
get "$prefix/$sum.idx" "$tmp/have" 2>/dev/null && { say "$prefix/$sum is already saved"; exit 0; }
|
||||
[ -d "$mod" ] || { say "no module cache to save"; exit 0; }
|
||||
mkdir -p "$build"
|
||||
t0=$(date +%s)
|
||||
api POST /v1/s3/buckets "{\"name\":\"$bucket\"}" >/dev/null 2>&1 || true # 201 made, 409 already there
|
||||
ln -s "$mod" "$tmp/mod"; ln -s "$build" "$tmp/build"
|
||||
tar -c -h -C "$tmp" mod build 2>/dev/null | zstd -q -T0 | split -b "$part" -d -a 3 - "$tmp/part." \
|
||||
|| { say "could not archive the cache"; exit 0; }
|
||||
n=$(ls "$tmp"/part.* | wc -l)
|
||||
hash=$(cat "$tmp"/part.* | sha256sum | cut -d' ' -f1)
|
||||
bytes=$(cat "$tmp"/part.* | wc -c)
|
||||
export -f put api; export base bucket tmp HANZO_API_TOKEN
|
||||
ls "$tmp"/part.* | xargs -P 4 -I{} bash -c 'put "$0.${1##*.}" "$1"' "$prefix/$sum.tar.zst" {} \
|
||||
|| { say "a part did not upload; nothing is indexed, so nothing half-written is ever read"; exit 0; }
|
||||
# The index goes last, so a reader never finds a key whose parts are not all there.
|
||||
printf '%s %s\n' "$hash" "$n" > "$tmp/idx"; printf '%s\n' "$sum" > "$tmp/latest"
|
||||
put "$prefix/$sum.idx" "$tmp/idx" && put "$prefix/latest" "$tmp/latest" || { say "the index did not upload"; exit 0; }
|
||||
say "saved $prefix/$sum: $((bytes / 1048576)) MiB in $n part(s), $(( $(date +%s) - t0 ))s"
|
||||
rm -rf "$tmp"
|
||||
Executable
+127
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tests for bin/gocache. Offline: stub `curl` plays cloud's /v1/s3 (presign) and
|
||||
# the store behind it, stub `go` names the caches, stub `zstd` is `cat`. What is
|
||||
# tested is the protocol — who may write, what a reader trusts, and that a
|
||||
# restored cache is byte-for-byte the saved one — not the network.
|
||||
# Run: bash bin/gocache_test.sh
|
||||
set -uo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
GOCACHE_BIN="$PWD/bin/gocache"
|
||||
fail=0
|
||||
T=$(mktemp -d); trap 'chmod -R u+w "$T"; rm -rf "$T"' EXIT
|
||||
mkdir -p "$T/stub" "$T/store" "$T/repo"
|
||||
|
||||
cat > "$T/stub/curl" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
# The API answers presigned store:// URLs; the store is a directory.
|
||||
o= w= m=GET d= up= url= auth=
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-o) o=$2; shift ;; -w) w=$2; shift ;; -X) m=$2; shift ;; -d) d=$2; shift ;;
|
||||
-T) up=$2; shift ;; -H) case "$2" in Authorization:*) auth=$2 ;; esac; shift ;;
|
||||
-m) shift ;; -*) ;; *) url=$1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
code() { [ -n "$w" ] && printf '%s' "$1"; }
|
||||
case "$url" in
|
||||
store://*)
|
||||
k=${url#store://}
|
||||
if [ -n "$up" ]; then mkdir -p "$STORE/$(dirname "$k")"; cp "$up" "$STORE/$k"; echo "$k" >> "$STORE.puts"; exit 0; fi
|
||||
[ -f "$STORE/$k" ] || exit 22
|
||||
cp "$STORE/$k" "$o"; exit 0 ;;
|
||||
http://api.test/v1/s3/buckets)
|
||||
[ "$auth" = "Authorization: Bearer tok" ] || { code 401; exit 0; }
|
||||
code 201; exit 0 ;;
|
||||
http://api.test/v1/s3/buckets/ci-cache/objects)
|
||||
[ "$auth" = "Authorization: Bearer tok" ] || { code 401; exit 0; }
|
||||
k=$(printf '%s' "$d" | jq -r .key)
|
||||
printf '{"url":"store://%s","method":"PUT"}' "$k" > "$o"; code 200; exit 0 ;;
|
||||
http://api.test/v1/s3/buckets/ci-cache/objects/*)
|
||||
[ "$auth" = "Authorization: Bearer tok" ] || { code 401; exit 0; }
|
||||
printf '{"url":"store://%s","method":"GET"}' "${url#http://api.test/v1/s3/buckets/ci-cache/objects/}" > "$o"; code 200; exit 0 ;;
|
||||
esac
|
||||
code 404
|
||||
EOF
|
||||
cat > "$T/stub/go" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
case "$2" in
|
||||
GOMODCACHE) echo "$HOME/go/pkg/mod" ;; GOCACHE) echo "$HOME/.cache/go-build" ;;
|
||||
GOVERSION) echo go1.27.1 ;; GOARCH) echo amd64 ;;
|
||||
esac
|
||||
EOF
|
||||
printf '#!/bin/sh\nexec cat\n' > "$T/stub/zstd"
|
||||
chmod +x "$T/stub/"*
|
||||
export PATH="$T/stub:$PATH" STORE="$T/store" HANZO_API=http://api.test HANZO_API_TOKEN=tok
|
||||
export GITHUB_REPOSITORY=hanzoai/gateway GOCACHE_PART=4096
|
||||
printf '{"repository":{"default_branch":"main"}}' > "$T/event.json"
|
||||
printf 'example.com/a v1.0.0 h1:x=\n' > "$T/repo/go.sum"
|
||||
|
||||
# A pod: a fresh HOME and temp dir, as every runner job gets.
|
||||
pod() { chmod -R u+w "$T/home" 2>/dev/null; rm -rf "$T/home" "$T/tmp"; mkdir -p "$T/home" "$T/tmp"; export HOME="$T/home" RUNNER_TEMP="$T/tmp"; }
|
||||
# Go's module cache is read-only, which is what a real save and restore meet.
|
||||
warm() {
|
||||
mkdir -p "$HOME/go/pkg/mod/example.com/[email protected]" "$HOME/.cache/go-build/ab"
|
||||
head -c 20000 /dev/urandom > "$HOME/go/pkg/mod/example.com/[email protected]/a.go"
|
||||
head -c 9000 /dev/urandom > "$HOME/.cache/go-build/ab/abcd-d"
|
||||
chmod -R a-w "$HOME/go/pkg/mod"
|
||||
}
|
||||
tree() { (cd "$HOME" && find go .cache/go-build -type f -exec sha256sum {} + 2>/dev/null | sort); }
|
||||
run() { # run <event> <ref> <reftype> <cmd>
|
||||
GITHUB_EVENT_NAME=$1 GITHUB_REF_NAME=$2 GITHUB_REF_TYPE=$3 GITHUB_EVENT_PATH="$T/event.json" \
|
||||
GITHUB_OUTPUT="$T/out" bash "$GOCACHE_BIN" "$4" "$T/repo" > "$T/log" 2>&1
|
||||
}
|
||||
ok() { printf 'ok %s\n' "$1"; }
|
||||
no() { printf 'FAIL %s\n' "$1"; sed 's/^/ /' "$T/log"; fail=1; }
|
||||
hit() { tail -1 "$T/out" 2>/dev/null | sed 's/^hit=//'; }
|
||||
puts() { wc -l < "$STORE.puts" 2>/dev/null || echo 0; }
|
||||
key=hanzoai/gateway/go1.27.1-amd64
|
||||
|
||||
pod; : > "$T/out"; run push main branch restore
|
||||
[ $? = 0 ] && [ "$(hit)" = miss ] && ok "an empty store is a miss, and exit 0" || no "an empty store is a miss, and exit 0"
|
||||
|
||||
pod; warm; run pull_request main branch save
|
||||
[ ! -e "$STORE.puts" ] && ok "a pull request never writes" || no "a pull request never writes"
|
||||
|
||||
pod; warm; run push feature branch save
|
||||
[ ! -e "$STORE.puts" ] && ok "a branch that is not the default never writes" || no "a branch that is not the default never writes"
|
||||
|
||||
pod; warm; want=$(tree); run push main branch save
|
||||
n=$(ls "$STORE/$key/" 2>/dev/null | grep -c '\.tar\.zst\.')
|
||||
[ -f "$STORE/$key/$(sha256sum "$T/repo/go.sum" | cut -c1-32).idx" ] && [ -f "$STORE/$key/latest" ] && [ "$n" -gt 1 ] \
|
||||
&& ok "the default branch writes $n parts, then the index, then latest" || no "the default branch writes parts, index and latest"
|
||||
[ "$(tail -2 "$STORE.puts" | head -1)" = "$key/$(sha256sum "$T/repo/go.sum" | cut -c1-32).idx" ] \
|
||||
&& ok "the index is written after every part" || no "the index is written after every part"
|
||||
|
||||
before=$(puts); pod; warm; run push main branch save
|
||||
[ "$(puts)" = "$before" ] && ok "a key already saved is not written again" || no "a key already saved is not written again"
|
||||
|
||||
pod; run push main branch restore
|
||||
[ "$(hit)" = exact ] && [ "$(tree)" = "$want" ] && ok "a fresh pod restores the exact bytes that were saved" \
|
||||
|| no "a fresh pod restores the exact bytes that were saved"
|
||||
|
||||
pod; : > "$T/out"; HANZO_API_TOKEN=wrong run push main branch restore
|
||||
[ $? = 0 ] && [ "$(hit)" = miss ] && ok "a refused credential is a cold build, not a red one" || no "a refused credential is a cold build, not a red one"
|
||||
|
||||
printf 'example.com/a v1.0.1 h1:y=\n' >> "$T/repo/go.sum"
|
||||
pod; run pull_request feature branch restore
|
||||
[ "$(hit)" = partial ] && [ "$(tree)" = "$want" ] && ok "a go.sum the store has not seen restores the repo's latest" \
|
||||
|| no "a go.sum the store has not seen restores the repo's latest"
|
||||
|
||||
old=$(cat "$STORE/$key/latest"); printf 'x' >> "$STORE/$key/$old.tar.zst.000"
|
||||
pod; : > "$T/out"; run push main branch restore
|
||||
[ $? = 0 ] && [ "$(hit)" = miss ] && [ -z "$(tree)" ] && ok "an archive that does not match its index is never unpacked" \
|
||||
|| no "an archive that does not match its index is never unpacked"
|
||||
|
||||
pod; : > "$T/out"; HANZO_API_TOKEN= run push main branch restore
|
||||
[ $? = 0 ] && [ "$(hit)" = miss ] && ok "no identity is a cold build" || no "no identity is a cold build"
|
||||
|
||||
# The workflow wires it: restore before the gate, save after it, both reading
|
||||
# the module the Go provisioning found.
|
||||
WF=.github/workflows/build.yml
|
||||
grep -q '"$CI_HOME/bin/gocache" restore' "$WF" && grep -q '"$CI_HOME/bin/gocache" save' "$WF" \
|
||||
&& ok "build.yml restores and saves through bin/gocache" || { printf 'FAIL build.yml restores and saves through bin/gocache\n'; fail=1; }
|
||||
grep -q 'cache: false' "$WF" && ! grep -q 'cache: ${{ inputs.go-cache' "$WF" \
|
||||
&& ok "setup-go's own cache (GitHub's store) is off" || { printf "FAIL setup-go's own cache (GitHub's store) is off\n"; fail=1; }
|
||||
|
||||
exit $fail
|
||||
@@ -422,6 +422,15 @@ test:
|
||||
# would have worked, and those are the ones people learn to route around.
|
||||
# Offline and deterministic: SITE_PLAN prints the manifest, no network.
|
||||
run: bash bin/site_test.sh
|
||||
- name: gocache
|
||||
# bin/gocache carries every Go repo's module and build cache between
|
||||
# runners, and Go trusts what it restores without re-hashing it. So the suite
|
||||
# pins who may write (the default branch and tags, never a pull request),
|
||||
# that an index is written only after all of its parts, that an archive not
|
||||
# matching its index is never unpacked, and that a refused or absent
|
||||
# identity is a cold build rather than a red one. Offline: a stub curl plays
|
||||
# cloud's presigning /v1/s3 and the store behind it.
|
||||
run: bash bin/gocache_test.sh
|
||||
- name: conflictmarkers
|
||||
# bin/conflictmarkers runs against every repo in the fleet, so a false
|
||||
# positive is as expensive as a miss: it blocks a build that would have
|
||||
|
||||
Reference in New Issue
Block a user