next
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d257f8f658 |
Lint in a container as a build step, via Dockerfile.lint (closes #113)
check / check (pull_request) Successful in 2m58s
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. `golangci-lint config verify` runs as its own epoch-keyed layer, above the lint. It is not belt-and-braces: `golangci-lint run` rejects a config it cannot PARSE but silently IGNORES an unknown top-level KEY. Renaming .golangci.yml's `linters:` to `linterz:` -- one character -- discards `default: all`, the disable list and every threshold, leaves only the small default linter set running, and exits 0 reporting `0 issues.` on a tree the real config fails with an lll finding, in a run whose lint layer demonstrably executed. That is a set-but- ineffective config falling back to defaults instead of failing loudly, sitting in the gate's own configuration. `config verify` catches it and does so with the network genuinely off at this pin: under `docker run --network none` against the pinned digest it exits 0 on this repo's config and exits 3 on the `linterz:` variant. It is keyed on CHECK_EPOCH like the lint itself, because a cached validation validates nothing. 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, the config verification running before the lint, and -- structurally, not by searching for one retired variable name -- that no script invokes golangci-lint except through docker. 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. The scanner behind the last of those has its own test, because a structural check that goes blind passes on every tree, including a broken one. 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. |
||
|
|
e3f407b440 |
Make the tagged-release path work on Gitea (closes #65)
check / check (push) Successful in 3m7s
No tag could be cut at all: .goreleaser.yaml had no gitea_urls block, so
goreleaser defaulted to the GitHub API, and the repo has zero tags.
.goreleaser.yaml now points at git.eeqj.de. Version derives from git via
a new script/version - exact tag with any leading v stripped, else
dev-<12-char sha>, with a -dirty suffix when tracked files are modified -
replacing the hardcoded 1.0.0-rc.1 that every local build was stamping
regardless of git state. A tag-triggered .gitea/workflows/release.yml
runs goreleaser with a scoped token (RELEASE_TOKEN); script/bootstrap
installs a sha256-verified goreleaser, and make release / release-snapshot
become script shims like every other target.
Two fabrications were removed rather than merely replaced. goreleaser's
snapshot.version_template was `{{ incpatch .Version }}-next`, which
invents a release number from the last tag - and with no tags, from
goreleaser's own fabricated v0.0.0. And internal/cli/version.go gated its
development-build notice on Version == "dev" exactly, so the moment
untagged builds carried a sha that notice would have gone silent and an
unreleased binary would have read as a release. Replaced with a tested
IsDevVersion predicate, and closed at both layers: the Makefile now
refuses to build when script/version yields nothing, and an empty version
counts as a development build - reachable today via
`docker build --build-arg VERSION=`.
The release workflow installs Go from a sha-pinned actions/setup-go
(v5.6.0) using go-version-file, so the compiler that produces released
binaries is pinned like every other external reference. Without it the
first tag push would either fail at goreleaser's before-hook or compile
the published artifacts with whatever unpinned Go the runner happened to
carry - the one unpinned thing in a release path that already refuses an
unpinned goreleaser.
Known gap: the Go tarball setup-go fetches is version-pinned but not
checksum-verified against a value in this repo, unlike the goreleaser
install and the Dockerfile digest.
|
||
|
|
b6e4a218a3 |
Isolate the lint cache and context-gate the native path (closes #99)
check / check (push) Successful in 2m23s
Closes #80. script/lint pointed GOLANGCI_LINT_CACHE at a path shared by every worktree of this repo. Two worktrees have identical Go file contents, so their cache keys collided and one tree's stored findings replayed for another, paths included - observed as 231 findings all citing another session's worktree, with no parallel-runner message to signal it. The failure is symmetric and only one direction is loud: a clean tree failed by a dirty sibling gets investigated, a dirty tree passed by a clean sibling does not. The cache is now keyed per worktree on a digest of $ROOT, and remains persistent. Independently of that, script/lint-audit inspects every run's output and fails the run if any finding cites a path outside the tree being linted. That guard is the load-bearing part: it converts a silent unearned green into a hard error regardless of how the cache is keyed. It is deliberately built so it can never certify a pass, only reject, so it cannot itself become a gate that reports green. The native path was gated on version equality alone, which admitted a locally installed matching binary and bypassed the digest pin. It now requires VAULTIK_LINT_IN_CONTAINER=1, set only by the Dockerfile lint stage, in addition to version equality. /.dockerenv was rejected as the signal because dockerd creates it for `docker run` but not reliably during a BuildKit `docker build`, which is the case the exception exists for. A version mismatch inside the container is now a hard error rather than a fall-through. This mattered more than the issue supposed: on this host a matching golangci-lint exists on PATH, so script/lint was taking the native path and linting against the global cache without ever running the pinned image. That is the likely root of the observed contamination, and it is closed here rather than mitigated. The parallel-runner error is retried rather than reported. It is not a lint result, and surfacing it as a non-zero exit is indistinguishable to a caller from real findings; exhausted retries fail saying the tree was never analysed. Note that a private cache alone does not remove lock contention - measured with two concurrent runs using separate cache directories. script/bootstrap no longer reports success on a machine that cannot run the gate: docker is now required by lint, check and precommit, so a missing binary or unreachable daemon is a hard failure naming what will not work. |
||
|
|
af607e3597 |
Run the linter at the pinned version locally too (closes #78)
check / check (push) Successful in 6s
script/lint ran bare golangci-lint from PATH while CI and the Dockerfile pinned v2.12.2 by digest, so make lint and CI could disagree about findings. That drift ran both directions: it produced two false green claims during the lint remediation, and on an ambient 2.10.1 it also reported four gosec findings on a tree CI linted clean. script/lint now extracts the image reference - tag and digest - from the Dockerfile lint stage FROM line and runs that exact image under docker. The Dockerfile FROM line is the single source of truth for the linter version; the duplicate pins in the Makefile deps target and in script/bootstrap are removed rather than kept in sync. A golangci-lint on PATH is used only when its version exactly equals the pin, which is what makes the in-container lint stage work (the Dockerfile runs make lint inside the pinned image, where there is no docker daemon). Any other version, or none, goes through docker. When docker is unavailable the script fails with an actionable message and never falls back to a different linter version. script/lint-fix delegates to script/lint --fix so autofixes come from the pinned linter too. The container mounts persistent build and module caches and runs as the invoking uid/gid. Verified by reinstating the four historical nolint directives that 2.10.1 requires and 2.12.2 reports as unused: the old script passed on that tree and the new one fails with four nolintlint findings. |
||
|
|
c9c72ef29d | script/bootstrap: install sqlite3, which the test suite shells out to | ||
|
|
43346e62db | Adopt scripts-to-rule-them-all: script/ entrypoints, Makefile shims |