Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/build-push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ on:
type: number
default: 30
description: "Bounded timeout for each native image build; use 5 through 120 minutes"
reclaim-runner-disk:
required: false
type: boolean
default: false
description: "Remove unused preinstalled SDKs from each ephemeral hosted runner before an unusually large build"
runners:
required: false
type: string
Expand Down Expand Up @@ -231,6 +236,51 @@ jobs:
fi
} >> "$GITHUB_OUTPUT"

- name: Reclaim hosted runner disk space
if: inputs.reclaim-runner-disk
timeout-minutes: 10
shell: bash
run: |
set -euo pipefail
df -h /
sudo apt-get remove -y \
'^aspnetcore-.*' \
'^dotnet-.*' \
'^llvm-.*' \
'php.*' \
'^mongodb-.*' \
'^mysql-.*' \
azure-cli \
firefox \
google-chrome-stable \
google-cloud-cli \
google-cloud-sdk \
microsoft-edge-stable \
mono-devel \
powershell \
snapd \
--fix-missing 2>/dev/null || true
sudo apt-get autoremove -y 2>/dev/null || true
sudo apt-get clean 2>/dev/null || true
sudo docker image prune --all --force 2>/dev/null || true
sudo rm -rf \
/opt/google/chrome \
/opt/hostedtoolcache \
/opt/microsoft/msedge \
/opt/microsoft/powershell \
/opt/pipx \
/usr/lib/mono \
/usr/local/julia* \
/usr/local/lib/android \
/usr/local/lib/node_modules \
/usr/local/share/chromium \
/usr/local/share/powershell \
/usr/share/dotnet \
/usr/share/swift \
2>/dev/null || true
ghcup nuke 2>/dev/null || true
df -h /

- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
ref: ${{ inputs.ref }}
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ An always-run cleanup job retains all run-scoped staging tags when a build or me

Builds always run on the fixed `ubuntu-24.04` and `ubuntu-24.04-arm` GitHub-hosted runners, and the merge always consumes the fixed `amd64` and `arm64` platforms. The deprecated `runners` input remains for caller compatibility, but only its default pair is accepted; it cannot select a self-hosted or arbitrary runner with publisher credentials.

Set `reclaim-runner-disk: true` only for source builds whose compiler and BuildKit working sets exhaust the default hosted-runner disk. The opt-in step removes unused preinstalled SDKs and tool caches from each ephemeral runner before checkout; later setup actions must therefore install any required host tools again. It does not change the fixed runner pair, registry credentials, scan gate, or publication policy.

### Caller secrets

The reusable workflow declares four optional secrets so callers can pass only what their registry needs. GHCR uses the caller's automatic `GITHUB_TOKEN` and needs none of these secrets. GAR callers explicitly map `GCLOUD_OIDC_POOL` and `GSA`; Docker Hub callers explicitly map `DOCKERHUB_USER` and `DOCKERHUB_PASSWORD`. Do not use `secrets: inherit`.
Expand Down
47 changes: 47 additions & 0 deletions ci/github/build_push_contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,53 @@ func TestNativeBuildTimeoutIsBoundedCallerInput(t *testing.T) {
requireContains(t, validation, "BUILD_TIMEOUT_MINUTES < 5 || BUILD_TIMEOUT_MINUTES > 120")
}

func TestOptionalRunnerDiskReclamationIsExplicitAndScoped(t *testing.T) {
workflow := workflowSource(t)
inputs := stepBlock(t, workflow, " inputs:\n", " secrets:\n")
reclaim := stepBlock(t, workflow, " - name: Reclaim hosted runner disk space\n", " - uses: actions/checkout@")

for _, required := range []string{
"reclaim-runner-disk:\n",
"type: boolean",
"default: false",
} {
requireContains(t, inputs, required)
}
for _, required := range []string{
"if: inputs.reclaim-runner-disk",
"timeout-minutes: 10",
"set -euo pipefail",
"sudo apt-get remove -y",
"sudo apt-get autoremove -y",
"sudo apt-get clean",
"sudo docker image prune --all --force",
"/opt/hostedtoolcache",
"/usr/local/lib/android",
"/usr/local/lib/node_modules",
"/usr/share/dotnet",
"/usr/share/swift",
"ghcup nuke",
"df -h /",
} {
requireContains(t, reclaim, required)
}
if got := strings.Count(workflow, "if: inputs.reclaim-runner-disk"); got != 1 {
t.Fatalf("runner-disk reclamation guard appears %d times, want exactly one native-build guard", got)
}
if strings.Contains(reclaim, "uses:") {
t.Error("runner-disk reclamation must not delegate host deletion to a third-party action")
}
if strings.Contains(reclaim, "$AGENT_TOOLSDIRECTORY") {
t.Error("runner-disk reclamation paths must be a fixed allowlist")
}
validationIndex := strings.Index(workflow, " - name: validate input\n")
reclaimIndex := strings.Index(workflow, " - name: Reclaim hosted runner disk space\n")
checkoutIndex := strings.Index(workflow, " - uses: actions/checkout@")
if validationIndex == -1 || reclaimIndex == -1 || checkoutIndex == -1 || !(validationIndex < reclaimIndex && reclaimIndex < checkoutIndex) {
t.Fatal("runner-disk reclamation must run after validation and before checkout")
}
}

func TestDefaultImageNameReceivesCanonicalValidation(t *testing.T) {
workflow := workflowSource(t)
validation := stepBlock(t, workflow, " - name: validate input\n", " - uses: actions/checkout@")
Expand Down