A fingerprint written into a recipe names one particular copy of the base — the copy on whichever machine the person typing it was using. On any other mesh that copy has never existed, so the build stops on its first line with a message about an image nobody can look up. Three modules in the catalogue were in exactly that state, and the line each of them replaced was equally dead. A module now names the module and artifact instead, and the mesh answers with what it holds. The builder is still a thing that clones, builds and answers: the answer travels with the question, because only the mesh knows what it has. A base the mesh has not built is refused before anything is built, naming which module has to exist first.
71 lines
2.6 KiB
Go
71 lines
2.6 KiB
Go
package builder
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-control/internal/catalogue"
|
|
)
|
|
|
|
// A module naming a base the mesh has not built is refused, and the refusal names what is missing.
|
|
//
|
|
// **This is the whole point of naming a base rather than pinning one** (novox/hq issue 044). A
|
|
// recipe with a fingerprint typed into it fails inside a container runtime, on its first line, with
|
|
// a message about an image nobody can look up. This fails before anything is built, saying which
|
|
// module has to exist first.
|
|
func TestABaseTheMeshHasNotBuiltIsRefused(t *testing.T) {
|
|
manifest := catalogue.Manifest{
|
|
Module: "postgres",
|
|
Build: &catalogue.Build{
|
|
On: []catalogue.BuildsOn{{Arg: "RUNTIME_BASE", Module: "mesh-tools", Artifact: "runtime"}},
|
|
},
|
|
}
|
|
_, err := standingOn(manifest, map[string]string{})
|
|
if err == nil {
|
|
t.Fatal("a base nothing has built was accepted; the build would have failed on its first line")
|
|
}
|
|
for _, want := range []string{"mesh-tools", "postgres"} {
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Errorf("the refusal does not name %s: %v", want, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// And one the mesh holds becomes the argument the recipe reads it from.
|
|
func TestABaseTheMeshHoldsBecomesABuildArgument(t *testing.T) {
|
|
manifest := catalogue.Manifest{
|
|
Module: "postgres",
|
|
Build: &catalogue.Build{
|
|
On: []catalogue.BuildsOn{{Arg: "RUNTIME_BASE", Module: "mesh-tools", Artifact: "runtime"}},
|
|
},
|
|
}
|
|
held := map[string]string{"mesh-tools/runtime": "127.0.0.1:5000/mesh-tools/runtime@sha256:" + strings.Repeat("a", 64)}
|
|
args, err := standingOn(manifest, held)
|
|
if err != nil {
|
|
t.Fatalf("a base this mesh holds was refused: %v", err)
|
|
}
|
|
want := []string{"--build-arg", "RUNTIME_BASE=" + held["mesh-tools/runtime"]}
|
|
if len(args) != len(want) || args[0] != want[0] || args[1] != want[1] {
|
|
t.Fatalf("the build was invoked with %v, not %v", args, want)
|
|
}
|
|
}
|
|
|
|
// A module naming no base asks for nothing, which is most modules.
|
|
func TestAModuleNamingNoBaseAddsNoArguments(t *testing.T) {
|
|
args, err := standingOn(catalogue.Manifest{Module: "hello-web", Build: &catalogue.Build{}}, nil)
|
|
if err != nil || args != nil {
|
|
t.Fatalf("a module naming no base produced %v, %v", args, err)
|
|
}
|
|
}
|
|
|
|
// Half a base is refused rather than half-applied.
|
|
func TestAnIncompleteBaseIsRefused(t *testing.T) {
|
|
manifest := catalogue.Manifest{
|
|
Module: "postgres",
|
|
Build: &catalogue.Build{On: []catalogue.BuildsOn{{Module: "mesh-tools", Artifact: "runtime"}}},
|
|
}
|
|
if _, err := standingOn(manifest, map[string]string{"mesh-tools/runtime": "x"}); err == nil {
|
|
t.Fatal("a base with no build argument was accepted; nothing would have read it")
|
|
}
|
|
}
|