build: run all linting in Docker via Dockerfile.lint (closes #104)
check / check (push) Successful in 2m36s
check / check (push) Successful in 2m36s
golangci-lint now runs only inside a container, never on the host. script/lint builds a hash-pinned root Dockerfile.lint; the nix-shell and host golangci-lint paths are gone. A per-run CACHEBUST build-arg is folded into the lint step's cache key, so the linter re-executes on every run and an unchanged tree cannot return a cached success having linted nothing; script/lint fails a build that did not run the linter. Dockerfile's lint stage runs golangci-lint directly, since make lint now builds a container and there is no Docker inside a build. It is the same image and config. golangci-lint config verify is left out: it fetches its schema over an unpinned live HTTPS call, which REPO_POLICIES.md forbids. Model: opus-4-8
This commit is contained in:
+5
-2
@@ -13,9 +13,12 @@ RUN go mod download
|
|||||||
# Copy source code
|
# Copy source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Run formatting check and linter
|
# Run formatting check and linter. The linter is invoked directly, not
|
||||||
|
# via `make lint`: `make lint` now builds Dockerfile.lint, and there is
|
||||||
|
# no Docker inside a Docker build. This is the same linter, image, and
|
||||||
|
# config that Dockerfile.lint and script/lint run.
|
||||||
RUN make fmt-check
|
RUN make fmt-check
|
||||||
RUN make lint
|
RUN golangci-lint run --config .golangci.yml ./...
|
||||||
|
|
||||||
# Build stage
|
# Build stage
|
||||||
# golang:1.25.4-alpine, 2026-02-25
|
# golang:1.25.4-alpine, 2026-02-25
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Dockerfile.lint: the one and only path that runs golangci-lint.
|
||||||
|
#
|
||||||
|
# golangci-lint is never installed on the host; it runs only inside this
|
||||||
|
# build. A clean build of this file therefore IS a clean lint over the
|
||||||
|
# whole tree. It runs the same linter and config as Dockerfile's lint
|
||||||
|
# stage, pinned to the same image so the two cannot drift to different
|
||||||
|
# linter versions.
|
||||||
|
#
|
||||||
|
# golangci/golangci-lint:v2.12.2-alpine, 2026-08-07
|
||||||
|
FROM golangci/golangci-lint:v2.12.2-alpine@sha256:91b27804074a0bacea298707f016911e60cf0cdbc6c7bf5ccacb5f0606d18d60
|
||||||
|
|
||||||
|
# pixa is CGO/libvips: the type-aware linters compile every package, so
|
||||||
|
# this image needs the same C libraries the build does.
|
||||||
|
RUN apk add --no-cache build-base vips-dev libheif-dev pkgconfig
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Modules first for layer caching; go.mod/go.sum settle this layer's
|
||||||
|
# result, so it may safely be reused between runs.
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Caching is deliberately waived for the lint step: an unchanged tree
|
||||||
|
# must still run the linter, not return a cached success in well under a
|
||||||
|
# second having linted nothing. CACHEBUST carries a value that differs
|
||||||
|
# on every run (script/lint supplies it and refuses to build without
|
||||||
|
# one). The lint RUN below references it, so BuildKit cannot serve that
|
||||||
|
# step from cache. Keep the ${CACHEBUST} reference on that step: dropping
|
||||||
|
# it lets the linter cache again and report a green that linted nothing.
|
||||||
|
ARG CACHEBUST
|
||||||
|
RUN test -n "${CACHEBUST}" || { \
|
||||||
|
echo "Dockerfile.lint requires the CACHEBUST build-arg; build it via script/lint." >&2; \
|
||||||
|
exit 1; }
|
||||||
|
|
||||||
|
# `golangci-lint config verify` is deliberately not run: it fetches its
|
||||||
|
# JSON schema over an unpinned live HTTPS call, which REPO_POLICIES.md
|
||||||
|
# forbids for external references.
|
||||||
|
RUN echo "pixa-lint: running golangci-lint (${CACHEBUST})" && \
|
||||||
|
golangci-lint run --config .golangci.yml ./...
|
||||||
@@ -29,6 +29,15 @@ P1: implement blocked networks configuration to extend SSRF protection
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-09-21 run all linting in Docker via `Dockerfile.lint` +
|
||||||
|
`script/lint` (closes #104): `script/lint` builds a hash-pinned root
|
||||||
|
`Dockerfile.lint`, and no host or nix-shell `golangci-lint` path
|
||||||
|
remains; a per-run `CACHEBUST` build-arg forces the lint step to
|
||||||
|
execute every run, so an unchanged tree cannot return a cached green
|
||||||
|
that linted nothing; `Dockerfile`'s lint stage runs `golangci-lint`
|
||||||
|
directly, since `make lint` now builds a container and there is no
|
||||||
|
Docker inside a build; `golangci-lint config verify` stays out, as it
|
||||||
|
fetches its schema over an unpinned live HTTPS call
|
||||||
- 2026-09-21 http.Server hardening (closes #92): added
|
- 2026-09-21 http.Server hardening (closes #92): added
|
||||||
`HTTPReadHeaderTimeout` (10s, bounds the slowloris header dribble) and
|
`HTTPReadHeaderTimeout` (10s, bounds the slowloris header dribble) and
|
||||||
`HTTPIdleTimeout` (120s, bounds keep-alive reuse) alongside the
|
`HTTPIdleTimeout` (120s, bounds keep-alive reuse) alongside the
|
||||||
|
|||||||
+46
-14
@@ -1,23 +1,55 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# script/lint: run the linter. CGO dependencies (pkg-config, vips,
|
# script/lint: run golangci-lint over the whole tree.
|
||||||
# libheif) come from nix-shell when not already available (e.g. inside
|
#
|
||||||
# a Docker build or an existing nix-shell).
|
# The linter is never installed on the host: it runs only inside the
|
||||||
|
# Dockerfile.lint build, one way, everywhere. A clean build is a clean
|
||||||
|
# lint. See Dockerfile.lint for why the lint step cannot be cached.
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
|
|
||||||
run_with_cgo_deps() {
|
main() {
|
||||||
if command -v pkg-config >/dev/null 2>&1; then
|
cd "$ROOT"
|
||||||
sh -c "$1"
|
|
||||||
else
|
# A value no other run repeats. Dockerfile.lint folds it into the
|
||||||
nix-shell -p pkg-config vips libheif golangci-lint git --run "$1"
|
# lint step's cache key, so the linter re-executes every run instead
|
||||||
|
# of an unchanged tree returning a cached success having linted
|
||||||
|
# nothing.
|
||||||
|
cachebust="$(date +%s)-$$"
|
||||||
|
|
||||||
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/pixa-lint.XXXXXX")"
|
||||||
|
trap 'rm -rf "$tmp"' EXIT INT TERM
|
||||||
|
|
||||||
|
# --progress=plain so the lint step's own output reaches the log we
|
||||||
|
# check below; --output=type=cacheonly because we want the linter's
|
||||||
|
# verdict, not an image left in the local store. The build status
|
||||||
|
# travels through a file: a pipeline's exit status is tee's, not the
|
||||||
|
# build's.
|
||||||
|
(
|
||||||
|
set +e
|
||||||
|
docker build \
|
||||||
|
--progress=plain \
|
||||||
|
--build-arg CACHEBUST="$cachebust" \
|
||||||
|
--output=type=cacheonly \
|
||||||
|
-f Dockerfile.lint . 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}"
|
||||||
|
|
||||||
|
# The linter's start line must appear as build output, not only in
|
||||||
|
# the build's echo of the RUN instruction. A step served from cache
|
||||||
|
# prints the instruction and none of its output; a step that runs
|
||||||
|
# prints a "#<n> <elapsed> ..." output line. Requiring that output
|
||||||
|
# line means a future edit dropping the CACHEBUST reference from
|
||||||
|
# Dockerfile.lint fails here rather than passing having linted
|
||||||
|
# nothing.
|
||||||
|
if ! grep -Eq '^#[0-9]+ +[0-9]+\.[0-9]+ +pixa-lint: running golangci-lint' \
|
||||||
|
"$tmp/build.log"; then
|
||||||
|
echo "script/lint: golangci-lint did not execute (cached step?)." >&2
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
|
||||||
cd "$ROOT"
|
|
||||||
echo "Running linter..."
|
|
||||||
run_with_cgo_deps "golangci-lint run"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
Reference in New Issue
Block a user