Lint in a container as a build step, via Dockerfile.lint (closes #113)
All checks were successful
check / check (pull_request) Successful in 4m0s
All checks were successful
check / check (pull_request) Successful in 4m0s
Every lint run now happens inside its own container, invoked through script/lint, and linting is a build step rather than a container command: a successful build of the new root Dockerfile.lint IS a clean lint. That shape also works where the docker daemon is remote and bind mounts are impossible. Its FROM line -- golangci/golangci-lint:v2.12.2, pinned by digest -- is now the only pin of the linter version in this repo. A container per run has its own lint cache and its own golangci-lint lock, both discarded with it, so neither cross-worktree contamination nor lock contention exists any more. The machinery that defended against them is therefore gone: the per-worktree cache directories, the lock-retry loop, and script/lint-audit, which existed to catch findings replayed from a cache that no longer exists. So is the host lint path in its entirety -- the native escape hatch, its version detection, and VAULTIK_LINT_IN_CONTAINER in both script/lint and the Dockerfile. Nothing lints on the host, at any version. A cached build lints nothing, so the CHECK_EPOCH mechanism the product Dockerfile already used is what makes a green mean something: ARG CHECK_EPOCH with no default, placed below the module layers so dependency caching survives, a `RUN [ -n "$CHECK_EPOCH" ] || exit 1` guard so a build that withholds the arg fails instead of replaying, and the value expanded into the lint command itself. script/lint computes `epoch="$(date +%s%N)$$"` as a bare assignment on its own line, because inline in the argument a failing substitution does not abort under `set -eu` and yields a constant empty epoch -- which is exactly the false green being prevented. The product Dockerfile loses its lint stage rather than gaining a second linter pin. That stage ran `make lint`, which is now `docker build`: docker-in-docker inside a BuildKit step with no daemon. Calling golangci-lint directly there instead would have meant two independently bumpable digests for one tool. `make fmt-check` moves beside `make test` in the builder stage, and script/cibuild now builds Dockerfile.lint and then Dockerfile, each with its own fresh epoch, failing on either. Consequence, stated in comments rather than left to be discovered: script/docker builds the product image only and no longer lints; script/check and script/cibuild are the gates. Two decisions taken deliberately and documented where they apply. `golangci-lint config verify` is omitted: it fetches its JSON schema over an unpinned live HTTPS call, which would make the gate depend on a remote resource outside this repo's hash-pinning discipline and turn an upstream outage or an egress-less runner into a red that is not a lint verdict. script/lint-fix is kept, reimplemented as a bind-mounted docker run against the image parsed out of Dockerfile.lint -- a build step cannot write fixes back to the worktree -- and its header states outright that it is a developer convenience, never a gate, and needs a local daemon. cmd/vaultik/lintdocker_test.go parses both Dockerfiles and both scripts and fails if any part of the mechanism is dropped: the digest pin, the defaultless ARG below `go mod download`, the emptiness guard, the expansion of the epoch into each check command, the bare per-invocation epoch assignment in both scripts, cibuild building both files, and the absence of any host-lint escape hatch. Every one of those losses is silent -- the build still exits 0 and nothing is checked -- which is why they are asserted rather than trusted. script/lint takes no arguments now, and says so instead of dropping them: a build step has no command line to pass linter flags to.
This commit is contained in:
@@ -48,11 +48,12 @@ missing() {
|
||||
! command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Docker is a hard requirement, not a nice-to-have: script/lint runs the
|
||||
# digest-pinned golangci-lint image from the Dockerfile's lint stage, and
|
||||
# script/check and script/precommit both run script/lint. A bootstrap
|
||||
# that prints "bootstrap complete" on a machine where `make check` cannot
|
||||
# run is a false success, so this fails instead.
|
||||
# Docker is a hard requirement, not a nice-to-have: script/lint lints by
|
||||
# building Dockerfile.lint, whose digest-pinned golangci-lint image is
|
||||
# the only place the linter runs, and script/check and script/precommit
|
||||
# both run script/lint. A bootstrap that prints "bootstrap complete" on a
|
||||
# machine where `make check` cannot run is a false success, so this fails
|
||||
# instead.
|
||||
#
|
||||
# Installing docker from here was considered and rejected: it needs root,
|
||||
# a running daemon, and on macOS a GUI cask, so an attempt would itself
|
||||
@@ -79,13 +80,15 @@ bootstrap: FAILED - $reason.
|
||||
|
||||
Docker is required to develop this repo. Without it these do not work:
|
||||
|
||||
script/lint runs the digest-pinned golangci-lint image declared
|
||||
by the Dockerfile's lint stage, which is the single
|
||||
source of truth for the linter version
|
||||
script/lint builds Dockerfile.lint, which runs the linter as a
|
||||
build step in a digest-pinned golangci-lint image.
|
||||
That FROM line is the single source of truth for the
|
||||
linter version
|
||||
script/check runs script/lint
|
||||
script/precommit runs script/check, so commits are blocked by the
|
||||
pre-commit hook installed by script/setup
|
||||
script/cibuild builds the Dockerfile, which is what CI runs
|
||||
script/cibuild builds Dockerfile.lint and Dockerfile, which is what
|
||||
CI runs
|
||||
|
||||
Install docker (and start the daemon, checking DOCKER_HOST and your
|
||||
group membership), then re-run script/bootstrap. golangci-lint on PATH
|
||||
@@ -104,12 +107,12 @@ main() {
|
||||
# Go toolchain
|
||||
if missing go; then pkg_install go golang go go; fi
|
||||
|
||||
# golangci-lint is deliberately NOT installed: script/lint runs the
|
||||
# digest-pinned golangci-lint image from the Dockerfile's lint stage,
|
||||
# so whatever a package manager happens to ship would only be a
|
||||
# shadow of the pinned version that could drift from CI. script/lint
|
||||
# will not use a PATH binary on a host at any version, so installing
|
||||
# one here would buy nothing.
|
||||
# golangci-lint is deliberately NOT installed: script/lint lints by
|
||||
# building Dockerfile.lint, whose digest-pinned image is the only
|
||||
# place the linter runs, so whatever a package manager happens to
|
||||
# ship would only be a shadow of the pinned version that could drift
|
||||
# from CI. Nothing on the host is ever used as a linter, at any
|
||||
# version, so installing one here would buy nothing.
|
||||
|
||||
# sqlite3 CLI: the test suite shells out to it (VACUUM).
|
||||
if missing sqlite3; then pkg_install sqlite sqlite3 sqlite sqlite; fi
|
||||
|
||||
Reference in New Issue
Block a user