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"