Files
rgoue/Dockerfile.lint
sneak 20cfb47912 build: define the lint stage name once so the two flags cannot diverge
The previous commit claimed --target and --no-cache-filter "validate each
other's magic string". They do not. --target validates only its own
argument; a typo confined to --no-cache-filter left the build green and
linting nothing:

    docker build --target lint --no-cache-filter=lnit ...
    #10 [lint 2/2] RUN golangci-lint run ... CACHED   exit 0

Three of the four edit paths were caught and one was not, so the original
false green survived in the narrow case.

The duplication was the defect: the stage name appeared twice on one
command line and nothing tied the copies together. Correcting only the
prose would have left the hazard live and merely warned about, so the name
is now written once, as `stage=lint`, and passed to both flags. Divergence
is unrepresentable rather than documented — there is a single name to get
wrong, and --target rejects it loudly when it is not a stage in
Dockerfile.lint, which now covers the filter too because it is the same
string.

The comments in script/lint and Dockerfile.lint and the TODO.md entry drop
the false "validate each other" claim and state the real property, along
with the residual hazard that is genuinely unguarded: --target checks that
the name exists, not that it names the stage which actually runs
golangci-lint, and it stops the build there, so relocating the lint step
or appending a stage after it would go unnoticed.
2026-08-10 13:11:42 +00:00

40 lines
1.8 KiB
Docker

# Lint-only image: used by script/lint on machines where the docker
# daemon is remote (no bind mounts possible) — the repo is COPYed into
# the build context and golangci-lint runs as a build step, so a
# successful build means a clean lint.
#
# Two stages on purpose. script/lint builds with --no-cache-filter=lint so
# that the lint stage re-executes on every run, including on an unchanged
# tree (caching of the lint result is explicitly waived: a cached build
# lints nothing). Keeping `go mod download` in a separate `deps` stage
# means busting the lint stage does not also re-fetch the module cache
# over the network every time.
# golangci/golangci-lint:v2.12.2 (Debian-based), 2026-08-07
FROM golangci/golangci-lint:v2.12.2@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240 AS deps
WORKDIR /src
# Copy go mod files first for better layer caching
COPY go.mod go.sum ./
RUN go mod download
# This stage must stay the one that runs golangci-lint, and its name must
# match $stage in script/lint, which passes that single name to both
# --target and --no-cache-filter. Renaming here without updating script/lint
# fails the build loudly (--target rejects a name that is not in this file),
# so a mismatch cannot pass silently — but moving the lint step to another
# stage, or adding a stage after this one, would not be caught. Change the
# two files together.
FROM deps AS lint
# Copy source code
COPY . .
# No `golangci-lint config verify` step here, deliberately (sneak/homoicon
# has one). It resolves its JSON schema over a live, unpinned HTTPS call:
# an unpinned network input inside the one step whose whole purpose is a
# pinned, reproducible gate, and a schema-host outage would show up as a
# red build. `golangci-lint run` already fails on a malformed config.
RUN golangci-lint run --config .golangci.yml ./...