From 8151185ededf25d6397f575352fd634e1ae6d434 Mon Sep 17 00:00:00 2001 From: efiten Date: Fri, 29 May 2026 09:52:08 +0200 Subject: [PATCH] =?UTF-8?q?fix(ci):=20Dockerfile=20COPY=20invariant=20chec?= =?UTF-8?q?k=20=E2=80=94=20prevent=20missing=20internal/=20Docker=20f?= =?UTF-8?q?ailures=20(#1316)=20(#1432)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Adds `scripts/check-dockerfile-internal-pkgs.sh`: reads `replace => ../../internal/` directives from `cmd/server/go.mod` and `cmd/ingestor/go.mod`, then verifies each referenced package has the correct number of `COPY internal//` lines in `Dockerfile` (one per builder section that needs it) - Wired into CI as a step in the `go-test` job, before CSS lint — runs on every PR, adds ~0.1s - Prevents the recurring failure pattern (#1316): new `internal/` added to go.mod but COPY line forgotten in Dockerfile; non-Docker CI passes, Docker build fails after merge with a cryptic module error Key details: - Counts COPY occurrences per package: if a pkg is referenced in both go.mods (both binaries need it), it must appear in at least 2 builder sections - Anchored regex: only matches actual `replace` directives (not comments) - Anchored grep: skips commented-out `COPY internal/...` lines Closes #1316. ## Test plan - [ ] Run `bash scripts/check-dockerfile-internal-pkgs.sh` locally — exits 0 on current Dockerfile - [ ] Manually remove a `COPY internal/perfio/` line from Dockerfile → script exits 1 with a clear error - [ ] CI step visible in the `go-test` job on this PR 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.6 --- .github/workflows/deploy.yml | 3 ++ scripts/check-dockerfile-internal-pkgs.sh | 56 +++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 scripts/check-dockerfile-internal-pkgs.sh diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6166d476..b5803aba 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -81,6 +81,9 @@ jobs: go test ./... echo "--- Decrypt CLI tests passed ---" + - name: Verify Dockerfile COPY invariants (issue #1316) + run: bash scripts/check-dockerfile-internal-pkgs.sh + - name: Lint CSS variables (issue #1128) run: | set -e diff --git a/scripts/check-dockerfile-internal-pkgs.sh b/scripts/check-dockerfile-internal-pkgs.sh new file mode 100644 index 00000000..c2391893 --- /dev/null +++ b/scripts/check-dockerfile-internal-pkgs.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# scripts/check-dockerfile-internal-pkgs.sh +# +# Asserts every internal/ referenced via a "replace" directive in +# cmd/server/go.mod or cmd/ingestor/go.mod has a matching +# "COPY internal//" line in Dockerfile for each builder section that +# needs it. +# +# This catches the recurring class of Docker build failure (issue #1316): +# go: github.com/meshcore-analyzer/: reading ../../internal//go.mod: +# open /internal//go.mod: no such file or directory +# +# The bug pattern: a PR adds a new internal/, updates both go.mod files +# with a replace directive, but forgets the matching COPY line in Dockerfile. +# All non-Docker CI passes (go build works in-tree). The Docker build fails +# AFTER the PR merges with a cryptic module error. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +DOCKERFILE="$ROOT/Dockerfile" +MODS=("$ROOT/cmd/server/go.mod" "$ROOT/cmd/ingestor/go.mod") +ERRORS=0 + +# PKG_COUNT[pkg] = number of go.mod files that reference internal/. +# The Dockerfile must have at least that many COPY directives — one per +# builder section that compiles a binary depending on the package. +declare -A PKG_COUNT + +for mod in "${MODS[@]}"; do + while IFS= read -r line; do + if [[ "$line" =~ ^[[:space:]]*replace[[:space:]].*=\>[[:space:]]+\.\./\.\./internal/([^[:space:]]+) ]]; then + pkg="${BASH_REMATCH[1]}" + PKG_COUNT["$pkg"]=$(( ${PKG_COUNT["$pkg"]-0} + 1 )) + fi + done < "$mod" +done + +for pkg in "${!PKG_COUNT[@]}"; do + expected="${PKG_COUNT[$pkg]}" + # Anchored ERE: skip commented-out COPY lines (e.g. "# COPY internal/foo/"). + count=$(grep -cE "^[[:space:]]*COPY internal/${pkg}/" "$DOCKERFILE" || true) + count="${count:-0}" + if [[ "$count" -lt "$expected" ]]; then + echo "ERROR: 'COPY internal/${pkg}/' appears ${count} time(s) in Dockerfile, expected at least ${expected} (one per builder section that uses it)" >&2 + ERRORS=$((ERRORS + 1)) + fi +done + +if [[ $ERRORS -gt 0 ]]; then + echo "" >&2 + echo " $ERRORS missing COPY directive(s) — Docker build will fail at module resolution." >&2 + echo " Add the missing COPY line(s) to each relevant builder section of Dockerfile." >&2 + exit 1 +fi + +echo "✓ Dockerfile COPY invariant: all internal/ COPY directives present."