Re-vendor the canonical files from sneak/prompts at dd4027b (closes #213)
check / check (push) Successful in 12m47s
check / check (push) Successful in 12m47s
Linting and testing become the lint and test phases of the Dockerfile, and the build stage depends on both. Dockerfile.lint, CHECK_EPOCH and the tests that checked them are removed. Every docker build in script/ passes --no-cache, and script/cibuild runs script/bootstrap first. A host without Go gets the go.mod version from script/install-go in .tool/go, which bootstrap, the Makefile, fmt, fmt-check, precommit and release add to PATH; fmt-check skips .tool. The image takes its version from the VERSION build arg or git describe, dev without .git. This repo's own entries follow the canonical content in .gitignore and .editorconfig. The golangci-lint v2.14.0 findings are fixed. The rules in CLAUDE.md move into AGENTS.md. IsDevVersion counts "unknown". Model: opus-5-5
This commit was merged in pull request #236.
This commit is contained in:
@@ -12,48 +12,44 @@ import (
|
||||
// #75). The failure it protects against is silent: the image still
|
||||
// builds and runs, but `vaultik version` inside it reports "commit:
|
||||
// unknown" or a version of "dev", so an operator cannot tell which
|
||||
// source produced a given backup. The build takes the values as build
|
||||
// args, which script/docker computes on the host, and otherwise derives
|
||||
// them from the .git in its context.
|
||||
// source produced a given backup. The build takes the version as a
|
||||
// build arg, which script/docker computes on the host, and otherwise
|
||||
// derives it from the .git in its context; the commit and its date
|
||||
// always come from that .git.
|
||||
//
|
||||
// These are parses of the committed files, for the same reason the lint
|
||||
// guards next door are: shelling out to docker would nest a build
|
||||
// inside `make test`. That `vaultik version` in the built image really
|
||||
// prints the host's version is verified by hand and recorded on the
|
||||
// pull request.
|
||||
// These are parses of the committed files, because shelling out to
|
||||
// docker would nest a build inside `make test`. That `vaultik version`
|
||||
// in the built image really prints the host's version is verified by
|
||||
// hand.
|
||||
|
||||
// dockerScript is script/docker, relative to the repository root.
|
||||
const dockerScript = "script/docker"
|
||||
// The files under guard, relative to the repository root.
|
||||
const (
|
||||
productDockerfile = "Dockerfile"
|
||||
dockerScript = "script/docker"
|
||||
)
|
||||
|
||||
// versionArgs are the ldflag targets the build stamps and, matching
|
||||
// them, the build args the host must supply. The names line up so the
|
||||
// same list checks both files.
|
||||
func versionArgs() []string {
|
||||
return []string{"VERSION", "COMMIT", "COMMIT_DATE"}
|
||||
}
|
||||
|
||||
// TestProductDockerfileTakesVersionAsBuildArgs fails unless the build
|
||||
// declares each version arg, with no default, and stamps it into the
|
||||
// binary whenever it is given, ahead of the value derived in the
|
||||
// container.
|
||||
func TestProductDockerfileTakesVersionAsBuildArgs(t *testing.T) {
|
||||
// TestProductDockerfileTakesVersionAsBuildArg fails unless the build
|
||||
// declares ARG VERSION, with no default, and stamps it into the binary
|
||||
// whenever it is given, ahead of the value derived in the container.
|
||||
func TestProductDockerfileTakesVersionAsBuildArg(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
found := instructions(t, productDockerfile)
|
||||
|
||||
for _, arg := range versionArgs() {
|
||||
require.Contains(t, found, "ARG "+arg,
|
||||
"%s must declare `ARG %s`, with no default, so the host can"+
|
||||
" pass it in", productDockerfile, arg)
|
||||
require.Contains(t, found, "ARG VERSION",
|
||||
"%s must declare `ARG VERSION`, with no default, so the host can"+
|
||||
" pass it in", productDockerfile)
|
||||
|
||||
assertLdflagReferences(t, found, arg)
|
||||
}
|
||||
assertLdflagReferences(t, found, "VERSION")
|
||||
}
|
||||
|
||||
// TestProductDockerfileDerivesVersionFromGit fails unless a build given
|
||||
// no VERSION, such as a plain `docker build .` of a clone, takes it from
|
||||
// `git describe` of the .git in its context, and fails rather than
|
||||
// stamp "dev" when that .git yields no version.
|
||||
// `git describe` of the .git in its context, stamps "dev" when the
|
||||
// context has no .git, and fails rather than stamp "dev" when that .git
|
||||
// yields no version. The commit and its date come from the same .git,
|
||||
// and the build fails rather than stamp them "unknown" when it is
|
||||
// present.
|
||||
func TestProductDockerfileDerivesVersionFromGit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -62,31 +58,31 @@ func TestProductDockerfileDerivesVersionFromGit(t *testing.T) {
|
||||
buildAt := indexContaining(found, "go build")
|
||||
require.GreaterOrEqual(t, buildAt, 0, "%s must build", productDockerfile)
|
||||
|
||||
assert.Contains(t, found[buildAt], "git describe --tags --always",
|
||||
"%s must derive the version from git when no VERSION is given",
|
||||
productDockerfile)
|
||||
assert.Contains(t, found[buildAt], "git describe --tags --always || echo dev",
|
||||
"%s must derive the version from git when no VERSION is given,"+
|
||||
" and stamp dev when the context has no .git", productDockerfile)
|
||||
assert.Contains(t, found[buildAt], "[ -e .git ]",
|
||||
"%s must fail when the context carries .git but yields no version",
|
||||
productDockerfile)
|
||||
assert.Contains(t, found[buildAt], "git rev-parse HEAD",
|
||||
"%s must stamp the commit from git", productDockerfile)
|
||||
assert.Contains(t, found[buildAt], "git show -s --format=%cs HEAD",
|
||||
"%s must stamp the commit date from git", productDockerfile)
|
||||
assert.Contains(t, found[buildAt],
|
||||
`[ "$commit" = unknown ] || [ "$commit_date" = unknown ]`,
|
||||
"%s must fail when the context carries .git but yields no commit"+
|
||||
" or date", productDockerfile)
|
||||
}
|
||||
|
||||
// TestDockerScriptComputesVersionOnTheHost fails unless script/docker
|
||||
// derives each value where .git exists and passes it as a build arg,
|
||||
// with VERSION coming from script/version so a Docker build reports the
|
||||
// same string a local build of the same tree would.
|
||||
// passes the version it derives where .git exists as a build arg.
|
||||
func TestDockerScriptComputesVersionOnTheHost(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
script := readRepoFile(t, dockerScript)
|
||||
|
||||
for _, arg := range versionArgs() {
|
||||
assert.Contains(t, script, "--build-arg "+arg+"=",
|
||||
"%s must pass --build-arg %s to the build", dockerScript, arg)
|
||||
}
|
||||
|
||||
assert.Contains(t, script, "/version",
|
||||
"%s must take VERSION from script/version, as the Makefile does",
|
||||
dockerScript)
|
||||
assert.Contains(t, script, "--build-arg VERSION=",
|
||||
"%s must pass --build-arg VERSION to the build", dockerScript)
|
||||
}
|
||||
|
||||
// assertLdflagReferences fails unless the build instruction uses the
|
||||
@@ -107,3 +103,47 @@ func assertLdflagReferences(t *testing.T, found []string, arg string) {
|
||||
"the go build in %s must use ${%s:-...}, or the arg is passed and"+
|
||||
" discarded", productDockerfile, arg)
|
||||
}
|
||||
|
||||
// instructions returns the Dockerfile's instructions, one per element,
|
||||
// with comments and blank lines dropped and continuation lines joined,
|
||||
// so a multi-line RUN is one string.
|
||||
func instructions(t *testing.T, name string) []string {
|
||||
t.Helper()
|
||||
|
||||
var (
|
||||
out []string
|
||||
continued string
|
||||
isContinued bool
|
||||
)
|
||||
|
||||
for line := range strings.SplitSeq(readRepoFile(t, name), "\n") {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if !isContinued && (trimmed == "" || strings.HasPrefix(trimmed, "#")) {
|
||||
continue
|
||||
}
|
||||
|
||||
isContinued = strings.HasSuffix(trimmed, `\`)
|
||||
continued += strings.TrimSuffix(trimmed, `\`)
|
||||
|
||||
if isContinued {
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, strings.Join(strings.Fields(continued), " "))
|
||||
continued = ""
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// indexContaining returns the position of the first instruction
|
||||
// containing want; -1 if there is none.
|
||||
func indexContaining(found []string, want string) int {
|
||||
for i, instruction := range found {
|
||||
if strings.Contains(instruction, want) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user