bring the repo up to org standards
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)
This commit is contained in:
125
script/assert-step-ran
Executable file
125
script/assert-step-ran
Executable file
@@ -0,0 +1,125 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user