#!/bin/sh # script/lint: run the linter. # # The linter always runs at the version pinned by the Dockerfile's lint # stage, so a local run and a CI run of the same tree cannot disagree. # That FROM line (image tag plus digest) is the single source of truth # for the linter version in this repo: bump it there and nothing else # needs editing. # # Normally that means running the pinned image with docker. The one # exception is a golangci-lint on PATH whose version is exactly equal to # the pin: that is the same linter, so it is run directly. This is what # happens inside the lint container itself (Dockerfile runs `make lint`, # and there is no docker daemon in there). A PATH binary at any other # version is never used - that silent substitution is the bug this # script exists to prevent. # # Extra arguments are passed through to `golangci-lint run`, before # `./...` (see script/lint-fix). set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" DOCKERFILE="$ROOT/Dockerfile" # The image reference of the Dockerfile's lint stage, tag and digest # included, e.g. # golangci/golangci-lint:v2.12.2-alpine@sha256:91b2... lint_image() { awk '$1 == "FROM" && $3 == "AS" && $4 == "lint" { print $2; exit }' \ "$DOCKERFILE" } # The bare version that image reference pins, e.g. 2.12.2 pinned_version() { lint_image | sed -e 's/@.*//' -e 's/.*://' -e 's/^v//' -e 's/-.*//' } # The version of the golangci-lint on PATH, if any, e.g. 2.12.2 installed_version() { command -v golangci-lint >/dev/null 2>&1 || return 0 golangci-lint version 2>/dev/null | awk ' { for (i = 1; i <= NF; i++) { if ($i ~ /^[0-9]+\.[0-9]+\.[0-9]+$/) { print $i exit } } }' } require_docker() { image="$1" if ! command -v docker >/dev/null 2>&1; then cat >&2 </dev/null 2>&1; then cat >&2 <&2 exit 1 fi if [ "$(installed_version)" = "$(pinned_version)" ]; then exec golangci-lint run "$@" ./... fi run_in_docker "$image" "$@" } main "$@"