Re-vendor the shared files from sneak/prompts at dd4027b (closes #504)
check / check (push) Failing after 3s
check / check (push) Failing after 3s
Fetches the shared files unchanged from sneak/prompts commit dd4027b and adds .prettierignore; .dockerignore keeps this repository's anchored host artifacts at its end. Linting moves into the Dockerfile's lint phase on the golangci-lint v2.14.0 image, which also runs the js-lint stage, and Dockerfile.lint is gone. Tests move into a test phase on the golang bookworm image. script/lint, test, docker and cibuild are the model scripts, every docker build in script/ passes --no-cache, and the .ci-fingerprint barrier is gone. The workflow no longer calls script/ci-mark-superseded, so it and its tests are removed. make build passes -trimpath and -s -w. Model: opus-5-5
This commit is contained in:
@@ -17,14 +17,14 @@ before deploying one.
|
||||
### Prerequisites
|
||||
|
||||
- Go 1.26.1+ (the version in `go.mod`)
|
||||
- Docker (for `make lint`, `make fmt` and `make css`, and so for `make check`,
|
||||
for the browser test in `make test-browser`, for the CI gate, and for
|
||||
containerized deployment)
|
||||
- Docker (for `make test`, `make lint`, `make fmt` and `make css`, and so for
|
||||
`make check`, for the browser test in `make test-browser`, for the CI gate,
|
||||
and for containerized deployment)
|
||||
|
||||
golangci-lint is not a prerequisite and must not be installed on the host:
|
||||
`script/bootstrap` does not install it, and `make lint` runs the digest-pinned
|
||||
linter image via `Dockerfile.lint`. The same holds for tailwindcss (see
|
||||
[Stylesheet](#stylesheet)). ESLint, prettier, node and yarn are not
|
||||
linter image in the Dockerfile's `lint` phase. The same holds for tailwindcss
|
||||
(see [Stylesheet](#stylesheet)). ESLint, prettier, node and yarn are not
|
||||
prerequisites either, and `make lint` and `make fmt` never use a host copy of
|
||||
them (see [Linting](#linting)).
|
||||
|
||||
@@ -55,11 +55,11 @@ make docker
|
||||
```bash
|
||||
make bootstrap # Install all dependencies (idempotent)
|
||||
make setup # Bootstrap + install git pre-commit hook
|
||||
make assets # Extract Alpine.js from 3p/ (test, check, build, dev run it)
|
||||
make assets # Extract Alpine.js from 3p/ (build and dev run it)
|
||||
make fmt # Format Go (gofmt + goimports) and Markdown (prettier, in Docker)
|
||||
make fmt-check # Fail if gofmt or prettier would change anything (writes nothing)
|
||||
make lint # Run golangci-lint and ESLint in Docker
|
||||
make test # Run tests with race detection
|
||||
make test # Run tests with race detection, in Docker
|
||||
make test-browser # Run the browser test in Docker (Dockerfile.browser)
|
||||
make check # test + lint + fmt-check + css-check (CI gate)
|
||||
make build # Build binary to bin/webhooker (version-stamped)
|
||||
@@ -1095,7 +1095,9 @@ field), in the UI footer, and in the startup log line (`msg=starting`,
|
||||
The value is stamped in at build time by the linker; it is not read from a file
|
||||
at runtime, so it identifies the build itself.
|
||||
|
||||
`script/version` produces the value and both build paths use it:
|
||||
`script/version` produces the value for `make build`, and the image build runs
|
||||
`make build` too; `script/docker` and `script/cibuild` run the same
|
||||
`git describe --tags --always --dirty` on the host. All of them report:
|
||||
|
||||
| Build | What it reports |
|
||||
| ----------------------- | --------------------------------------------------- |
|
||||
@@ -1110,18 +1112,19 @@ carries, so any `docker build .` of a clone, with no build arguments, stamps the
|
||||
commit it was built from; a shallow clone of one branch has no tags and stamps
|
||||
the short SHA. `.dockerignore` must therefore leave out neither `.git` nor any
|
||||
tracked file, which git in the build would see as deleted, marking the version
|
||||
`-dirty`. It does leave `.git/config`, which can hold a remote URL carrying a
|
||||
credential and which `git describe` does not need, out of a directory context. A
|
||||
context sent as a tar is not filtered by `.dockerignore`, so it carries
|
||||
`.git/config` unless its sender leaves it out; for upaas, that is
|
||||
`-dirty`. It does leave out every git `config` (`**/.git/config`,
|
||||
`**/.git/modules/**/config`), which can hold a remote URL carrying a credential
|
||||
and which `git describe` does not need, from a directory context. A context sent
|
||||
as a tar is not filtered by `.dockerignore`, so it carries `.git/config` unless
|
||||
its sender leaves it out; for upaas, that is
|
||||
https://git.eeqj.de/sneak/upaas/issues/274. git in the build reads the checkout
|
||||
whoever owns its files, since a context sent as a tar archive keeps the sender's
|
||||
owners and git otherwise refuses a checkout owned by another user. A `VERSION`
|
||||
build arg (`--build-arg VERSION=...`) takes precedence; `script/docker` (and so
|
||||
`make docker`) passes the one `script/version` resolves on the host. The image
|
||||
build fails if its context carries `.git` and the version still comes out
|
||||
`unknown`, which means git is missing from the build or could not read the
|
||||
checkout.
|
||||
`make docker`) and `script/cibuild` pass the one they resolve on the host, or
|
||||
`unknown` where git gives none. The image build fails if its context carries
|
||||
`.git` and the version still comes out empty, `dev` or `unknown`, which means
|
||||
git is missing from the build or could not read the checkout.
|
||||
|
||||
`unknown` is what a source tarball, or a `docker build` with no `.git` in its
|
||||
context and no `VERSION` build arg, reports. A build that reports `unknown` is a
|
||||
@@ -1135,7 +1138,9 @@ to a commit.
|
||||
|
||||
Nothing that varies between two builds of the same commit is stamped — no
|
||||
timestamp, no hostname, no builder identity — so two builds of one commit still
|
||||
produce a byte-identical binary.
|
||||
produce a byte-identical binary. `make build` passes `-trimpath`, so the
|
||||
directory it builds in is not recorded either, and `-s -w`, which leave out the
|
||||
symbol table and debug information.
|
||||
|
||||
### Backups contain secrets
|
||||
|
||||
@@ -1213,11 +1218,14 @@ commands with no script behind them, though `build`, `run` and `dev` first run
|
||||
`script/assets`, and `build` and `version` both take their value from
|
||||
`script/version`.
|
||||
|
||||
`script/test`, `make build` and `make dev` each run `script/assets` first, which
|
||||
writes the ignored `static/js/alpine.min.js` (see
|
||||
[Third-party browser assets](#third-party-browser-assets)), so `make test`,
|
||||
`make check` and the pre-commit hook work on a fresh clone without a separate
|
||||
step.
|
||||
`make build` and `make dev` each run `script/assets` first, which writes the
|
||||
uncommitted `static/js/alpine.min.js` (see
|
||||
[Third-party browser assets](#third-party-browser-assets)), so they work on a
|
||||
fresh clone without a separate step. The Docker stages that compile the code run
|
||||
it themselves.
|
||||
|
||||
Every `docker build` in `script/` passes `--no-cache`: a check served from the
|
||||
build cache is a check that did not run.
|
||||
|
||||
We provide:
|
||||
|
||||
@@ -1227,10 +1235,12 @@ We provide:
|
||||
- `script/projectname` — output the project name ("webhooker")
|
||||
- `script/assets` — extract Alpine.js from its tarball in `3p/` (see
|
||||
[Third-party browser assets](#third-party-browser-assets))
|
||||
- `script/test` — run the test suite
|
||||
- `script/test` — run the test suite: builds the Dockerfile's `test` phase,
|
||||
tagged `webhooker-test`
|
||||
- `script/test-browser` — run the browser test in Docker (see
|
||||
[Third-party browser assets](#third-party-browser-assets))
|
||||
- `script/lint` — run golangci-lint and ESLint in Docker (see Linting below)
|
||||
- `script/lint` — run the `gofmt` check, golangci-lint and ESLint: builds the
|
||||
Dockerfile's `lint` phase, tagged `webhooker-lint` (see Linting below)
|
||||
- `script/fmt` — format the Go code and, in Docker, the Markdown (writes)
|
||||
- `script/fmt-check` — check formatting (read-only)
|
||||
- `script/css` — regenerate `static/css/tailwind.css` in Docker (writes; see
|
||||
@@ -1241,11 +1251,11 @@ We provide:
|
||||
- `script/version` — output the version to stamp into the binary (see
|
||||
[Version stamping](#version-stamping))
|
||||
- `script/docker` — build the Docker image tagged via `script/projectname`,
|
||||
passing `script/version`'s output in as the `VERSION` build arg
|
||||
- `script/cibuild` — CI entrypoint: `docker build .` (the Dockerfile runs the
|
||||
checks, so a green build implies a green repo)
|
||||
- `script/ci-mark-superseded` — CI helper: mark the commits whose run a newer
|
||||
push cancelled (see [CI gate honesty](#ci-gate-honesty))
|
||||
passing the version `git describe` gives on the host in as the `VERSION` build
|
||||
arg
|
||||
- `script/cibuild` — CI entrypoint: `script/bootstrap`, then `script/check`,
|
||||
then the same image build as `script/docker`, whose gate phases run again (see
|
||||
[CI gate honesty](#ci-gate-honesty))
|
||||
- `script/precommit` — pre-commit checks (`go mod tidy` guard, then
|
||||
`script/check`)
|
||||
- `script/install-precommit` — install the git pre-commit hook that runs
|
||||
@@ -1298,12 +1308,12 @@ apply. The directory is `3p/` rather than `vendor/` because Go treats a root
|
||||
|
||||
`script/assets` (`make assets`) extracts the browser build,
|
||||
`package/dist/cdn.min.js`, from the tarball to `static/js/alpine.min.js`, where
|
||||
`go:embed` picks it up. `script/test`, `make build` and `make dev` run it first,
|
||||
and the Dockerfile builds through `make test` and `make build`, so nothing
|
||||
downloads Alpine.js. The extracted file is not committed, and `.dockerignore`
|
||||
keeps any host copy out of the build context. `static/static.go` names every
|
||||
file it embeds, so a build that skips the extraction, such as a bare `go build`,
|
||||
fails with an error naming `js/alpine.min.js`.
|
||||
`go:embed` picks it up. `make build` and `make dev` run it first, and so do the
|
||||
Dockerfile's lint, test and build stages, so nothing downloads Alpine.js. The
|
||||
extracted file is not committed, and `.dockerignore` keeps any host copy out of
|
||||
the build context. `static/static.go` names every file it embeds, so a build
|
||||
that skips the extraction, such as a bare `go build`, fails with an error naming
|
||||
`js/alpine.min.js`.
|
||||
|
||||
To move to a new version: download
|
||||
`https://registry.npmjs.org/@alpinejs/csp/-/csp-<version>.tgz`, check it against
|
||||
@@ -2969,8 +2979,6 @@ webhooker/
|
||||
├── internal/
|
||||
│ ├── banner/
|
||||
│ │ └── banner.go # Ruled block for the one credential shown in the clear
|
||||
│ ├── ciscript/
|
||||
│ │ └── doc.go # Tests for the CI shell scripts in script/; no runtime code
|
||||
│ ├── resetpw/
|
||||
│ │ └── resetpw.go # `webhooker resetpw`: set an account's password, stopped deployments only
|
||||
│ ├── config/
|
||||
@@ -3075,8 +3083,7 @@ webhooker/
|
||||
│ └── js/alpine.min.js # Alpine.js CSP build, extracted from 3p/ by make assets, not committed
|
||||
├── templates/ # Go HTML templates (base, login, sources, etc.)
|
||||
├── script/ # Scripts to Rule Them All entrypoints
|
||||
├── Dockerfile # Stages: lint, stylesheet, JavaScript lint, Markdown, test+build, Alpine runtime
|
||||
├── Dockerfile.lint # Lint-only image built by script/lint
|
||||
├── Dockerfile # Stages: stylesheet, JavaScript lint, Markdown, lint, test, build, Alpine runtime
|
||||
├── Dockerfile.browser # Browser test image built by script/test-browser
|
||||
├── Makefile # 13 of 19 targets shim script/; 6 are inline
|
||||
├── go.mod / go.sum
|
||||
@@ -3084,6 +3091,7 @@ webhooker/
|
||||
├── .yarnrc.yml # yarn settings: install into node_modules/
|
||||
├── eslint.config.mjs # ESLint configuration for static/js/
|
||||
├── .prettierrc # prettier settings for the Markdown
|
||||
├── .prettierignore # Files prettier skips
|
||||
└── .golangci.yml # golangci-lint configuration
|
||||
```
|
||||
|
||||
@@ -3339,47 +3347,36 @@ Two operational consequences follow from bounding the sequence:
|
||||
|
||||
### Linting
|
||||
|
||||
golangci-lint never runs on the host. `script/lint` builds `Dockerfile.lint`,
|
||||
which copies the repo into the digest-pinned golangci-lint image and lints as a
|
||||
build step, so a successful build is a clean lint. A host binary would share one
|
||||
cache and one lock with every other checkout on the machine, which has produced
|
||||
both invented findings attributed to other worktrees and unearned passes.
|
||||
golangci-lint never runs on the host. `script/lint` builds the Dockerfile's
|
||||
`lint` phase, which copies the repo into the digest-pinned golangci-lint image
|
||||
and lints as a build step, so a successful build is a clean lint. A host binary
|
||||
would share one cache and one lock with every other checkout on the machine,
|
||||
which has produced both invented findings attributed to other worktrees and
|
||||
unearned passes.
|
||||
|
||||
Three properties are load-bearing:
|
||||
Two properties are load-bearing:
|
||||
|
||||
- `script/lint` passes `--no-cache-filter=lint`. Without it an unchanged tree
|
||||
replays the lint layer from cache and the build exits 0 in under a second
|
||||
having linted nothing. The `deps` stage stays cacheable, so module downloads
|
||||
are not repeated. Invalidation is scoped to the one stage; never prune the
|
||||
shared build cache.
|
||||
- `script/lint` does not trust that flag. Docker silently ignores
|
||||
`--no-cache-filter` for a stage name that does not match, so a stage rename or
|
||||
a one-character typo would restore the cached false green with no warning and
|
||||
a fast exit 0. The script therefore tees the build output and treats a run as
|
||||
a pass only if golangci-lint's own summary line (`N issues.` / `N issues:`)
|
||||
appears in it: no summary, no lint, whatever the exit code says.
|
||||
- Both lint steps use `RUN --network=none`. `golangci-lint config verify` is
|
||||
documented as fetching its JSON schema over HTTPS, which would be an unpinned
|
||||
remote dependency; the pinned image resolves the schema without network
|
||||
access, and `--network=none` enforces that instead of trusting it. Verify is
|
||||
worth keeping because `golangci-lint run` silently ignores config keys it does
|
||||
not recognize, so a typo would disable a setting with no warning.
|
||||
- `script/lint` passes `--no-cache`. Without it an unchanged tree replays the
|
||||
lint layer from cache and the build exits 0 in under a second having linted
|
||||
nothing. Never prune the shared build cache instead.
|
||||
- Both golangci-lint steps use `RUN --network=none`.
|
||||
`golangci-lint config verify` is documented as fetching its JSON schema over
|
||||
HTTPS, which would be an unpinned remote dependency; the pinned image resolves
|
||||
the schema without network access, and `--network=none` enforces that instead
|
||||
of trusting it. Verify is worth keeping because `golangci-lint run` silently
|
||||
ignores config keys it does not recognize, so a typo would disable a setting
|
||||
with no warning.
|
||||
|
||||
ESLint never runs on the host either. It lints `static/js/` (not the extracted
|
||||
Alpine.js) in the Dockerfile's `js-lint` stage, which `script/lint` builds after
|
||||
`Dockerfile.lint` and the image build runs before the builder stage. Its version
|
||||
is pinned in `package.json` and every package's hash in `yarn.lock`. The
|
||||
`js-deps` stage before it installs ESLint with `yarn install --immutable`, which
|
||||
fails rather than change `yarn.lock`. The yarn it runs is the one the
|
||||
`packageManager` field in `package.json` pins by version and hash, which the
|
||||
node image's own corepack fetches and checks. The stage stays cached until
|
||||
`package.json`, `yarn.lock` or `.yarnrc.yml` changes, so only the lint step
|
||||
re-runs and ESLint is not downloaded again. `eslint.config.mjs` turns on the
|
||||
rules of the JavaScript styleguide `REPO_POLICIES.md` links to that a linter can
|
||||
check: `no-var` and `prefer-const`. ESLint prints nothing on a pass, so
|
||||
`script/lint` has no summary line to look for; it names the stage once for both
|
||||
`--target` and `--no-cache-filter`, and `--target` fails on a name that matches
|
||||
no stage.
|
||||
Alpine.js) in the Dockerfile's `js-lint` stage. The `lint` phase copies a file
|
||||
from it, so `make lint` and the image build both run ESLint. Its version is
|
||||
pinned in `package.json` and every package's hash in `yarn.lock`. The `js-deps`
|
||||
stage before it installs ESLint with `yarn install --immutable`, which fails
|
||||
rather than change `yarn.lock`. The yarn it runs is the one the `packageManager`
|
||||
field in `package.json` pins by version and hash, which the node image's own
|
||||
corepack fetches and checks. `eslint.config.mjs` turns on the rules of the
|
||||
JavaScript styleguide `REPO_POLICIES.md` links to that a linter can check:
|
||||
`no-var` and `prefer-const`.
|
||||
|
||||
prettier formats the Markdown, and it never runs on the host either. It is
|
||||
pinned in `package.json` and `yarn.lock` beside ESLint, installed by the same
|
||||
@@ -3391,103 +3388,81 @@ on any Markdown file prettier would change.
|
||||
### Docker
|
||||
|
||||
The Dockerfile uses a multi-stage build. Each stage is pinned by digest, and the
|
||||
lint and builder stages are separate images so the linter's version is fixed
|
||||
independently of the compiler's:
|
||||
lint phase and the Go stages are separate images so the linter's version is
|
||||
fixed independently of the compiler's:
|
||||
|
||||
1. **Lint stage** (`golangci/golangci-lint:v2.12.2`, Debian-based) — downloads
|
||||
dependencies, copies the source, and runs the `gofmt` check, then
|
||||
`script/assets` to extract Alpine.js from `3p/`, then
|
||||
`golangci-lint config verify` and `golangci-lint run`, both with
|
||||
`--network=none`.
|
||||
2. **Stylesheet stages** (`debian:bookworm-slim`, with the Tailwind standalone
|
||||
1. **Stylesheet stages** (`debian:bookworm-slim`, with the Tailwind standalone
|
||||
CLI pinned by version and sha256, one binary per architecture) — generate
|
||||
`static/css/tailwind.css` from `static/css/input.css` and the files its
|
||||
`@source` lines name. `css-check` fails when the committed file differs from
|
||||
the generated one, and `make css` writes the generated file out from
|
||||
`css-output` (see [Stylesheet](#stylesheet)).
|
||||
3. **JavaScript lint stages** (`node:24.21.0-alpine`, with the yarn
|
||||
2. **JavaScript lint stages** (`node:24.21.0-alpine`, with the yarn
|
||||
`package.json` pins, run through the image's corepack) — `js-deps` installs
|
||||
ESLint and prettier from `yarn.lock` and `js-lint` runs ESLint over
|
||||
`static/js/` (see [Linting](#linting)).
|
||||
4. **Markdown stages** (on `js-deps`) — `markdown-check` runs prettier over the
|
||||
3. **Markdown stages** (on `js-deps`) — `markdown-check` runs prettier over the
|
||||
Markdown and fails on any file it would change, and `make fmt` writes the
|
||||
formatted files out from `markdown-output`.
|
||||
5. **Builder stage** (`golang:1.26.1-bookworm`) — depends on the lint,
|
||||
`css-check`, `js-lint` and `markdown-check` stages passing (it copies a file
|
||||
from each), runs `make test` and `make build` (both extract Alpine.js from
|
||||
`3p/` first), and finally rebuilds the binary with `CGO_ENABLED=1` and static
|
||||
linking so it runs on musl. Both builds go through `make build`, the relink
|
||||
adding its `-extldflags` via `GO_LDFLAGS`, so neither can drop the `-X` that
|
||||
stamps the version. The version is the `VERSION` build arg if one is given,
|
||||
4. **Lint phase** (`lint`, `golangci/golangci-lint:v2.14.0`, Debian-based) —
|
||||
downloads dependencies, copies the source, and runs the `gofmt` check, then
|
||||
`script/assets` to extract Alpine.js from `3p/`, then
|
||||
`golangci-lint config verify` and `golangci-lint run`, both with
|
||||
`--network=none`, and depends on `js-lint` (it copies a file from it).
|
||||
`make lint` builds this stage alone.
|
||||
5. **Test phase** (`test`, `golang:1.26.1-bookworm`, whose C compiler `-race`
|
||||
needs) — extracts Alpine.js, then runs `go test -race -cover` at most four
|
||||
packages and eight tests at a time, with a 90-second timeout per package. On
|
||||
a failure it runs only the failed tests again with `-v`, since verbose output
|
||||
from the whole suite would pass the 2 MiB at which the Docker build cuts off
|
||||
a step's log, and then fails. `make test` builds this stage alone.
|
||||
6. **Builder stage** (`golang:1.26.1-bookworm`) — depends on the lint and test
|
||||
phases and the `css-check` and `markdown-check` stages passing (it copies a
|
||||
file from each), runs `make build` (which extracts Alpine.js from `3p/`
|
||||
first), and then rebuilds the binary with `CGO_ENABLED=1` and static linking
|
||||
so it runs on musl. Both builds go through `make build`, the relink adding
|
||||
its `-extldflags` via `GO_LDFLAGS`, so neither can drop the `-X` that stamps
|
||||
the version. The version is the `VERSION` build arg if one is given,
|
||||
otherwise derived from the `.git` in the context, and the stage fails if a
|
||||
context with `.git` would stamp `unknown` (see
|
||||
context with `.git` would stamp an empty version, `dev` or `unknown` (see
|
||||
[Version stamping](#version-stamping)).
|
||||
6. **Runtime stage** (`alpine:3.21`) — copies the static binary and
|
||||
7. **Runtime stage** (`alpine:3.21`) — copies the static binary and
|
||||
`deploy/docker-entrypoint.sh`, creates the `/var/lib/webhooker` directory for
|
||||
all SQLite databases, exposes port 8080, and includes a health check against
|
||||
`/.well-known/healthcheck`. It sets no `USER`: the `ENTRYPOINT` script starts
|
||||
as root, sets the data directory's owner and mode, and runs the app as the
|
||||
non-root `webhooker` user (UID 1000) through `su-exec`.
|
||||
|
||||
The lint stage invokes `gofmt` and `golangci-lint` directly rather than
|
||||
`make fmt-check` and `make lint`: it is already the pinned linter image, and
|
||||
both targets build docker stages, which would need a docker daemon inside this
|
||||
build.
|
||||
The lint and test phases invoke `gofmt`, `golangci-lint` and `go test` directly
|
||||
rather than `make fmt-check`, `make lint` and `make test`: those targets build
|
||||
docker stages, which would need a docker daemon inside this build.
|
||||
|
||||
The lint and builder stages use Debian rather than Alpine because
|
||||
The lint phase and the Go stages use Debian rather than Alpine because
|
||||
`gorm.io/driver/sqlite` pulls in `mattn/go-sqlite3`, which needs CGO and does
|
||||
not compile against musl. Only the final binary is statically linked, which is
|
||||
what lets it run on the Alpine runtime image.
|
||||
|
||||
`script/cibuild` — `docker build .` — is the CI gate: the checks run inside the
|
||||
image, so a build that succeeds is a repo that is formatted, linted, tested and
|
||||
compiled, with a current stylesheet. `script/lint` also uses Docker
|
||||
(`Dockerfile.lint` and the `js-lint` stage, see Linting above), so `make lint`
|
||||
and `make check` run the same pinned linter versions the gate does; of the steps
|
||||
`make check` runs, only `script/test` and the `gofmt` check in
|
||||
`script/fmt-check` run on the host.
|
||||
`script/cibuild` is the CI gate: it runs `script/bootstrap`, then
|
||||
`script/check`, then builds the image, whose build runs the lint and test phases
|
||||
and the stylesheet and Markdown checks again. A build that succeeds is a repo
|
||||
that is formatted, linted, tested and compiled, with a current stylesheet.
|
||||
`make check` runs the same stages the gate does; of its steps, only the `gofmt`
|
||||
check in `script/fmt-check` runs on the host.
|
||||
|
||||
#### CI gate honesty
|
||||
|
||||
A layer cache lets `docker build .` exit 0 in seconds with the lint and test
|
||||
stages replayed rather than executed, which would make a green check
|
||||
meaningless. The `check` workflow therefore writes `.ci-fingerprint` into the
|
||||
build context before building. Its value is the hash of the commit being
|
||||
checked, so every commit, docs-only ones and a squash merge whose tree matches
|
||||
an already-built branch included, gets a new fingerprint, invalidates the
|
||||
`COPY . .` layer of every check stage, and really runs the `gofmt` check,
|
||||
`golangci-lint`, the stylesheet check, ESLint, the Markdown check, `make test`,
|
||||
and `make build`. A run that reports success ran them.
|
||||
meaningless. Every `docker build` in `script/` therefore passes `--no-cache`, so
|
||||
on every run the `gofmt` check, `golangci-lint`, ESLint, the stylesheet check,
|
||||
the Markdown check, `go test` and `make build` really execute. A run that
|
||||
reports success ran them. A bare `docker build .` carries no such guarantee.
|
||||
|
||||
The module download layer sits above `COPY . .` and stays cached.
|
||||
|
||||
A separate workflow step, run before the fingerprint is written, covers a second
|
||||
way the gate lied: Gitea cancels an in-flight run when a newer commit lands on
|
||||
the same branch and records that cancellation as a `failure` status, so a commit
|
||||
nothing ever tested reads as a test result. Cancellation is unconditional
|
||||
server-side for push events, so the superseding run calls
|
||||
`script/ci-mark-superseded`, which rewrites that exact status to `failure` /
|
||||
`Superseded by a newer commit; never tested`.
|
||||
|
||||
The state stays `failure` on purpose: Gitea's combined status folds `skipped`
|
||||
into `success`, so marking a never-tested commit `skipped` made the status API
|
||||
report green for it, indistinguishable from a commit that passed. Reading a
|
||||
commit's status on this repo therefore goes:
|
||||
|
||||
- `success` / `Successful in ...` — the checks ran and passed.
|
||||
- `failure` / `Failing after ...` — the checks ran and failed.
|
||||
- `failure` / `Superseded by a newer commit; never tested` — the run was
|
||||
cancelled, by a newer push or by hand, and nothing was verified about this
|
||||
commit. Test the commit itself before concluding anything about it.
|
||||
|
||||
Genuine failures and successes are never touched, and no status is left
|
||||
`pending`, which would block the commit indefinitely. The step derives its
|
||||
context string from the workflow name, the job **id** and the event. That is
|
||||
deliberately not byte-identical to Gitea's own rule, which uses the job's
|
||||
display `name:` where the runner exports the id, so giving the job a `name:` —
|
||||
or renaming the workflow — makes the derived context stop matching. The step
|
||||
fails loudly when no status on the commit carries that context, so no rename can
|
||||
silently disable the rewrite.
|
||||
The `check` workflow is the shared one from `REPO_POLICIES.md`: it checks out
|
||||
the repository and runs `script/cibuild`, nothing else. Gitea cancels an
|
||||
in-flight run when a newer commit lands on the same branch and records that as
|
||||
`failure` / `Has been cancelled`: nothing was verified about that commit, so
|
||||
test the commit itself before concluding anything about it.
|
||||
|
||||
## TODO
|
||||
|
||||
|
||||
Reference in New Issue
Block a user