A plain `docker build .` stamped `dev`: `.dockerignore` left out `.git` and the Dockerfile defaulted VERSION to `dev`. `.dockerignore` now sends `.git` without `.git/config`. Given no build arguments, the builder stamps `git describe --tags --always` and the commit and date from git, and fails if `.git` is present but yields no version. The empty CHECK_EPOCH refusal is gone so the plain build succeeds. `script/version` now prints `git describe --tags --always --dirty`, so make, the scripts and a plain build agree. `vaultik version` treats the short commit, tag-N-gHASH forms and any version ending in `-dirty` as development builds, so they keep the development-build notice. Model: opus-5-5
110 lines
3.9 KiB
Go
110 lines
3.9 KiB
Go
package main_test
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// This file guards the version stamping of the product image (issue
|
|
// #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.
|
|
//
|
|
// 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.
|
|
|
|
// dockerScript is script/docker, relative to the repository root.
|
|
const 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) {
|
|
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)
|
|
|
|
assertLdflagReferences(t, found, arg)
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
func TestProductDockerfileDerivesVersionFromGit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
found := instructions(t, productDockerfile)
|
|
|
|
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], "[ -e .git ]",
|
|
"%s must fail when the context carries .git but yields no version",
|
|
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.
|
|
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)
|
|
}
|
|
|
|
// assertLdflagReferences fails unless the build instruction uses the
|
|
// named ARG whenever it is given (a ${arg:- reference), so a value
|
|
// passed in is not overridden by one derived inside the container.
|
|
func assertLdflagReferences(t *testing.T, found []string, arg string) {
|
|
t.Helper()
|
|
|
|
for _, instruction := range found {
|
|
if strings.HasPrefix(instruction, "RUN ") &&
|
|
strings.Contains(instruction, "go build") &&
|
|
strings.Contains(instruction, "${"+arg+":-") {
|
|
return
|
|
}
|
|
}
|
|
|
|
assert.Fail(t, "version arg is declared but never stamped",
|
|
"the go build in %s must use ${%s:-...}, or the arg is passed and"+
|
|
" discarded", productDockerfile, arg)
|
|
}
|