Adopt the standard tooling: a Makefile of thin shims over a full scripts-to-rule-them-all `script/` set, the canonical `.golangci.yml` vendored byte-identical from `sneak/prompts`, docker-only linting via `Dockerfile.lint`, a `Dockerfile` and Gitea workflow that gate every push, and prettier/editorconfig/dockerignore config. `make build` pointed at a `cmd/dcfinfo` that is not in the tree and could never have succeeded; this repo is a library, so `build` is now the compile check over every package. Clear the 57 findings the canonical linter config reports on `pkg/dcf`. Two were real: `findDCFMountPoints` checked a never-assigned `erro` instead of the error from `findAllMountPoints`, discarding it, and `privatePath` was computed twice so the first computation was dead. Mountpoint selection otherwise behaves exactly as before; the defects that survive are filed as issue #6, not fixed here. Rename `DCFStore` to `Store` and `DCFObject` to `Object` (with its `DCFStoreRoot` field to `StoreRoot`), which revive's stutter rule requires and which the `fs.FS` rework in issue #4 will build on. Replace the placeholder test with tests over the exported surface. The filesystem walk stays uncovered: it is reachable only through `GetDCFStores`, which needs real mounted media. README keeps its content, reorganised into the required sections and gaining Entrypoints. (closes #1)
126 lines
5.3 KiB
Bash
Executable File
126 lines
5.3 KiB
Bash
Executable File
#!/bin/sh
|
|
# script/assert-step-ran: fail unless a docker build actually executed a
|
|
# named step, proved by output the step's own tool wrote.
|
|
#
|
|
# Usage: script/assert-step-ran LOG STAGE STEP_REGEX EVIDENCE_REGEX WHAT
|
|
#
|
|
# LOG must be `docker build --progress=plain` output. The default tty
|
|
# renderer rewrites lines in place and keeps only the tail of a step's
|
|
# output, so the flag is load-bearing wherever this is used.
|
|
#
|
|
# It passes only if some step in LOG satisfies all three of:
|
|
#
|
|
# 1. its header is "#ID [STAGE n/m] CMD" with CMD matching STEP_REGEX,
|
|
# 2. one of the lines that step wrote matches EVIDENCE_REGEX,
|
|
# 3. BuildKit reported "#ID DONE".
|
|
#
|
|
# This observes the build that happened; it parses no Dockerfile. A
|
|
# stage renamed, moved, made unreachable, deleted, split by a whitespace
|
|
# byte BuildKit treats as a separator, or an instruction rewritten —
|
|
# none of it can make this pass a build in which the step did not run,
|
|
# because a step that did not run wrote no output, and a step served
|
|
# from cache writes none either ("#ID CACHED", no output lines).
|
|
#
|
|
# The stage name in a header is BuildKit's label for that vertex, not a
|
|
# reading of the file being built. A vertex shared with an earlier build
|
|
# of a DIFFERENT Dockerfile is replayed under the label it was first
|
|
# recorded with, so a `Dockerfile.lint` run can print `[lint 1/8]` for
|
|
# the main `Dockerfile`'s lint stage. A borrowed label cannot produce a
|
|
# pass — a replayed vertex prints CACHED and writes no evidence, and a
|
|
# vertex that really executes really ran the command in the header — but
|
|
# it does mean a header alone proves nothing about which stages the file
|
|
# declares. Hence one failure message naming both causes rather than a
|
|
# split that would claim to know which.
|
|
#
|
|
# What this guard cannot see — not an exhaustive list of ways a green can
|
|
# be untrue, only the ones known:
|
|
#
|
|
# - Evidence forged inside the matched step, e.g.
|
|
# `RUN golangci-lint run ... || echo "0 issues."`. Condition 1 ties
|
|
# the evidence to a step whose own command is in the log, so the
|
|
# forgery has to be written into that command in the Dockerfile, in
|
|
# plain sight.
|
|
# This guards against a step falling silently out of the build. It does
|
|
# not certify that what ran examined everything it should have — a
|
|
# `.dockerignore` entry excluding a package makes the linter genuinely
|
|
# run and genuinely print `0 issues.` while a real violation sits
|
|
# unexamined in the repo. That is a different question, asked separately
|
|
# by script/assert-context-complete, which every caller of this script
|
|
# also runs.
|
|
#
|
|
# What it depends on — BuildKit's plain progress format and the tool's
|
|
# own success wording — fails the caller loudly if it drifts, because
|
|
# drift removes a match rather than creating one.
|
|
set -eu
|
|
|
|
die() {
|
|
echo "script/assert-step-ran: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
[ $# -eq 5 ] || die "usage: $0 LOG STAGE STEP_REGEX EVIDENCE_REGEX WHAT"
|
|
[ -r "$1" ] || die "cannot read $1"
|
|
|
|
# The regexes travel in the environment, not in -v: awk expands escape
|
|
# sequences in a -v assignment, which would eat a backslash before the
|
|
# regex ever sees it.
|
|
ASR_STAGE="$2" ASR_STEP="$3" ASR_EVIDENCE="$4" ASR_WHAT="$5" awk '
|
|
function fail(msg) {
|
|
printf "script/assert-step-ran: %s\n", msg | "cat 1>&2"
|
|
close("cat 1>&2")
|
|
exit 1
|
|
}
|
|
|
|
BEGIN {
|
|
stage = ENVIRON["ASR_STAGE"]
|
|
step = ENVIRON["ASR_STEP"]
|
|
evidence = ENVIRON["ASR_EVIDENCE"]
|
|
what = ENVIRON["ASR_WHAT"]
|
|
}
|
|
|
|
# Every progress line is "#ID " and then a step header, a status, or one
|
|
# line the step itself wrote.
|
|
/^#[0-9]+ / {
|
|
id = substr($1, 2)
|
|
rest = substr($0, length($1) + 2)
|
|
|
|
if (rest == "CACHED") { cached[id] = 1; next }
|
|
if (rest ~ /^DONE /) { done[id] = 1; next }
|
|
|
|
# Header: "[STAGE n/m] CMD", or "[PLATFORM STAGE n/m] CMD" when the
|
|
# build names a platform. Bracketed spans with no n/m are BuildKit
|
|
# internals ("[internal] load build context"), not steps.
|
|
if (substr(rest, 1, 1) == "[") {
|
|
p = index(rest, "] ")
|
|
if (p == 0) next
|
|
n = split(substr(rest, 2, p - 2), part, " ")
|
|
if (n < 2 || part[n] !~ /^[0-9]+\/[0-9]+$/) next
|
|
if (part[n - 1] != stage) next
|
|
if (substr(rest, p + 2) ~ step) matched[id] = 1
|
|
next
|
|
}
|
|
|
|
# Output: "#ID 41.80 <the line the step wrote>".
|
|
if (rest ~ /^[0-9]+[.][0-9]+ /) {
|
|
sub(/^[0-9]+[.][0-9]+ /, "", rest)
|
|
if (rest ~ evidence) emitted[id] = 1
|
|
}
|
|
}
|
|
|
|
END {
|
|
for (id in matched) {
|
|
nmatched++
|
|
if (done[id] && emitted[id]) exit 0
|
|
if (cached[id]) ncached++
|
|
}
|
|
|
|
if (nmatched == 0)
|
|
fail(sprintf("the build ran no step matching /%s/ in stage \"%s\", so %s did not run. Either the stage is not in the build graph — renamed, deleted, or nothing the final stage builds depends on it any more — or it was built without that command among its steps", step, stage, what))
|
|
|
|
if (ncached == nmatched)
|
|
fail(sprintf("every step matching /%s/ in stage \"%s\" was served from cache, so %s did not run on this tree; the cache bust for that stage is not taking effect", step, stage, what))
|
|
|
|
fail(sprintf("a step matching /%s/ ran in stage \"%s\" but never wrote a line matching /%s/, so there is no evidence %s did the work; the command or the tool that produces that line has changed", step, stage, evidence, what))
|
|
}
|
|
' "$1"
|