#!/bin/sh # script/lint: run the linter. golangci-lint is never installed locally: it # runs via docker only, one way, everywhere. # # Traps, each of which yields a green run over an unlinted or partly linted # tree: # # 1. A bare `docker build -f Dockerfile.lint .` serves the lint layer from # cache on an unchanged tree and exits 0 having linted nothing, and # BuildKit ignores a --no-cache-filter whose stage name matches # nothing, restoring that no-op after a rename. So the run is not # trusted: script/assert-step-ran requires the log to show the lint # step executing and golangci-lint's own success line coming out of # it. A cached or absent step fails that. # # 2. .dockerignore decides what reaches the container, and only what # reaches it is linted, so excluding a Go file drops it from the lint # with the linter still reporting `0 issues.` over what it was # handed. So the context is not trusted either: the lint stage # inventories the sources that reached it, and # script/assert-context-complete compares that against the git index, # which .dockerignore cannot touch. Never exclude Go sources, # go.mod/go.sum or .golangci.yml — and now nothing silently does. set -eu SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)" dockerfile=Dockerfile.lint # Must match the stage name in Dockerfile.lint. stage=lint main() { cd "$ROOT" tmp="$(mktemp -d "${TMPDIR:-/tmp}/$("$SCRIPT_DIR/projectname")-lint.XXXXXX")" trap 'rm -rf "$tmp"' EXIT INT TERM # No --target: whatever stage is last is the one BuildKit builds, and # the assertion below is what decides whether the linter was in it. # --progress=plain is load-bearing — the tty renderer discards all but # the tail of a step's output. The exit status travels through a file # because a pipeline's status is the last command's; `set +e` is what # lets the recording line run at all, since errexit would otherwise # abandon the subshell on a failing build. A missing file reads as # failure. ( set +e docker build \ --progress=plain \ --no-cache-filter="$stage" \ --output=type=cacheonly \ -f "$dockerfile" . 2>&1 echo "$?" >"$tmp/status" ) | tee "$tmp/build.log" status="$(cat "$tmp/status" 2>/dev/null || echo 1)" [ "${status:-1}" -eq 0 ] || exit "${status:-1}" script/assert-step-ran "$tmp/build.log" "$stage" \ '^RUN golangci-lint run' '^0 issues[.]$' 'the linter' # That the linter ran says nothing about what it was given. The # expectation comes from git and the evidence from the build, which # is the only reason comparing them means anything. script/repo-source-manifest >"$tmp/expected" script/assert-context-complete "$tmp/build.log" "$stage" "$tmp/expected" } main "$@"