mesh/merge-gate head a641ed7d15c7 merged into trunk b12c4b82e820 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head a641ed7d15c7 merged into trunk b12c4b82e820 — pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group fix/121-genesis-package-registry delivered: every member is delivered
With npm-package-registry optional, the binding file the agent's environment names may be absent, and mesh-builder failed every build on reading it. It now reads the genesis document the installer seals at genesis's packages step when no binding is present, prefers the binding when both are, and each build's log says which it resolved from (§4). A build that needs the registry - a package, an image naming .npmrc, TypeScript with an @novox/ dependency - on an agent with neither is refused before anything is built or published, naming the machine, the unbound requirement and gitea (§6); it never falls back to the public registry. The captured catalogue moves to the mesh-catalog change it is judged with.
123 lines
5.5 KiB
Go
123 lines
5.5 KiB
Go
package builder
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/beside"
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// **A build that needs the registry fails loudly while the build agent has none** (novox/hq ADR 0336
|
|
// §5, §6, issue 121). On a holder with neither the npm-package-registry binding nor the genesis
|
|
// document, a `package` artifact, an image whose Dockerfile names .npmrc and a TypeScript artifact
|
|
// with an `@novox/` dependency each fail before anything is built or published, naming the machine,
|
|
// the unbound requirement and the module that provides it. A Go artifact, and an image that does not
|
|
// name .npmrc, build there as before.
|
|
func TestABuildNeedingTheRegistryIsRefusedOnAnAgentWithoutOne(t *testing.T) {
|
|
none := Npmrc{Machine: "workstation"}
|
|
cases := map[string]struct {
|
|
manifest string
|
|
files map[string]string
|
|
held map[string]string
|
|
}{
|
|
"a package": {aPackage, map[string]string{"package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`}, nil},
|
|
"an image naming .npmrc": {withBoth, map[string]string{
|
|
"Dockerfile": "FROM scratch\nCOPY .npmrc /root/.npmrc\n", "files/x": "y"}, nil},
|
|
"a TypeScript bundle depending on the SDK": {aBundle, map[string]string{
|
|
"index.ts": "console.log(1)", "package.json": `{"dependencies":{"@novox/mesh-sdk":"^0.1.0"}}`},
|
|
map[string]string{"mesh-tools/build": "registry.invalid/mesh-tools/build@sha256:" + strings.Repeat("b", 64)}},
|
|
}
|
|
for name, c := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
r, workspace := aRepository(t, c.manifest, c.files)
|
|
var said []string
|
|
_, err := Build(context.Background(), compiling{r}.run, r,
|
|
"https://forge.invalid/m.git", "", "", workspace, c.held, none, GitCredential{},
|
|
func(step, message string) { said = append(said, step+": "+message) })
|
|
if err == nil {
|
|
t.Fatal("built with no package registry")
|
|
}
|
|
for _, want := range []string{"workstation", "npm-package-registry", "unbound", "gitea"} {
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Errorf("the refusal does not say %q: %v", want, err)
|
|
}
|
|
}
|
|
for _, line := range r.ran {
|
|
if strings.HasPrefix(line, "docker build") || strings.HasPrefix(line, "docker run") ||
|
|
strings.HasPrefix(line, "docker pull") {
|
|
t.Errorf("something was built after the refusal: %s", line)
|
|
}
|
|
}
|
|
if len(r.images) != 0 || len(r.archives) != 0 {
|
|
t.Errorf("something was published after the refusal: %v %v", r.images, r.archives)
|
|
}
|
|
if !strings.Contains(strings.Join(said, "\n"), "REFUSED") {
|
|
t.Errorf("the build's log does not say it was refused:\n%s", strings.Join(said, "\n"))
|
|
}
|
|
})
|
|
}
|
|
|
|
// A Go artifact builds on the same agent.
|
|
files := aSharedRepository("one", "1")
|
|
manifest := `{"module":"ctl","version":"1","build":{"artifacts":[
|
|
{"name":"controller","kind":"bundle","language":"go","system":"arch","from":"cmd/ctl","binary":"ctl"}]},
|
|
"resources":[{"id":"controller","type":"process","name":"ctl","artifact":"controller","run":["./ctl"]}]}`
|
|
r := &recorded{contents: map[string]string{ManifestName: manifest}}
|
|
for k, v := range files {
|
|
r.contents[k] = v
|
|
}
|
|
held := map[string]string{"mesh-tools-go/build": "registry.invalid/mesh-tools-go/build@sha256:" + strings.Repeat("b", 64)}
|
|
if _, err := Build(context.Background(), onTrunk{recorded: r}.run, r,
|
|
"https://forge.invalid/ctl.git", "", "", t.TempDir(), held, none, GitCredential{}, nil); err != nil {
|
|
t.Fatalf("a Go artifact did not build on an agent without the registry: %v", err)
|
|
}
|
|
// And an image that does not name .npmrc.
|
|
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch\nCOPY . .", "files/x": "y"})
|
|
if _, err := Build(context.Background(), r.run, r,
|
|
"https://forge.invalid/meshboard.git", "", "", workspace, nil, none, GitCredential{}, nil); err != nil {
|
|
t.Fatalf("an image not naming .npmrc did not build on an agent without the registry: %v", err)
|
|
}
|
|
}
|
|
|
|
// **The build's log says which registry it resolved from** (novox/hq ADR 0336 §4).
|
|
func TestABuildSaysWhichRegistryItResolvedFrom(t *testing.T) {
|
|
for _, from := range []string{FromBinding, FromGenesis} {
|
|
r, workspace := aRepository(t, aPackage, map[string]string{
|
|
"package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`,
|
|
})
|
|
n := Npmrc{Scope: "@novox", Registry: "http://127.0.0.1:3000/api/packages/novox/npm/",
|
|
Username: "mesh-builder", Password: "p", From: from, Machine: "anchor"}
|
|
var said []string
|
|
if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/mesh-sdk.git", "", "",
|
|
workspace, nil, n, GitCredential{}, func(step, message string) { said = append(said, step+": "+message) }); err != nil {
|
|
t.Fatalf("%s: %v", from, err)
|
|
}
|
|
if !strings.Contains(strings.Join(said, "\n"), "from "+from) {
|
|
t.Errorf("the log does not say the registry came from %s:\n%s", from, strings.Join(said, "\n"))
|
|
}
|
|
}
|
|
}
|
|
|
|
// The refusal names the module that provides the registry; it is the catalogue's provider.
|
|
func TestTheRegistryABuildIsRefusedForNamesItsProvider(t *testing.T) {
|
|
raw, err := os.ReadFile(filepath.Join(beside.Catalogue(t), RegistryProvider, "module.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
forge, err := catalogue.ParseManifest(raw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
provides := false
|
|
for _, p := range forge.Provides {
|
|
provides = provides || p.Name == RegistryRequirement
|
|
}
|
|
if !provides {
|
|
t.Fatalf("%s does not provide %s, which a refused build names it for", RegistryProvider, RegistryRequirement)
|
|
}
|
|
}
|