next
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| d4eaf5fed2 |
Run all linting in Docker via Dockerfile.lint (closes #46)
All checks were successful
check / check (push) Successful in 1m38s
Per the owner ruling the linter runs in a container invoked through the script/ entrypoint, never installed on a host. 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, and works with a remote docker daemon, where bind mounts are impossible. script/bootstrap loses the `go install`, the pin constants, the version parser and verify_golangci_lint: with nothing linting on the host, the $GOPATH/bin versus PATH shadowing they diagnosed has no subject. It keeps the git/make/go presence checks and `go mod download`, and warns rather than fails when docker is absent. Traps for anyone changing this. A lint build on an unchanged tree exits 0 in under a second having run no linter -- #32 and #39 again. Caching is waived by ruling: Dockerfile.lint carries ARG CHECK_EPOCH referenced inside every gate RUN, because BuildKit hashes the expanded command and a declared but unreferenced ARG invalidates nothing. script/lint passes "$(date +%s)-$$"; the PID is there because two runs land in the same second easily and a bare epoch would cache the second. Nothing inside an image build may shell out to docker. The main Dockerfile's lint stage therefore invokes golangci-lint directly rather than `make lint`, and its build stage runs `make test` and `make fmt-check` rather than the `make check` aggregate, which reaches script/lint. Both stay `make` invocations rather than bare scripts because the Makefile's `export CGO_ENABLED = 0` only reaches what it invokes. COPY --from=lint /usr/bin/golangci-lint becomes 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 now, so ENV PATH=/home/builder/go/bin:$PATH goes with the `go install` that justified it. script/verify-linter-pin is retired with its README entry: it compared a linter binary against GOLANGCI_LINT_VERSION in script/bootstrap and neither subject still exists. The drift moved rather than went away -- the linter is pinned twice, as the FROM line of Dockerfile.lint and the FROM line of the Dockerfile lint stage, which is what #42 made a build failure. script/verify-lint-image-pin compares those two references to each other and restates neither pin; a hardcoded digest would be a third copy and the same drift one file further out. It runs as a gate in both files, and an unreadable reference is a hard failure rather than a vacuous pass. `golangci-lint config verify` is included per the ruling, and its unpinned live HTTPS schema fetch was measured rather than assumed: under --network none the pinned binary passes a valid config and rejects an invalid one with the jsonschema error, so it validates against a schema it embeds. That holds for the gate steps, none of which makes a network call, but not for the build around them -- Dockerfile.lint runs `go mod download` above the gates, so a cold cache needs the network and only a warm one lints offline, until go.mod or go.sum changes. 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 under distinct epochs with the COPY layer CACHED above them. A planted unused variable failed script/lint with that finding, and failed `make docker` at the lint stage with the build stage stopped before its COPY --from=lint; 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. |
|||
|
|
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. |
||
| 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). |