#!/usr/bin/env bash # Guard: Dockerfile.variant must OPEN with its BuildKit `# check=` directive. # # Why this is a gate and not a comment. BuildKit parses `# check=` ONLY before # any other line in the file, so moving it below the title comment — or dropping # it during an unrelated header edit — silently re-enables the # InvalidDefaultArgInFrom warning on `ARG BASE_IMAGE` / `FROM ${BASE_IMAGE}`. # # A re-enabled warning is not cosmetic. buildx attaches the warning's source # context to the build metadata as buildx.build.warnings[].sourceInfo.data: the # entire Dockerfile, base64, on ONE line. docker/build-push-action writes that # metadata to $GITHUB_OUTPUT as a `name<` heredoc, and # Gitea's act_runner truncates any single line at exactly 65536 chars. Since # base64(Dockerfile.variant) passed 64 KiB (50034 B source -> 66712 chars, cut # to 65536), the closing delimiter is lost and act_runner fails the job with # invalid format delimiter 'ghadelimiter_...' not found before end of file # while attributing it to NO step: every step reports Success, the smoke suite # prints "0 failed", and the job is red regardless. That cost two tags — # v1.10.0 (run 704, where a stale clipboard assertion masked it) and v1.10.1 # (run 707) — and most of a session to localise. # # So: one deterministic grep, run both on push AND in the publish workflow's # lint-gate. A check that gates only `push` lets a tag regress — the same # adoption slip that let doc-drift land 27 h after the v1.9.3 tag. # # Exit codes: 0 OK, 1 violation, 2 cannot-run (missing file) — matching # lint-shell.sh / check-skill-floor.sh / check-doc-drift.sh, because a gate that # cannot run must not pass. set -euo pipefail DF="${1:-Dockerfile.variant}" EXPECTED='# check=skip=InvalidDefaultArgInFrom' if [ ! -f "$DF" ]; then echo "::error::check-dockerfile-directives: '$DF' not found (cannot-run)" >&2 exit 2 fi first="$(head -n 1 "$DF")" if [ "$first" != "$EXPECTED" ]; then { echo "::error::$DF line 1 must be exactly: $EXPECTED" echo "::error::found instead: ${first:-}" echo "::error::" echo "::error::BuildKit only honours '# check=' before any other line. Without it the" echo "::error::InvalidDefaultArgInFrom warning returns, buildx embeds this whole file as" echo "::error::base64 in buildx.build.warnings[].sourceInfo.data, that single line exceeds" echo "::error::act_runner's 65536-char cap, and the smoke jobs fail with" echo "::error:: invalid format delimiter 'ghadelimiter_...' not found before end of file" echo "::error::and NO failing step. See the header of $DF for the full measurement." } >&2 exit 1 fi # Second, independent assertion: the condition the directive exists FOR. If a # later edit gives ARG BASE_IMAGE a default, the directive becomes dead weight # and should be removed deliberately rather than left to rot — and if the ARG is # renamed, this guard would otherwise keep passing while guarding nothing. if ! grep -qE '^ARG BASE_IMAGE$' "$DF"; then { echo "::error::$DF no longer contains a bare 'ARG BASE_IMAGE' (no default)." echo "::error::That is the only thing '$EXPECTED' suppresses. Either restore the bare ARG" echo "::error::or drop the directive and this guard together — do not leave a skip" echo "::error::directive pointing at a check that can no longer fire." } >&2 exit 1 fi echo "Dockerfile directive guard OK — $DF opens with the check-skip directive and still declares a bare ARG BASE_IMAGE."