ci: no image build on push — split cheap checks from builds
A push to main used to trigger validate.yml, which builds an amd64 variant and
runs the smoke test. Pushing work-in-progress to main therefore cost a build.
Restructure so the three workflows divide by cost, matching pi-devbox:
lint.yml cheap checks, every push/PR — the ONLY workflow a
push to main triggers
validate.yml amd64 build + smoke test — pull_request and
workflow_dispatch only, no push trigger at all
docker-publish-split.yml the release path, tag-only (unchanged)
docs-check (DOCKER_HUB.md vs HUB_TEMPLATE) moves from validate.yml to lint.yml.
It needs no image, and anything that needs no image belongs in the workflow that
actually runs on push — otherwise the doc-drift guard would have been silently
lost when validate.yml stopped running on pushes. That guard earns its keep: it
has caught real drift.
validate-base/validate-omos additionally keep a `github.event_name != 'push'`
clause. It is redundant now that the trigger is gone, and deliberately so:
re-adding a push trigger later cannot silently re-enable builds on every push.
Renamed lint.yml `Lint workflows` -> `Lint`, since it now covers Dockerfiles and
docs as well as workflows. No references to the old name existed.
Safe because the release path already fails closed: docker-publish-split.yml
pushes variant tags only after smoke-base/smoke-omos pass and promotes
base-latest last, so an aborted release leaves at worst an unreferenced
base-<hash> blob on Hub — never a half-published version tag. Pre-tag
validation remains available three ways: open a PR, dispatch Validate, or
dispatch docker-publish-split.yml against a throwaway tag with
promote_latest=false — the only route that also exercises a CHANGED base, which
validate.yml structurally cannot (it builds variants on Hub's base-latest).
Coverage lost is narrower than it looks: validate-base/validate-omos were
already skipped whenever a commit touched Dockerfile.base, rootfs/, or
entrypoint*.sh, so they only ever ran for variant-only changes — most usefully a
bare OPENCODE_VERSION bump, for which an explicit dispatch is now the
equivalent.
Verified with the CI-pinned actionlint 1.7.7 and scripts/check-workflow-shell.sh.
AGENTS.md updated in the same commit (file roles for both workflows + the
trigger-model convention). The CHANGELOG entry lands with the v2.9.0 changeset
in the following commit, which is one unreleased block covering all of v2.9.0.
This commit is contained in:
@@ -1,13 +1,20 @@
|
||||
name: Lint workflows
|
||||
name: Lint
|
||||
|
||||
# Durable guard against CI-workflow bugs — most importantly the "bash-only
|
||||
# syntax under the default `sh`/dash shell" footgun. Ported from pi-devbox,
|
||||
# where this class broke resolve-versions (ed49b8d) and promote-base-latest
|
||||
# (b7197e8 → run 418). actionlint runs shellcheck against each `run:` step
|
||||
# using its *effective* shell, so `set -o pipefail` under dash is flagged as
|
||||
# SC3040 before any expensive build runs. This is cheap (~10s) and independent
|
||||
# of the build pipeline, so it fires on every push/PR — not just on release
|
||||
# tags, which is where docker-publish-split.yml is otherwise only triggered.
|
||||
# The repo's cheap-checks workflow: everything that can be verified WITHOUT
|
||||
# building an image. Runs on every push and PR, in ~30s, independent of the
|
||||
# build pipeline. Three jobs: workflow lint (actionlint + the Gitea shell
|
||||
# guard), Dockerfile lint (hadolint), and the DOCKER_HUB.md docs-sync check.
|
||||
#
|
||||
# Deliberately the ONLY workflow that runs on a push to main. validate.yml
|
||||
# (amd64 build + smoke) is PR/dispatch-only and docker-publish-split.yml is
|
||||
# tag-only, so pushing work-in-progress to main never starts an image build.
|
||||
#
|
||||
# Its original purpose, still the most important one: the "bash-only syntax
|
||||
# under the default `sh`/dash shell" footgun. Ported from pi-devbox, where this
|
||||
# class broke resolve-versions (ed49b8d) and promote-base-latest (b7197e8 → run
|
||||
# 418). actionlint runs shellcheck against each `run:` step using its
|
||||
# *effective* shell, so `set -o pipefail` under dash is flagged as SC3040
|
||||
# before any expensive build runs.
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
@@ -90,3 +97,20 @@ jobs:
|
||||
|
||||
- name: Run hadolint
|
||||
run: hadolint Dockerfile.base Dockerfile.variant
|
||||
|
||||
docs-check:
|
||||
# Fails if DOCKER_HUB.md is out of sync with what generate-dockerhub-md.py
|
||||
# would produce from HUB_TEMPLATE. Keeps the two docs from drifting.
|
||||
#
|
||||
# Lives here rather than in validate.yml because it needs no image: keeping
|
||||
# it in the cheap workflow means it still runs on every push to main now
|
||||
# that validate.yml is PR/dispatch-only. Reproduce locally with
|
||||
# `python3 scripts/generate-dockerhub-md.py --check`.
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check DOCKER_HUB.md is in sync with HUB_TEMPLATE
|
||||
run: python3 scripts/generate-dockerhub-md.py --check
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
name: Validate
|
||||
|
||||
# Lightweight validation on pushes to main. Builds single-arch (amd64),
|
||||
# runs the smoke test, and checks image size — without pushing anything
|
||||
# to Docker Hub. Tag pushes are handled by docker-publish-split.yml which
|
||||
# does the full multi-arch split-base build-and-push.
|
||||
# Image build + smoke test. This workflow has NO push trigger at all — it fires
|
||||
# only on pull_request and on explicit workflow_dispatch.
|
||||
#
|
||||
# Division of labour across this repo's three workflows:
|
||||
# lint.yml — cheap checks, every push/PR: workflow lint,
|
||||
# Dockerfile lint, DOCKER_HUB.md sync. No builds.
|
||||
# validate.yml (this file) — amd64 build + smoke test. PR / manual only.
|
||||
# docker-publish-split.yml — the release path, tag-only: multi-arch build,
|
||||
# smoke gates, then push + promote.
|
||||
#
|
||||
# Net effect: no image build can start from a push to main. A tag — or an
|
||||
# explicit dispatch — is required. That is safe because the release path fails
|
||||
# closed: variant tags are pushed only after smoke-base/smoke-omos pass, and
|
||||
# base-latest is promoted last, so an aborted release leaves at worst an
|
||||
# unreferenced base-<hash> blob on Hub, never a half-published version tag.
|
||||
#
|
||||
# To validate before tagging: open a PR, dispatch this workflow, or dispatch
|
||||
# docker-publish-split.yml against a throwaway tag with promote_latest=false
|
||||
# (the only route that also exercises a CHANGED BASE — see the trade-off below).
|
||||
#
|
||||
# Trade-off: variant builds here use the published `base-latest` image
|
||||
# from Docker Hub as their parent, NOT a locally-built base. This is
|
||||
@@ -22,18 +37,10 @@ name: Validate
|
||||
# blind-spot applies.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- 'CHANGELOG.md'
|
||||
- 'README.md'
|
||||
- 'DOCKER_HUB.md'
|
||||
- 'deploy/**'
|
||||
- '.gitleaks.toml'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
# Gitea Actions' default step shell is `sh` (dash); force bash workflow-wide so
|
||||
# no run: step silently falls through to dash. Enforced by lint.yml's
|
||||
@@ -44,20 +51,6 @@ defaults:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
docs-check:
|
||||
# Fails if DOCKER_HUB.md is out of sync with what generate-dockerhub-md.py
|
||||
# would produce from README.md. Keeps the two docs from drifting.
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check DOCKER_HUB.md is in sync with README.md
|
||||
run: |
|
||||
python3 scripts/generate-dockerhub-md.py --check
|
||||
|
||||
base-change-warning:
|
||||
# Surfaces a warning when this commit changes base-image inputs
|
||||
# (Dockerfile.base, rootfs/, entrypoint*.sh) AND exports `base_changed` so
|
||||
@@ -97,12 +90,21 @@ jobs:
|
||||
printf '%s\n' "$changed"
|
||||
else
|
||||
echo "base_changed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "No base-image inputs changed in this commit — validate.yml fully exercises the published base-latest."
|
||||
echo "No base-image inputs changed in this commit — validate-base/validate-omos will build against the published base-latest."
|
||||
fi
|
||||
|
||||
validate-base:
|
||||
needs: [base-change-warning]
|
||||
if: ${{ needs.base-change-warning.outputs.base_changed != 'true' }}
|
||||
# Two gates, both must hold for this job to run:
|
||||
# 1. base_changed != true — the documented blind spot: this workflow builds
|
||||
# variants on top of Hub's base-latest, so a commit changing base inputs
|
||||
# would either not exercise the change or hard-fail against a stale
|
||||
# base-latest (see base-change-warning).
|
||||
# 2. event_name != push — belt-and-braces. Redundant today (this workflow
|
||||
# has no push trigger), kept deliberately so that re-adding a push
|
||||
# trigger later cannot silently re-enable image builds on every push to
|
||||
# main. If you intend that, remove this clause explicitly.
|
||||
if: ${{ needs.base-change-warning.outputs.base_changed != 'true' && github.event_name != 'push' }}
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
@@ -165,7 +167,8 @@ jobs:
|
||||
|
||||
validate-omos:
|
||||
needs: [base-change-warning]
|
||||
if: ${{ needs.base-change-warning.outputs.base_changed != 'true' }}
|
||||
# Same two gates as validate-base — see the comment there.
|
||||
if: ${{ needs.base-change-warning.outputs.base_changed != 'true' && github.event_name != 'push' }}
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
|
||||
Reference in New Issue
Block a user