215539cd15248389f468c360398f88fa14fc0e5c
7 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 215539cd15 |
Run all linting in Docker via Dockerfile.lint (closes #46)
All checks were successful
check / check (push) Successful in 1m15s
Per the owner ruling, the linter runs inside a container invoked through the script/ entrypoint and is never installed on a host. A new root Dockerfile.lint COPYs the repo into the digest-pinned golangci/golangci-lint:v2.12.2 image and runs `golangci-lint config verify` and `golangci-lint run` as build steps, so a successful build IS a clean lint. script/lint is reduced to building it, which also works when the docker daemon is remote and bind mounts are impossible. script/bootstrap loses the `go install`, the pin constants, the version parser and verify_golangci_lint outright rather than being hardened: with nothing linting on the host, the $GOPATH/bin versus PATH shadowing problem those existed to diagnose has no subject. It keeps the git/make/go presence checks and `go mod download`, and warns rather than fails when docker is absent. Two traps. A lint build on an unchanged tree returns success in well under a second having run no linter, which is #32 and #39 over again. Caching is waived by ruling, so Dockerfile.lint carries ARG CHECK_EPOCH referenced inside every gate RUN -- BuildKit hashes the expanded command, not the declaration, so a declared but unreferenced ARG invalidates nothing -- and script/lint passes "$(date +%s)-$$". The PID is in that value because two lint runs land inside the same second easily and a bare epoch would cache the second one. Nothing inside an image build may shell out to docker. The main Dockerfile's lint stage therefore invokes golangci-lint directly instead of `make lint`, and its build stage runs `make test` and `make fmt-check` instead of the `make check` aggregate, which reaches script/lint. Those two remain `make` invocations rather than the bare scripts because the Makefile's `export CGO_ENABLED = 0` only applies to what it invokes, and today's `make check` gets it. COPY --from=lint /usr/bin/golangci-lint is replaced by COPY --from=lint /src/go.sum /dev/null. The copied binary was the only edge forcing BuildKit to finish linting before the build stage starts; dropping it without replacing the edge would have ended fail-fast linting silently under a still-green build. That no-op copy is the ordering edge canonical REPO_POLICIES.md prescribes. Nothing in the build stage runs the linter any more, so the binary itself is not wanted there, and ENV PATH=/home/builder/go/bin:$PATH goes with the `go install` that justified it. script/verify-linter-pin is retired -- deleted along with its README entry -- because both of its subjects ceased to exist in this same change: it compared a linter binary against GOLANGCI_LINT_VERSION in script/bootstrap, and there is now neither a binary crossing between stages nor a version pin in bootstrap. The drift it guarded has not gone away, it has moved. The linter is still pinned twice, now as the FROM line of Dockerfile.lint and the FROM line of the Dockerfile lint stage, with nothing syncing them, which is exactly what #42 made a build failure. Its replacement is one new script/verify-lint-image-pin that compares those two references to each other and deliberately restates neither pin: a hardcoded expected digest would be a third copy and the same drift one file further out. It runs as a gate in both files, so `make lint`, `make check` and `make docker` all catch drift, and an unreadable reference is a hard failure rather than a vacuous pass. `golangci-lint config verify` is included per the ruling. The concern about its unpinned live HTTPS schema fetch was measured rather than assumed: under --network none the pinned binary both passes a valid config and rejects an invalid one with the jsonschema error, so it validates against a schema it embeds and linting needs no network beyond pulling the image. The README states that rather than a requirement that does not exist. Verified. `make lint` green with every PATH directory containing a golangci-lint removed and `command -v golangci-lint` empty. Two consecutive script/lint runs on an untouched tree both executed the linter, 27.7s and 28.7s in the lint step under distinct epochs with the COPY layer CACHED above them. A planted unused variable failed script/lint with that exact finding and failed `make docker` at the lint stage with the build stage stopped before its COPY --from=lint, then reverted clean. The drift guard fails on tag-only, digest-only and unreadable-reference cases, naming both sides. `make check` green; `make docker` green in 5m35s with all six gates executing and the test gate reporting real coverage rather than a cached ok. In the builder image with the Go test cache off, --user 0:0 still fails TestScanHardlinkRunFailsTogether where the unprivileged user passes, so the non-root quirk is intact. |
|||
|
|
5ca68804ac |
Fail the Docker build when the lint stage's linter is not the pin
All checks were successful
check / check (push) Successful in 1m23s
The reordered COPY --from=lint did not make the two stages provably one toolchain, as the Dockerfile comment, the previous commit message and TODO.md all claimed. script/bootstrap compares its pin against whatever PATH resolves, and $GOPATH/bin sits ahead of /usr/local/bin, so any drift was absorbed: bootstrap rebuilt the pinned version from source, verified that, and the build went green with the lint stage having linted at one version and make check having run at another. Bumping the lint stage image without touching the pin was enough to produce it. New script/verify-linter-pin fails, naming both versions, unless a given golangci-lint binary is exactly the version script/bootstrap pins. The build stage runs it on the binary copied out of the lint stage, immediately after the copy and before bootstrap, so no reinstall can satisfy it. The pin is read out of script/bootstrap, which stays its single source of truth; a pin that cannot be read is a hard failure rather than a skip. The check takes no CHECK_EPOCH because its only inputs are the copied binary and script/, so Docker invalidates the layer exactly when a cached result would stop being true. The linter version is pinned independently in the lint stage's image digest and in GOLANGCI_LINT_VERSION, with nothing keeping them in sync; a half-applied bump is now a build failure instead of a silent split. ENV PATH keeps $GOPATH/bin, but its comment no longer claims a reinstall is the reason: bootstrap must be able to run and verify what it installs, and nothing in this image is shadowed by the entry. Verified: with the lint stage's linter faked to 2.11.0 after the gates had really run, the build fails at verify-linter-pin naming 2.11.0 and 2.12.2, with bootstrap and the check gate never reached; an unmodified make docker is green with all three gates run on a fresh epoch and real test results. A planted unused finding still fails at the lint stage with gate check absent from the log; the image still fails TestScanHardlinkRunFailsTogether under --user 0:0 and passes as uid 1000, both with the Go test cache disabled; and a second build serves bootstrap, the verify layer and the dependency layers CACHED while the gates go cold. |
||
|
|
964fc29ed3 |
Bust the Docker layer cache for the gate steps (closes #32)
All checks were successful
check / check (push) Successful in 1m50s
script/cibuild and script/docker were bare docker build invocations with no cache control, and the Dockerfile copies the tree before running its gates. On an unchanged tree Docker served those layers from cache, so the gates never executed and the build still exited 0. A merge commit here has a tree byte-identical to the branch head it merges, so every merge CI run was almost certainly a full cache hit, and PR #31's reviewer caught make docker returning success as a 17-layer cache hit that proved nothing. Declare ARG CHECK_EPOCH in both stages and have the scripts pass --build-arg CHECK_EPOCH="$(date +%s)". ARG is scoped per stage and this Dockerfile has three gates across two of them (make fmt-check and make lint in the lint stage, make check in the build stage), so one declaration would have left a stage silently cacheable. BuildKit hashes the expanded command rather than the declaration, so each gate RUN echoes the epoch: an unreferenced ARG invalidates nothing, and the echo doubles as evidence in the build log that the layer really ran. Both declarations sit below the dependency layers, so the pinned base images, go mod download, apk add and the source copies keep their cache and only the gates go cold. The build-stage declaration sits after USER, so the drop to the unprivileged builder user still happens before make check and the chmod(0) permission tests stay real. |
||
|
|
9d06c13777 |
Verify the golangci-lint install actually took effect
All checks were successful
check / check (push) Successful in 1m31s
`go install` writes into GOBIN (or GOPATH/bin), but the linter `make lint` runs is whichever golangci-lint PATH resolves first. On a host where a wrong-version binary sits ahead of that directory — a nix profile, apt, brew, apk, a tarball in /usr/local/bin, or the /usr/local/bin copy the Dockerfile builder stage makes — the install landed behind the shadow, changed nothing the gate uses, and bootstrap still printed "bootstrap complete" and exited 0. That leaves the local gate linting against a different ruleset than CI while affirmatively claiming otherwise, and every subsequent run reinstalls forever, so the second run is never a no-op. After installing, re-read the effective version. On a mismatch print the resolved binary, the install directory and both versions to stderr and exit non-zero. Do not reorder PATH or remove anyone's binary: diagnose and stop. Also: - stop discarding `golangci-lint --version` stderr, so a present but broken binary (missing shared library, wrong architecture) says why instead of silently yielding the empty string and reinstalling on every run forever. Only stdout is parsed, so the parse matrix is unchanged. - bound the `--version` call with timeout(1) where it exists, since bootstrap now executes a binary it previously only located and a wedged one would otherwise hang the script. Hosts without timeout(1) run it unbounded, as before. - use X.Y.Z in the parsing comment so the pinned version stays a single literal in the script. |
||
|
|
9e924721e6 |
Check the golangci-lint version in bootstrap, not just presence (closes #24)
All checks were successful
check / check (push) Successful in 1m48s
script/bootstrap installed the pinned linter only when the command was absent, so on any host that already had some golangci-lint the pin was never consulted and a version bump was inert forever. That is how a host running v2.10.1 against a v2.12.2 pin got a green `make check` while `make docker` rejected the same commit: the local gate was linting with a different ruleset than CI, and the disagreement only surfaced after a push. The version is now a single value, GOLANGCI_LINT_VERSION, with the `go install` module ref derived from it, so a future bump cannot half-apply. A golangci_lint_version helper parses the installed version out of `golangci-lint --version` (the field after the word "version", with an optional leading "v" stripped, since the module ref carries one and the binary's output does not) and yields the empty string when the tool is absent or unreadable. Any version that is not the pin -- older, newer, absent or unparseable -- is reinstalled, so a first run upgrades and a second is a no-op. git, make and go keep their presence-only checks: they come from the host package manager, the repo pins no system toolchain versions, and go.mod governs the language version. That is now stated in a comment next to them rather than left ambiguous beside a tool that is version-checked. |
||
| 814bdada2b |
Update golangci-lint to v2.12.2 with canonical config
All checks were successful
check / check (push) Successful in 1m5s
Bump the pinned golangci-lint from v2.12.1 to v2.12.2 in the Dockerfile lint stage (tagged, digest-pinned Debian image) and in script/bootstrap (go install ref). Replace .golangci.yml with the canonical config: linter settings (lll, funlen, cyclop, dupl) move under linters.settings per the v2 schema so they are actually applied, and the redundant issues.exclude-use-default key is dropped. No new lint findings surfaced; make check is green. |
|||
| 3abeacf8ee |
Add scripts-to-rule-them-all scaffold (refs #1)
All checks were successful
check / check (push) Successful in 6s
Bring the repo into conformance with the scripts-to-rule-them-all (STRTA) scaffold. The real logic that lived inline in the Makefile now lives in POSIX-sh entrypoints under script/, and the Makefile's standard targets are thin @script/NAME shims. - script/: bootstrap, setup, projectname, test, lint, fmt, fmt-check, check, docker, precommit, install-precommit, cibuild. All are executable #!/bin/sh entrypoints; the go mod tidy guard from the old inline hooks recipe moved into script/precommit. - Makefile: the nine standard targets (bootstrap, setup, test, lint, fmt, fmt-check, check, docker, hooks) are now thin shims; the repo-specific sfdupes/build/clean targets and the CGO_ENABLED export are preserved. - .gitea/workflows/check.yml: run script/cibuild instead of a bare docker build. - Dockerfile: run make check (and the build) as an unprivileged builder user rather than root. We should never build or run as root, and doing so also lets the permission-denied tests run legitimately: root bypasses the chmod(0) that TestScanHardlinkRunFailsTogether relies on, which made the in-image make check fail. HOME and the Go caches point at the user's home so go build/test and golangci-lint can write. make check passes locally and docker build . is green (the in-image non-root make check passes, including the hardlink permission test). |