ci: stop disk reclaim from deleting a smoke image mid-test
v1.12.2 run 740: build-variant's `docker system prune -af --volumes` untagged pi-devbox:smoke-studio 30 ms after smoke-studio loaded it, so every studio assertion failed and the -studio tags were not published. All jobs share the runner's Docker daemon, and a smoke image is unused between smoke-test.sh's one-shot `docker run`s. - scripts/ci-reclaim-docker.sh: the same reclaim, per object type, but never prunes an image labelled se.jordbo.pi-devbox.ci-smoke-image; labelled orphans are swept after 6h. `assert-protected IMG` is the smoke preflight; `lint WORKFLOW...` rejects raw system/image prunes. - smoke jobs: label their image, assert it, tag it per run (pi-devbox:smoke[-studio]-<run_id>), remove it in an always() step. - all five reclaim sites call the script; lint.yml runs the guard. - Job order unchanged: studio jobs still gate only the -studio tags. Verified on an isolated docker:29-dind (old prune deletes a labelled image; new reclaim keeps it, removes unlabelled + 1970-created labelled images, spares an in-use image, no errors); actionlint, shellcheck, lint-shell, check-workflow-shell, check-doc-drift all clean.
This commit is contained in:
@@ -90,6 +90,12 @@ env:
|
|||||||
IMAGE: ${{ vars.DOCKERHUB_USERNAME }}/pi-devbox
|
IMAGE: ${{ vars.DOCKERHUB_USERNAME }}/pi-devbox
|
||||||
RELEASE_TAG: ${{ github.ref_type == 'tag' && github.ref_name || inputs.release_tag }}
|
RELEASE_TAG: ${{ github.ref_type == 'tag' && github.ref_name || inputs.release_tag }}
|
||||||
PROMOTE_LATEST: ${{ github.ref_type == 'tag' && 'true' || inputs.promote_latest }}
|
PROMOTE_LATEST: ${{ github.ref_type == 'tag' && 'true' || inputs.promote_latest }}
|
||||||
|
# Label that protects a smoke job's loaded image from the disk reclaim every
|
||||||
|
# job runs on the shared runner daemon (scripts/ci-reclaim-docker.sh says
|
||||||
|
# why: v1.12.2 run 740 lost pi-devbox:smoke-studio to build-variant's prune
|
||||||
|
# 30 ms after loading it). Set once here; the smoke builds apply it, the
|
||||||
|
# reclaim script honours it, the smoke preflight asserts it.
|
||||||
|
SMOKE_IMAGE_LABEL: se.jordbo.pi-devbox.ci-smoke-image
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ── Phase 1: decide whether base needs rebuilding ──────────────────
|
# ── Phase 1: decide whether base needs rebuilding ──────────────────
|
||||||
@@ -538,8 +544,7 @@ jobs:
|
|||||||
/usr/lib/jvm 2>/dev/null || true
|
/usr/lib/jvm 2>/dev/null || true
|
||||||
apt-get clean || true
|
apt-get clean || true
|
||||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* || true
|
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* || true
|
||||||
docker system prune -af --volumes || true
|
bash scripts/ci-reclaim-docker.sh
|
||||||
docker builder prune -af || true
|
|
||||||
df -h / || true
|
df -h / || true
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
@@ -597,6 +602,10 @@ jobs:
|
|||||||
needs.resolve-versions.result == 'success' &&
|
needs.resolve-versions.result == 'success' &&
|
||||||
(needs.build-base.result == 'success' || needs.build-base.result == 'skipped')
|
(needs.build-base.result == 'success' || needs.build-base.result == 'skipped')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
# Per run, so two runs on different refs cannot test or delete each
|
||||||
|
# other's image on the shared daemon. run_id is stable across re-runs.
|
||||||
|
SMOKE_IMAGE: pi-devbox:smoke-${{ github.run_id }}
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: catthehacker/ubuntu:act-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -610,8 +619,7 @@ jobs:
|
|||||||
/usr/local/lib/android /usr/local/share/powershell \
|
/usr/local/lib/android /usr/local/share/powershell \
|
||||||
/usr/local/share/chromium /usr/local/share/boost \
|
/usr/local/share/chromium /usr/local/share/boost \
|
||||||
/usr/lib/jvm 2>/dev/null || true
|
/usr/lib/jvm 2>/dev/null || true
|
||||||
docker system prune -af --volumes || true
|
bash scripts/ci-reclaim-docker.sh
|
||||||
docker builder prune -af || true
|
|
||||||
- uses: docker/setup-buildx-action@v4
|
- uses: docker/setup-buildx-action@v4
|
||||||
with: {driver-opts: network=host}
|
with: {driver-opts: network=host}
|
||||||
- uses: docker/login-action@v3
|
- uses: docker/login-action@v3
|
||||||
@@ -626,7 +634,8 @@ jobs:
|
|||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: false
|
push: false
|
||||||
load: true
|
load: true
|
||||||
tags: pi-devbox:smoke
|
tags: ${{ env.SMOKE_IMAGE }}
|
||||||
|
labels: ${{ env.SMOKE_IMAGE_LABEL }}=true
|
||||||
build-args: |
|
build-args: |
|
||||||
BASE_IMAGE=${{ env.IMAGE }}:${{ needs.base-decide.outputs.base_tag }}
|
BASE_IMAGE=${{ env.IMAGE }}:${{ needs.base-decide.outputs.base_tag }}
|
||||||
PI_VERSION=${{ needs.resolve-versions.outputs.pi_version }}
|
PI_VERSION=${{ needs.resolve-versions.outputs.pi_version }}
|
||||||
@@ -648,7 +657,14 @@ jobs:
|
|||||||
# Asserting the BUILT image matches it also catches a stale cached layer.
|
# Asserting the BUILT image matches it also catches a stale cached layer.
|
||||||
EXPECTED_NODE_MAJOR=$(sed -n 's/^ARG NODE_VERSION=\([0-9][0-9]*\).*/\1/p' Dockerfile.base)
|
EXPECTED_NODE_MAJOR=$(sed -n 's/^ARG NODE_VERSION=\([0-9][0-9]*\).*/\1/p' Dockerfile.base)
|
||||||
export EXPECTED_NODE_MAJOR
|
export EXPECTED_NODE_MAJOR
|
||||||
bash scripts/smoke-test.sh pi-devbox:smoke
|
bash scripts/ci-reclaim-docker.sh assert-protected "$SMOKE_IMAGE"
|
||||||
|
bash scripts/smoke-test.sh "$SMOKE_IMAGE"
|
||||||
|
# The label keeps this image from every job's reclaim, so it is removed
|
||||||
|
# here instead. A job killed before this step leaves an orphan, which the
|
||||||
|
# reclaim's age sweep deletes once it is older than SMOKE_IMAGE_MAX_AGE.
|
||||||
|
- name: Remove smoke image
|
||||||
|
if: always()
|
||||||
|
run: docker image rm "$SMOKE_IMAGE" || true
|
||||||
|
|
||||||
# ── Phase 3b: amd64 smoke for the studio variant ────────────────────
|
# ── Phase 3b: amd64 smoke for the studio variant ────────────────────
|
||||||
# Additive + independent of the core `smoke` job: gates ONLY
|
# Additive + independent of the core `smoke` job: gates ONLY
|
||||||
@@ -662,6 +678,10 @@ jobs:
|
|||||||
needs.resolve-versions.result == 'success' &&
|
needs.resolve-versions.result == 'success' &&
|
||||||
(needs.build-base.result == 'success' || needs.build-base.result == 'skipped')
|
(needs.build-base.result == 'success' || needs.build-base.result == 'skipped')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
# Per run, so two runs on different refs cannot test or delete each
|
||||||
|
# other's image on the shared daemon. run_id is stable across re-runs.
|
||||||
|
SMOKE_IMAGE: pi-devbox:smoke-studio-${{ github.run_id }}
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: catthehacker/ubuntu:act-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -675,8 +695,7 @@ jobs:
|
|||||||
/usr/local/lib/android /usr/local/share/powershell \
|
/usr/local/lib/android /usr/local/share/powershell \
|
||||||
/usr/local/share/chromium /usr/local/share/boost \
|
/usr/local/share/chromium /usr/local/share/boost \
|
||||||
/usr/lib/jvm 2>/dev/null || true
|
/usr/lib/jvm 2>/dev/null || true
|
||||||
docker system prune -af --volumes || true
|
bash scripts/ci-reclaim-docker.sh
|
||||||
docker builder prune -af || true
|
|
||||||
- uses: docker/setup-buildx-action@v4
|
- uses: docker/setup-buildx-action@v4
|
||||||
with: {driver-opts: network=host}
|
with: {driver-opts: network=host}
|
||||||
- uses: docker/login-action@v3
|
- uses: docker/login-action@v3
|
||||||
@@ -691,7 +710,8 @@ jobs:
|
|||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: false
|
push: false
|
||||||
load: true
|
load: true
|
||||||
tags: pi-devbox:smoke-studio
|
tags: ${{ env.SMOKE_IMAGE }}
|
||||||
|
labels: ${{ env.SMOKE_IMAGE_LABEL }}=true
|
||||||
build-args: |
|
build-args: |
|
||||||
BASE_IMAGE=${{ env.IMAGE }}:${{ needs.base-decide.outputs.base_tag }}
|
BASE_IMAGE=${{ env.IMAGE }}:${{ needs.base-decide.outputs.base_tag }}
|
||||||
PI_VERSION=${{ needs.resolve-versions.outputs.pi_version }}
|
PI_VERSION=${{ needs.resolve-versions.outputs.pi_version }}
|
||||||
@@ -716,7 +736,14 @@ jobs:
|
|||||||
# Asserting the BUILT image matches it also catches a stale cached layer.
|
# Asserting the BUILT image matches it also catches a stale cached layer.
|
||||||
EXPECTED_NODE_MAJOR=$(sed -n 's/^ARG NODE_VERSION=\([0-9][0-9]*\).*/\1/p' Dockerfile.base)
|
EXPECTED_NODE_MAJOR=$(sed -n 's/^ARG NODE_VERSION=\([0-9][0-9]*\).*/\1/p' Dockerfile.base)
|
||||||
export EXPECTED_NODE_MAJOR
|
export EXPECTED_NODE_MAJOR
|
||||||
bash scripts/smoke-test.sh pi-devbox:smoke-studio
|
bash scripts/ci-reclaim-docker.sh assert-protected "$SMOKE_IMAGE"
|
||||||
|
bash scripts/smoke-test.sh "$SMOKE_IMAGE"
|
||||||
|
# The label keeps this image from every job's reclaim, so it is removed
|
||||||
|
# here instead. A job killed before this step leaves an orphan, which the
|
||||||
|
# reclaim's age sweep deletes once it is older than SMOKE_IMAGE_MAX_AGE.
|
||||||
|
- name: Remove smoke image
|
||||||
|
if: always()
|
||||||
|
run: docker image rm "$SMOKE_IMAGE" || true
|
||||||
|
|
||||||
# ── Phase 4: multi-arch publish ─────────────────────────────────────
|
# ── Phase 4: multi-arch publish ─────────────────────────────────────
|
||||||
build-variant:
|
build-variant:
|
||||||
@@ -741,8 +768,7 @@ jobs:
|
|||||||
/usr/local/lib/android /usr/local/share/powershell \
|
/usr/local/lib/android /usr/local/share/powershell \
|
||||||
/usr/local/share/chromium /usr/local/share/boost \
|
/usr/local/share/chromium /usr/local/share/boost \
|
||||||
/usr/lib/jvm 2>/dev/null || true
|
/usr/lib/jvm 2>/dev/null || true
|
||||||
docker system prune -af --volumes || true
|
bash scripts/ci-reclaim-docker.sh
|
||||||
docker builder prune -af || true
|
|
||||||
- uses: docker/setup-qemu-action@v3
|
- uses: docker/setup-qemu-action@v3
|
||||||
with: {platforms: arm64}
|
with: {platforms: arm64}
|
||||||
- uses: docker/setup-buildx-action@v4
|
- uses: docker/setup-buildx-action@v4
|
||||||
@@ -834,8 +860,7 @@ jobs:
|
|||||||
/usr/local/lib/android /usr/local/share/powershell \
|
/usr/local/lib/android /usr/local/share/powershell \
|
||||||
/usr/local/share/chromium /usr/local/share/boost \
|
/usr/local/share/chromium /usr/local/share/boost \
|
||||||
/usr/lib/jvm 2>/dev/null || true
|
/usr/lib/jvm 2>/dev/null || true
|
||||||
docker system prune -af --volumes || true
|
bash scripts/ci-reclaim-docker.sh
|
||||||
docker builder prune -af || true
|
|
||||||
- uses: docker/setup-qemu-action@v3
|
- uses: docker/setup-qemu-action@v3
|
||||||
with: {platforms: arm64}
|
with: {platforms: arm64}
|
||||||
- uses: docker/setup-buildx-action@v4
|
- uses: docker/setup-buildx-action@v4
|
||||||
|
|||||||
@@ -90,6 +90,14 @@ jobs:
|
|||||||
# actionlint so the more precise diagnostic surfaces first.
|
# actionlint so the more precise diagnostic surfaces first.
|
||||||
run: bash scripts/check-workflow-shell.sh .gitea/workflows
|
run: bash scripts/check-workflow-shell.sh .gitea/workflows
|
||||||
|
|
||||||
|
- name: No raw docker prune in workflows (shared runner daemon)
|
||||||
|
# Every docker-publish.yml job shares the runner's Docker daemon, and
|
||||||
|
# a bare `docker system prune -a` in one job deletes the smoke image
|
||||||
|
# another job is testing (v1.12.2 run 740 shipped without its -studio
|
||||||
|
# tags that way). Reclaim goes through scripts/ci-reclaim-docker.sh,
|
||||||
|
# which spares labelled smoke images; this keeps it that way.
|
||||||
|
run: bash scripts/ci-reclaim-docker.sh lint .gitea/workflows/*.yml
|
||||||
|
|
||||||
- name: link-pi-models.sh contract (two-sided, no docker needed)
|
- name: link-pi-models.sh contract (two-sided, no docker needed)
|
||||||
# The entrypoint links ~/.pi/agent/models.json to a mounted shared file
|
# The entrypoint links ~/.pi/agent/models.json to a mounted shared file
|
||||||
# and must never replace a real file or a user's own link. Tested here
|
# and must never replace a real file or a user's own link. Tested here
|
||||||
|
|||||||
@@ -45,6 +45,13 @@ re-brand of opencode-devbox's `pi-only` variant.
|
|||||||
update-description). The `-studio` variant adds independent
|
update-description). The `-studio` variant adds independent
|
||||||
`smoke-studio` + `build-variant-studio` jobs that gate only the
|
`smoke-studio` + `build-variant-studio` jobs that gate only the
|
||||||
`-studio` tags (never the core `:latest` release).
|
`-studio` tags (never the core `:latest` release).
|
||||||
|
- `scripts/ci-reclaim-docker.sh` — the ONLY way a CI job frees disk. All jobs
|
||||||
|
share the runner's Docker daemon, so a bare `docker system prune -a` in one
|
||||||
|
job deletes the smoke image another job is mid-test on (v1.12.2 run 740 lost
|
||||||
|
its `-studio` tags that way). The script spares images labelled
|
||||||
|
`se.jordbo.pi-devbox.ci-smoke-image`; the smoke jobs apply that label,
|
||||||
|
assert it (`assert-protected`) and remove their own image at the end.
|
||||||
|
`lint.yml` fails on any raw `docker system|image prune` in a workflow.
|
||||||
|
|
||||||
## Versioning scheme
|
## Versioning scheme
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,41 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **CI: a smoke job's image could be deleted mid-test by another job's disk
|
||||||
|
reclaim.** This is why v1.12.2's `-studio` tags were not published by its
|
||||||
|
tag run. All `docker-publish.yml` jobs share the runner's Docker daemon, and
|
||||||
|
each began with `docker system prune -af --volumes`, which removes every
|
||||||
|
image that no container is using at that moment. A smoke image is unused
|
||||||
|
between `smoke-test.sh`'s one-shot `docker run`s. In run 740, `smoke`
|
||||||
|
passed and `build-variant` started. Its prune logged
|
||||||
|
`untagged: pi-devbox:smoke-studio` at 22:58:49.11Z, 30 ms after
|
||||||
|
`smoke-studio` had loaded that image. Every studio assertion then failed with
|
||||||
|
"Unable to find image … locally", and `build-variant-studio` was skipped.
|
||||||
|
`v1.12.2` and `latest` were published; `v1.12.2-studio` and
|
||||||
|
`latest-studio` were not, until the failed jobs were re-run. Now every job
|
||||||
|
reclaims disk through the new `scripts/ci-reclaim-docker.sh`. It does the
|
||||||
|
same work as before (stopped containers, unused images, networks, anonymous
|
||||||
|
volumes, build cache) but never prunes an image labelled
|
||||||
|
`se.jordbo.pi-devbox.ci-smoke-image`. The smoke builds apply that label, a
|
||||||
|
preflight fails if it is missing, and each smoke job removes its own image at
|
||||||
|
the end. Smoke images left by a killed job are swept once they are older
|
||||||
|
than 6 h. Smoke tags carry the run id (`pi-devbox:smoke-<run_id>`), so two
|
||||||
|
runs on different refs cannot test or delete each other's image. `lint.yml`
|
||||||
|
fails on any raw `docker system|image prune` in a workflow. Job order is
|
||||||
|
unchanged: the studio jobs still gate only the `-studio` tags. Reordering
|
||||||
|
could not have closed the race anyway, since the two smoke jobs also prune
|
||||||
|
each other whenever their start times drift. Measured 2026-10-23 on an
|
||||||
|
isolated `docker:29-dind` (engine 29.9.2; the runner has 29.7.2). The old
|
||||||
|
prune deletes an unused labelled image (the bug). The new reclaim keeps a
|
||||||
|
fresh labelled image, deletes an unlabelled one and a labelled one created
|
||||||
|
in 1970, keeps an image a running container uses, and prints no errors.
|
||||||
|
`assert-protected` passes the labelled image and fails both an unlabelled
|
||||||
|
and a missing one.
|
||||||
|
|
||||||
## v1.12.2 — 2026-10-22
|
## v1.12.2 — 2026-10-22
|
||||||
|
|
||||||
Patch release. Headline: **pi `1.4.0`** (audited, see *Changed* below), plus
|
Patch release. Headline: **pi `1.4.0`** (audited, see *Changed* below), plus
|
||||||
|
|||||||
Executable
+104
@@ -0,0 +1,104 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ci-reclaim-docker.sh — free disk on the CI runner's Docker daemon WITHOUT
|
||||||
|
# deleting a smoke image that another job is still testing.
|
||||||
|
#
|
||||||
|
# Used by every docker-publish.yml job that reclaims disk (build-base, smoke,
|
||||||
|
# smoke-studio, build-variant, build-variant-studio). Not shipped in the image.
|
||||||
|
#
|
||||||
|
# bash scripts/ci-reclaim-docker.sh # reclaim (default)
|
||||||
|
# bash scripts/ci-reclaim-docker.sh assert-protected IMG # smoke preflight
|
||||||
|
# bash scripts/ci-reclaim-docker.sh lint WORKFLOW... # lint.yml guard
|
||||||
|
#
|
||||||
|
# WHY THIS EXISTS. The jobs share ONE Docker daemon (the runner's socket), and
|
||||||
|
# each used to start with `docker system prune -af --volumes`. That removes
|
||||||
|
# every image no container is using at that instant, and a smoke image is
|
||||||
|
# exactly that for most of its life: smoke-test.sh runs one `docker run --rm`
|
||||||
|
# per assertion, so between assertions nothing holds the image. v1.12.2 run 740
|
||||||
|
# (2026-10-22): smoke passed, build-variant started and pruned, and its log
|
||||||
|
# shows `untagged: pi-devbox:smoke-studio` at 22:58:49.11Z, 30 ms after
|
||||||
|
# smoke-studio had imported that image (22:58:49.08Z). Every smoke-studio
|
||||||
|
# assertion then failed with "Unable to find image 'pi-devbox:smoke-studio'
|
||||||
|
# locally", build-variant-studio was skipped, and the release went out without
|
||||||
|
# its -studio tags. Job ORDER cannot close this: smoke and smoke-studio prune
|
||||||
|
# each other too whenever their start times drift apart, and other runs share
|
||||||
|
# the daemon. So the smoke images carry a label, and this script never prunes
|
||||||
|
# an image that has it.
|
||||||
|
#
|
||||||
|
# WHAT IT DOES. The same reclaim as `docker system prune -af --volumes` +
|
||||||
|
# `docker builder prune -af`, done per object type so the image step can take
|
||||||
|
# a filter:
|
||||||
|
# 1. stopped containers (running ones are never touched)
|
||||||
|
# 2. unused images WITHOUT the label (the old `system prune -a` image step)
|
||||||
|
# 3. unused images WITH the label, older than SMOKE_IMAGE_MAX_AGE
|
||||||
|
# — orphans of a smoke job that died before its own cleanup step
|
||||||
|
# 4. unused networks, anonymous volumes (what `system prune --volumes` did)
|
||||||
|
# 5. the build cache
|
||||||
|
# Each step is `|| true`, as before: failing to reclaim disk must not fail the
|
||||||
|
# job. A filter the daemon rejected would show up as an error line in the log.
|
||||||
|
#
|
||||||
|
# `assert-protected IMG` is the smoke jobs' preflight: it fails unless IMG is in
|
||||||
|
# the daemon AND carries the label. Without it, a build that silently lost the
|
||||||
|
# label would look fine until the next time a sibling job's prune landed
|
||||||
|
# mid-test; and a missing image gets one clear error line instead of every
|
||||||
|
# assertion failing with "Unable to find image ... locally".
|
||||||
|
#
|
||||||
|
# `lint WORKFLOW...` fails if any workflow runs `docker system prune` or
|
||||||
|
# `docker image prune` itself. One such line in any job reopens the race, so
|
||||||
|
# lint.yml runs this on every push rather than relying on review.
|
||||||
|
#
|
||||||
|
# Environment:
|
||||||
|
# SMOKE_IMAGE_LABEL label key that protects an image (default below; the
|
||||||
|
# workflow sets it once in its top-level env)
|
||||||
|
# SMOKE_IMAGE_MAX_AGE `until=` value for the orphan sweep (default 6h; a
|
||||||
|
# smoke job takes ~10 min). Compared against the image's
|
||||||
|
# Created time, which is the build time: nothing here
|
||||||
|
# sets SOURCE_DATE_EPOCH.
|
||||||
|
set -u
|
||||||
|
|
||||||
|
SMOKE_IMAGE_LABEL="${SMOKE_IMAGE_LABEL:-se.jordbo.pi-devbox.ci-smoke-image}"
|
||||||
|
SMOKE_IMAGE_MAX_AGE="${SMOKE_IMAGE_MAX_AGE:-6h}"
|
||||||
|
|
||||||
|
step() { printf '+ %s\n' "$*"; "$@" || true; }
|
||||||
|
|
||||||
|
reclaim() {
|
||||||
|
step docker container prune -f
|
||||||
|
step docker image prune -af --filter "label!=${SMOKE_IMAGE_LABEL}"
|
||||||
|
step docker image prune -af --filter "label=${SMOKE_IMAGE_LABEL}" \
|
||||||
|
--filter "until=${SMOKE_IMAGE_MAX_AGE}"
|
||||||
|
step docker network prune -f
|
||||||
|
step docker volume prune -f
|
||||||
|
step docker builder prune -af
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_protected() {
|
||||||
|
local img="${1:?usage: $0 assert-protected <image>}" val
|
||||||
|
if ! val=$(docker image inspect \
|
||||||
|
--format "{{ index .Config.Labels \"${SMOKE_IMAGE_LABEL}\" }}" "$img" 2>&1); then
|
||||||
|
echo "::error::${img} is not in the runner's Docker daemon (${val}). Was it removed by a concurrent prune that does not honour ${SMOKE_IMAGE_LABEL}?"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ "$val" != "true" ]; then
|
||||||
|
echo "::error::${img} lacks the label ${SMOKE_IMAGE_LABEL}=true (got '${val}'), so a sibling job's reclaim could delete it mid-test. Check the build step's labels: input."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "OK: ${img} is present and carries ${SMOKE_IMAGE_LABEL}=true"
|
||||||
|
}
|
||||||
|
|
||||||
|
lint_workflows() {
|
||||||
|
[ "$#" -gt 0 ] || { echo "usage: $0 lint <workflow.yml>..." >&2; return 2; }
|
||||||
|
local hits
|
||||||
|
hits=$(grep -nE '^[^#]*docker[[:space:]]+(system|image)[[:space:]]+prune' "$@" || true)
|
||||||
|
if [ -n "$hits" ]; then
|
||||||
|
echo "::error::raw docker prune in a workflow. It deletes smoke images other jobs are testing on the shared runner daemon; call 'bash scripts/ci-reclaim-docker.sh' instead."
|
||||||
|
printf '%s\n' "$hits"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "OK: no raw 'docker system|image prune' in $# workflow file(s)"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-reclaim}" in
|
||||||
|
reclaim) reclaim ;;
|
||||||
|
assert-protected) shift; assert_protected "$@" ;;
|
||||||
|
lint) shift; lint_workflows "$@" ;;
|
||||||
|
*) echo "usage: $0 [reclaim | assert-protected <image> | lint <workflow.yml>...]" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user