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)
167 lines
6.9 KiB
Bash
Executable File
167 lines
6.9 KiB
Bash
Executable File
#!/bin/sh
|
|
# script/assert-context-complete: fail unless the build context a stage
|
|
# was given contained every source file the repository has.
|
|
#
|
|
# Usage: script/assert-context-complete LOG STAGE EXPECTED
|
|
#
|
|
# LOG must be `docker build --progress=plain` output. EXPECTED is a list
|
|
# of repo-relative paths, one per line, as script/repo-source-manifest
|
|
# prints it.
|
|
#
|
|
# script/assert-step-ran answers "did the tool execute". This answers
|
|
# the other half: "was the tool given the tree". They are different
|
|
# questions, and a green build can satisfy the first while failing the
|
|
# second — a .dockerignore entry, or a COPY that brings in less than the
|
|
# whole tree, removes files from the context silently, and golangci-lint
|
|
# then genuinely runs, genuinely examines what it was handed, and
|
|
# genuinely reports `0 issues.` over a repo that has a violation in it.
|
|
# `go test ./...` likewise never runs a package that did not arrive.
|
|
#
|
|
# How it knows: each source-consuming stage emits, right after its
|
|
# `COPY . .`, an inventory of the Go files actually present —
|
|
#
|
|
# context-manifest-begin
|
|
# context-file: cmd/gotemplate/main.go
|
|
# ...
|
|
# context-manifest-end
|
|
#
|
|
# — and this compares that against EXPECTED, which comes from the git
|
|
# index rather than from the same build. The two sources are
|
|
# independent: .dockerignore decides the first and cannot touch the
|
|
# second. An expectation read from the evidence would prove nothing.
|
|
#
|
|
# The manifest lines are only believed under the same discipline
|
|
# script/assert-step-ran applies to a tool's success line: they must be
|
|
# attributed to the id of a step whose header is "#ID [STAGE n/m] CMD"
|
|
# with CMD matching the manifest command, which BuildKit reported DONE,
|
|
# and both sentinels must be present. So another step's output does not
|
|
# count, a header does not count, a cached step (which writes nothing)
|
|
# does not count, and a truncated log fails rather than passing with a
|
|
# short list.
|
|
#
|
|
# Only one direction is checked: everything the repo has must have
|
|
# arrived. Files in the context that git does not track are not a
|
|
# failure — untracked local work is normal and hides nothing.
|
|
#
|
|
# What this does not cover: a Dockerfile edit to the manifest step
|
|
# itself, and evidence forged inside a matched command. Both are visible
|
|
# in the Dockerfile in plain sight, and both are equally outside
|
|
# script/assert-step-ran. This is not an exhaustive list of ways a green
|
|
# run can be untrue; it is the ones known.
|
|
set -eu
|
|
|
|
die() {
|
|
echo "script/assert-context-complete: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
[ $# -eq 3 ] || die "usage: $0 LOG STAGE EXPECTED"
|
|
[ -r "$1" ] || die "cannot read $1"
|
|
[ -r "$3" ] || die "cannot read $3"
|
|
|
|
# The stage and the expected-list path travel in the environment, not in
|
|
# -v: awk expands escape sequences in a -v assignment.
|
|
ACC_STAGE="$2" ACC_EXPECTED="$3" awk '
|
|
function fail(msg) {
|
|
printf "script/assert-context-complete: %s\n", msg | "cat 1>&2"
|
|
close("cat 1>&2")
|
|
exit 1
|
|
}
|
|
|
|
# The package a missing file belongs to — its directory, which is what a
|
|
# reader needs named. A bare "." for a root-level file reads as a typo
|
|
# next to the sentence punctuation, so it is spelled out.
|
|
function pkgof(path, i) {
|
|
i = length(path)
|
|
while (i > 0 && substr(path, i, 1) != "/") i--
|
|
return i == 0 ? "the repository root" : substr(path, 1, i - 1)
|
|
}
|
|
|
|
function preview(list, n, limit, i, out) {
|
|
for (i = 1; i <= n && i <= limit; i++)
|
|
out = out (i > 1 ? ", " : "") list[i]
|
|
if (n > limit)
|
|
out = out sprintf(", and %d more", n - limit)
|
|
return out
|
|
}
|
|
|
|
BEGIN {
|
|
stage = ENVIRON["ACC_STAGE"]
|
|
|
|
# Fixed by the protocol the Dockerfile emits, so callers cannot
|
|
# drift from it.
|
|
step = "^RUN echo context-manifest-begin"
|
|
beginmark = "context-manifest-begin"
|
|
endmark = "context-manifest-end"
|
|
prefix = "context-file: "
|
|
|
|
expected_path = ENVIRON["ACC_EXPECTED"]
|
|
while ((getline line < expected_path) > 0)
|
|
if (line != "") expected[++nexpected] = line
|
|
close(expected_path)
|
|
|
|
if (nexpected == 0)
|
|
fail(sprintf("the expected-file list (%s) is empty, so any build context would satisfy this check", expected_path))
|
|
}
|
|
|
|
# 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, 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 0.31 <the line the step wrote>".
|
|
if (rest ~ /^[0-9]+[.][0-9]+ /) {
|
|
sub(/^[0-9]+[.][0-9]+ /, "", rest)
|
|
if (rest == beginmark) begun[id] = 1
|
|
else if (rest == endmark) ended[id] = 1
|
|
else if (index(rest, prefix) == 1)
|
|
arrived[id SUBSEP substr(rest, length(prefix) + 1)] = 1
|
|
}
|
|
}
|
|
|
|
END {
|
|
for (id in matched) {
|
|
nmatched++
|
|
if (cached[id]) ncached++
|
|
if (done[id] && begun[id] && ended[id]) chosen = id
|
|
}
|
|
|
|
if (nmatched == 0)
|
|
fail(sprintf("the build ran no step matching /%s/ in stage \"%s\", so the build context was never inventoried. Either the stage is not in the build graph — renamed, deleted, or nothing the final stage builds depends on it any more — or the manifest step was taken out of it", step, stage))
|
|
|
|
if (chosen == "" && ncached == nmatched)
|
|
fail(sprintf("every step matching /%s/ in stage \"%s\" was served from cache, so its inventory describes an earlier tree rather than this one; the cache bust for that stage is not taking effect", step, stage))
|
|
|
|
if (chosen == "")
|
|
fail(sprintf("a step matching /%s/ ran in stage \"%s\" but wrote no complete inventory between %s and %s. The log is truncated, the build was not run with --progress=plain, or that step no longer emits the manifest", step, stage, beginmark, endmark))
|
|
|
|
for (i = 1; i <= nexpected; i++) {
|
|
if (arrived[chosen SUBSEP expected[i]]) continue
|
|
missing[++nmissing] = expected[i]
|
|
pkg = pkgof(expected[i])
|
|
if (!(pkg in seenpkg)) { seenpkg[pkg] = 1; pkgs[++npkgs] = pkg }
|
|
}
|
|
|
|
if (nmissing == 0) exit 0
|
|
|
|
fail(sprintf("%d of the %d source files this repository tracks never reached the build context of stage \"%s\", so nothing examined them. Missing package(s): %s. Missing file(s): %s. A .dockerignore entry, or a COPY that brings in less than the whole tree, drops files silently: the tool still runs, still finds nothing wrong with what it was handed, and still reports success", nmissing, nexpected, stage, preview(pkgs, npkgs, 10), preview(missing, nmissing, 10)))
|
|
}
|
|
' "$1"
|