All checks were successful
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.
127 lines
4.1 KiB
Bash
Executable File
127 lines
4.1 KiB
Bash
Executable File
#!/bin/sh
|
|
# script/bootstrap: install all dependencies needed to build and develop
|
|
# this repo. Idempotent: every install is guarded by a check so already
|
|
# installed tools are skipped. Base tooling comes from nix, apt, brew,
|
|
# or apk (detected in that order); assumes NOTHING is present (not git,
|
|
# make, or go).
|
|
set -eu
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|
|
|
PKGMGR=""
|
|
SUDO=""
|
|
|
|
detect_pkgmgr() {
|
|
[ -n "$PKGMGR" ] && return 0
|
|
if command -v nix-env >/dev/null 2>&1; then
|
|
PKGMGR="nix"
|
|
elif command -v apt-get >/dev/null 2>&1; then
|
|
PKGMGR="apt"
|
|
elif command -v brew >/dev/null 2>&1; then
|
|
PKGMGR="brew"
|
|
elif command -v apk >/dev/null 2>&1; then
|
|
PKGMGR="apk"
|
|
else
|
|
echo "bootstrap: no supported package manager (nix, apt, brew, apk)" >&2
|
|
exit 1
|
|
fi
|
|
if [ "$PKGMGR" = "apt" ]; then
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
if [ "$(id -u)" != "0" ]; then
|
|
SUDO="sudo"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# pkg_install <nix-attr> <apt-pkg> <brew-formula> <apk-pkg>
|
|
pkg_install() {
|
|
detect_pkgmgr
|
|
case "$PKGMGR" in
|
|
nix) nix-env -iA "nixpkgs.$1" ;;
|
|
apt) $SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y "$2" ;;
|
|
brew) brew install "$3" ;;
|
|
apk) apk add --no-cache "$4" ;;
|
|
esac
|
|
}
|
|
|
|
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.
|
|
#
|
|
# 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
|
|
# fail in the common case - trading one false success for a second
|
|
# failure mode. Naming exactly what breaks is more useful.
|
|
# Prints the problem and returns 0 when docker cannot be used; returns
|
|
# 1 (and prints nothing) when it can.
|
|
docker_problem() {
|
|
if missing docker; then
|
|
echo "docker is not installed"
|
|
return 0
|
|
fi
|
|
if ! docker info >/dev/null 2>&1; then
|
|
echo "the docker daemon is not reachable"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
require_docker() {
|
|
reason="$(docker_problem)" || return 0
|
|
cat >&2 <<EOF
|
|
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/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
|
|
|
|
Install docker (and start the daemon, checking DOCKER_HOST and your
|
|
group membership), then re-run script/bootstrap. golangci-lint on PATH
|
|
is deliberately not a substitute: script/lint will not use it.
|
|
EOF
|
|
exit 1
|
|
}
|
|
|
|
main() {
|
|
cd "$ROOT"
|
|
|
|
# Base tooling (every repo)
|
|
if missing git; then pkg_install git git git git; fi
|
|
if missing make; then pkg_install gnumake make make make; fi
|
|
|
|
# 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.
|
|
|
|
# sqlite3 CLI: the test suite shells out to it (VACUUM).
|
|
if missing sqlite3; then pkg_install sqlite sqlite3 sqlite sqlite; fi
|
|
|
|
go mod download
|
|
|
|
# Last, so that everything installable is installed before the one
|
|
# thing this script cannot install decides the outcome.
|
|
require_docker
|
|
|
|
echo "bootstrap complete"
|
|
}
|
|
|
|
main "$@"
|