an image artifact may name its own build context, apart from the module's repository
route-proxy's own Dockerfile documents the shape it has always needed and
never had: 'the proxy source is not vendored here... the build context is
the mesh-controller repository root, and this Dockerfile compiles
./examples/route-proxy from it.' Nothing in the mesh could do that — the
build command clones one repository and builds every artifact from
within it, so route-proxy has never once been built through the pipeline,
consistent with it never having been assigned anywhere. Found attempting
exactly that build tonight: 'stat go.mod: file does not exist', because
the context was mesh-catalog, which does not have one.
An image artifact may now carry a context: {repository, ref}, cloned
fresh alongside the module's own tree. The recipe (Dockerfile) is still
read from the module's own directory, at the module's own commit — only
docker build's own context argument moves. Packaging and source stay
exactly as separate as route-proxy's own comment already said they were,
now for real.
This commit is contained in:
@@ -18,13 +18,19 @@ import (
|
||||
// tree, that two builds of one commit produce one digest. Running docker here would test docker.
|
||||
|
||||
type recorded struct {
|
||||
ran []string
|
||||
ran []string
|
||||
// dirs is the directory each entry in ran was run from, same index — so a test can ask not
|
||||
// only what ran but where.
|
||||
dirs []string
|
||||
images map[string]string
|
||||
archives map[string]string
|
||||
failPush bool
|
||||
// contents is what a clone of this repository lands, so the fake clone can restore the tree
|
||||
// Build deliberately removes first.
|
||||
contents map[string]string
|
||||
// secondary is what a clone of a repository OTHER than the one under test lands, keyed by
|
||||
// that repository's URL — an artifact's own build context, cloned apart from the module.
|
||||
secondary map[string]map[string]string
|
||||
// stamped is the modification time the clone gives every file. Set differently between two
|
||||
// builds of one commit, because otherwise both land in the same second and a packer that
|
||||
// carried timestamps would still produce one digest — which is a test that passes for a
|
||||
@@ -35,13 +41,19 @@ type recorded struct {
|
||||
func (r *recorded) run(_ context.Context, dir, name string, args ...string) (string, error) {
|
||||
line := name + " " + strings.Join(args, " ")
|
||||
r.ran = append(r.ran, line)
|
||||
r.dirs = append(r.dirs, dir)
|
||||
switch {
|
||||
case name == "git" && len(args) > 0 && args[0] == "clone":
|
||||
repository := args[len(args)-2]
|
||||
tree := args[len(args)-1]
|
||||
if err := os.MkdirAll(tree, 0o755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
for path, body := range r.contents {
|
||||
lands := r.contents
|
||||
if by, is := r.secondary[repository]; is {
|
||||
lands = by
|
||||
}
|
||||
for path, body := range lands {
|
||||
full := filepath.Join(tree, path)
|
||||
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
|
||||
return "", err
|
||||
@@ -59,7 +71,6 @@ func (r *recorded) run(_ context.Context, dir, name string, args ...string) (str
|
||||
case name == "git" && len(args) > 0 && args[0] == "rev-parse":
|
||||
return "c0ffeec0ffeec0ffeec0ffeec0ffeec0ffeec0ff\n", nil
|
||||
}
|
||||
_ = dir
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -331,3 +342,72 @@ func TestAPathThatLeavesTheRepositoryIsRefused(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **Packaging and source are allowed to live apart** — a module that ships only the recipe for
|
||||
// source that lives in a second repository (the reference route-proxy, packaged in the catalogue
|
||||
// but built from mesh-controller's own repository) names where that source actually is, rather
|
||||
// than vendoring a second copy the two could drift from.
|
||||
func TestAnArtifactWithItsOwnContextIsBuiltFromThere(t *testing.T) {
|
||||
const withContext = `{"module":"route-proxy","version":"1",
|
||||
"build":{"artifacts":[
|
||||
{"name":"server","kind":"image","from":"Dockerfile",
|
||||
"context":{"repository":"https://forge.invalid/source.git","ref":"main"}}]}}`
|
||||
r := &recorded{
|
||||
contents: map[string]string{
|
||||
ManifestName: withContext,
|
||||
// The recipe lives with the packaging, not the source — read from here regardless of
|
||||
// where the build context comes from. FROM scratch declares no base, so what is under
|
||||
// test — where the context comes from — is not entangled with ADR 0097's own checks.
|
||||
"Dockerfile": "FROM scratch\nCOPY go.mod ./\n",
|
||||
},
|
||||
secondary: map[string]map[string]string{
|
||||
// go.mod exists only in the second repository. A build context taken from the wrong
|
||||
// place would never find it, which a real docker build would refuse on — the fake
|
||||
// does not read files, so what is checked below is that the build was even pointed
|
||||
// at the right place, not that COPY would have succeeded.
|
||||
"https://forge.invalid/source.git": {"go.mod": "module route-proxy\n"},
|
||||
},
|
||||
}
|
||||
_, err := Build(context.Background(), r.run, r,
|
||||
"https://forge.invalid/catalogue.git", "", "", t.TempDir(), nil, Npmrc{}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var clonedSource bool
|
||||
for _, line := range r.ran {
|
||||
if strings.HasPrefix(line, "git clone") && strings.Contains(line, "https://forge.invalid/source.git") {
|
||||
clonedSource = true
|
||||
}
|
||||
}
|
||||
if !clonedSource {
|
||||
t.Fatalf("the artifact's own context was never cloned: %v", r.ran)
|
||||
}
|
||||
|
||||
buildIndex := -1
|
||||
for i, line := range r.ran {
|
||||
if strings.HasPrefix(line, "docker build ") {
|
||||
buildIndex = i
|
||||
}
|
||||
}
|
||||
if buildIndex == -1 {
|
||||
t.Fatal("no docker build was run")
|
||||
}
|
||||
build := r.ran[buildIndex]
|
||||
buildDir := r.dirs[buildIndex]
|
||||
|
||||
if !strings.Contains(buildDir, "context-server") {
|
||||
t.Errorf("docker build ran from %q, not the artifact's own cloned context", buildDir)
|
||||
}
|
||||
recipe := strings.SplitN(strings.SplitN(build, "-f ", 2)[1], " ", 2)[0]
|
||||
if !filepath.IsAbs(recipe) {
|
||||
t.Errorf("the recipe %q is not an absolute path, so it is read relative to whatever "+
|
||||
"directory the build context moved to rather than where it actually is", recipe)
|
||||
}
|
||||
if !strings.HasSuffix(recipe, string(filepath.Separator)+"Dockerfile") {
|
||||
t.Errorf("the recipe is not the module's own Dockerfile: %q", recipe)
|
||||
}
|
||||
if !strings.HasSuffix(build, " .") {
|
||||
t.Errorf("the build was not given a context: %s", build)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user