From 7a81afec6526c7932aaabdcd6e897c7c03a1db97 Mon Sep 17 00:00:00 2001 From: Ryan McGuire Date: Sun, 9 Aug 2026 11:48:50 -0400 Subject: [PATCH] Lint by piping files to the linters instead of bind-mounting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `make lint` passed locally but failed in Gitea Actions with every Dockerfile reported as missing: hadolint: images/node-agent/Dockerfile: withBinaryFile: does not exist The job itself runs in a container, and the checkout lives in a docker volume rather than on the host filesystem. Starting hadolint as a sibling container through the host's docker socket meant `-v "$(CURDIR)":/repo` was resolved against the HOST, where /workspace/... does not exist — so Docker created an empty directory and mounted that. Stream each file in on stdin instead. No shared filesystem is involved, so the same command works locally and in CI. The linters then only ever see "-" as the filename, so hack/lint.sh prints the real path itself, and only on failure. Reproduced the original error and verified the fix against a faithful local simulation: the repo in a docker volume at a path absent from the host, with the socket mounted, running the real runner image. Co-Authored-By: Claude Opus 5 (1M context) --- Makefile | 11 ++------- hack/lint.sh | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 9 deletions(-) create mode 100755 hack/lint.sh diff --git a/Makefile b/Makefile index 4874f6f..f3c6b52 100644 --- a/Makefile +++ b/Makefile @@ -14,10 +14,6 @@ TARGETS := $(if $(IMAGE),$(IMAGE),$(IMAGES)) # CI overrides REF with the real published ref so the smoke test runs against it. REF ?= -HADOLINT ?= hadolint/hadolint:latest-alpine -SHELLCHECK ?= koalaman/shellcheck:stable -SCRIPTS := $(wildcard hack/*.sh images/*/test.sh template/test.sh) - .DEFAULT_GOAL := help .PHONY: help list check lint hadolint shellcheck build test all @@ -40,13 +36,10 @@ check: ## Verify each image directory follows the expected layout exit $$rc hadolint: ## Lint every Dockerfile - @docker run --rm -v "$(CURDIR)":/repo:ro -w /repo $(HADOLINT) \ - hadolint $(addprefix images/,$(addsuffix /Dockerfile,$(IMAGES))) template/Dockerfile - @echo "hadolint ok" + @hack/lint.sh dockerfiles shellcheck: ## Lint every shell script - @docker run --rm -v "$(CURDIR)":/repo:ro -w /repo $(SHELLCHECK) $(SCRIPTS) - @echo "shellcheck ok" + @hack/lint.sh scripts lint: hadolint shellcheck ## Run all linters diff --git a/hack/lint.sh b/hack/lint.sh new file mode 100755 index 0000000..f009510 --- /dev/null +++ b/hack/lint.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# Lint Dockerfiles with hadolint and shell scripts with shellcheck. +# +# Each file is streamed into the linter container on stdin instead of bind-mounting +# the repository. That is deliberate: in Gitea Actions the job itself runs in a +# container, and the checkout lives in a docker volume rather than on the host +# filesystem. A sibling container started through the host's docker socket resolves +# `-v "$PWD":/repo` against the HOST, where that path does not exist — Docker then +# silently creates an empty directory and the linter reports every file as missing. +# Piping needs no shared filesystem, so one command works locally and in CI. +# +# The linters therefore only ever see "-" as the filename, so the real path is +# printed here. Note this also means shellcheck cannot follow `source` directives; +# none of these scripts use them. +# +# Usage: lint.sh [dockerfiles|scripts|all] +set -euo pipefail +cd "$(dirname "$0")/.." + +HADOLINT_IMAGE=${HADOLINT_IMAGE:-hadolint/hadolint:latest-alpine} +SHELLCHECK_IMAGE=${SHELLCHECK_IMAGE:-koalaman/shellcheck:stable} + +rc=0 + +report() { + local file=$1 out=$2 ok=$3 + if [ "$ok" = 0 ]; then + printf ' ok %s\n' "$file" + else + printf ' FAIL %s\n' "$file" + [ -n "$out" ] && printf '%s\n' "$out" | sed 's/^/ /' + rc=1 + fi +} + +dockerfiles() { + local file out ok + for file in images/*/Dockerfile template/Dockerfile; do + [ -f "$file" ] || continue + ok=0 + out=$(docker run --rm --interactive "$HADOLINT_IMAGE" \ + hadolint --no-color - <"$file" 2>&1) || ok=$? + report "$file" "$out" "$ok" + done +} + +scripts() { + local file out ok + for file in hack/*.sh images/*/test.sh template/test.sh; do + [ -f "$file" ] || continue + ok=0 + out=$(docker run --rm --interactive "$SHELLCHECK_IMAGE" - <"$file" 2>&1) || ok=$? + report "$file" "$out" "$ok" + done +} + +case "${1:-all}" in + dockerfiles) dockerfiles ;; + scripts) scripts ;; + all) + dockerfiles + scripts + ;; + *) + echo "usage: ${0##*/} [dockerfiles|scripts|all]" >&2 + exit 2 + ;; +esac + +exit "$rc"