mesh/merge-gate head 5f147fe6b9c7 merged into trunk 0d11b4fc930d — pass: the change touches no module of the mesh's graph
mesh/repo-check head 5f147fe6b9c7 merged into trunk 0d11b4fc930d — pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group fix/121-genesis-package-registry delivering: 2 of 4 delivered
The build agent declares neither the retired builder's npm-password own-secret nor a serves setting, so the packages step handed its credential to nothing and genesis's sdk step had no way to publish. The packages step now seals the genesis-package-registry own-secret to build-agent: the forge on this node's loopback, the port given for the package registry, its npm path, and the mesh-builder account with its password. setFoundationSettings no longer gives the builder a serves.port.
220 lines
8.8 KiB
Go
220 lines
8.8 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Defends novox/hq 04-ISSUES/085: the port given for the package registry at genesis is a setting
|
|
// of a module and not text in a manifest, so registering that manifest again does not put the
|
|
// catalogue's number back.
|
|
|
|
// theBuildersBinding is the resource the builder carries in place of a binding nothing can resolve
|
|
// yet, exactly as the catalogue's manifest has it — settable, with the port as its only default.
|
|
const theBuildersBinding = `{
|
|
"module": "builder",
|
|
"resources": [
|
|
{
|
|
"id": "package-binding",
|
|
"type": "file",
|
|
"path": "/var/lib/mesh/builder/package-registry.json",
|
|
"mode": "0600",
|
|
"merge": "json",
|
|
"protected": ["provision", "from", "at", "as"],
|
|
"content": "{\"provision\": \"package-registry\", \"from\": \"gitea\", \"at\": \"127.0.0.1\", \"as\": \"mesh-builder\", \"serves\": {\"scheme\": \"http\", \"port\": 3000, \"npm-path\": \"/api/packages/novox/npm/\"}}\n"
|
|
}
|
|
]
|
|
}`
|
|
|
|
func recording(t *testing.T) (*controlRecorder, controlPlane) {
|
|
t.Helper()
|
|
t.Setenv("TMPDIR", t.TempDir())
|
|
c := &controlRecorder{settings: map[string]string{}}
|
|
return c, controlPlane{container: "temp-mesh-controller", run: c.run, timeout: time.Second}
|
|
}
|
|
|
|
func TestTheBuilderOnTheDefaultPackagesPortIsToldNothing(t *testing.T) {
|
|
c, control := recording(t)
|
|
o := Options{Node: "anchor", Wait: time.Second}
|
|
if _, err := installModule(context.Background(), o, control, BuilderModule,
|
|
[]byte(theBuildersBinding), quietly); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if c.index("settings") >= 0 {
|
|
t.Errorf("a genesis on the catalogue's packages port set a setting: %v", c.told)
|
|
}
|
|
}
|
|
|
|
// aCatalogueWith writes a checkout holding one module's manifest, which is all the installer reads
|
|
// a catalogue for.
|
|
func aCatalogueWith(t *testing.T, module, manifest string) string {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
at := filepath.Join(dir, catalogueDir, module)
|
|
if err := os.MkdirAll(at, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(at, "module.json"), []byte(manifest), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return dir
|
|
}
|
|
|
|
const theForgesManifest = `{"module": "gitea", "version": "1"}`
|
|
|
|
func TestThePackagesPortIsTheForgeModulesSettingOnThisNode(t *testing.T) {
|
|
c, control := recording(t)
|
|
o := Options{Node: "anchor", Catalogue: aCatalogueWith(t, ForgeModule, theForgesManifest),
|
|
Ports: FoundationPorts{Packages: 3100}, Wait: time.Second}
|
|
if err := recordTheForgesPort(context.Background(), o, control, quietly); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := c.settings["gitea-settings.json"]; got != `{"ports":{"3000":3100}}` {
|
|
t.Errorf("the forge module was told %q about its port", got)
|
|
}
|
|
// Registered so there is something to hold the setting against, and never assigned: the forge
|
|
// module cannot run until the base it stands on has been built.
|
|
add, set := c.index("module add"), c.index("settings set gitea")
|
|
if add < 0 || set < 0 || add > set {
|
|
t.Fatalf("the forge's setting was recorded against a module the mesh does not know: %v", c.told)
|
|
}
|
|
if c.index("assign") >= 0 {
|
|
t.Errorf("genesis assigned the forge module: %v", c.told)
|
|
}
|
|
if !strings.Contains(c.told[set], "--node anchor") {
|
|
t.Errorf("the forge's port was set for the whole mesh, not this machine: %s", c.told[set])
|
|
}
|
|
}
|
|
|
|
// knowingControl is a control plane that already has the forge in its catalogue, as a mesh that
|
|
// has been running has.
|
|
type knowingControl struct{ controlRecorder }
|
|
|
|
func (k *knowingControl) run(ctx context.Context, name string, args ...string) (string, error) {
|
|
out, err := k.controlRecorder.run(ctx, name, args...)
|
|
if name == "docker" && args[0] == "exec" && len(args) > 4 &&
|
|
args[3] == "module" && args[4] == "list" {
|
|
return ForgeModule + " 1 assigned to anchor\n", nil
|
|
}
|
|
return out, err
|
|
}
|
|
|
|
func TestARerunDoesNotReplaceAForgeTheMeshAlreadyHas(t *testing.T) {
|
|
t.Setenv("TMPDIR", t.TempDir())
|
|
k := &knowingControl{controlRecorder{settings: map[string]string{}}}
|
|
control := controlPlane{container: "mesh-controller", run: k.run, timeout: time.Second}
|
|
// An older checkout than the mesh is running, which is what makes an overwrite here damage.
|
|
o := Options{Node: "anchor", Catalogue: aCatalogueWith(t, ForgeModule, `{"module": "gitea"}`),
|
|
Ports: FoundationPorts{Packages: 3100}, Wait: time.Second}
|
|
if err := recordTheForgesPort(context.Background(), o, control, quietly); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Registering is an overwrite, and the manifest of a forge that is built and assigned is not
|
|
// this installer's to replace with whatever checkout it was pointed at.
|
|
if k.index("module add") >= 0 {
|
|
t.Errorf("a re-run replaced the registered forge's manifest: %v", k.told)
|
|
}
|
|
// The port is still recorded: a settings row needs the module row, and it is already there.
|
|
if got := k.settings["gitea-settings.json"]; got != `{"ports":{"3000":3100}}` {
|
|
t.Errorf("the forge's port was not recorded on a mesh that knows it: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestAGenesisOnTheCataloguesPackagesPortRegistersNoForge(t *testing.T) {
|
|
c, control := recording(t)
|
|
o := Options{Node: "anchor", Catalogue: aCatalogueWith(t, ForgeModule, theForgesManifest),
|
|
Wait: time.Second}
|
|
if err := recordTheForgesPort(context.Background(), o, control, quietly); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(c.told) != 0 {
|
|
t.Errorf("a genesis on the defaults told the mesh something new: %v", c.told)
|
|
}
|
|
}
|
|
|
|
func TestAForgeManifestTheCatalogueDoesNotHaveIsNamed(t *testing.T) {
|
|
_, control := recording(t)
|
|
o := Options{Node: "anchor", Catalogue: t.TempDir(),
|
|
Ports: FoundationPorts{Packages: 3100}, Wait: time.Second}
|
|
err := recordTheForgesPort(context.Background(), o, control, quietly)
|
|
if err == nil || !strings.Contains(err.Error(), ForgeModule) ||
|
|
!strings.Contains(err.Error(), "package registry") {
|
|
t.Errorf("a missing forge manifest was not explained: %v", err)
|
|
}
|
|
}
|
|
|
|
// theCataloguesModule is the catalogue's own manifest of a module, never a copy: the clone beside a merge
|
|
// check (MESH_CHECK_BESIDE), where it must be; else a checkout beside this one; else the test skips aloud.
|
|
func theCataloguesModule(t *testing.T, module string) []byte {
|
|
t.Helper()
|
|
inside := filepath.Join("mesh-catalog", "modules", module, "module.json")
|
|
if root := os.Getenv("MESH_CHECK_BESIDE"); root != "" {
|
|
raw, err := os.ReadFile(filepath.Join(root, inside))
|
|
if err != nil {
|
|
t.Fatalf("the catalogue beside this check (MESH_CHECK_BESIDE=%s) has no %s: %v", root, inside, err)
|
|
}
|
|
return raw
|
|
}
|
|
raw, err := os.ReadFile(filepath.Join("..", "..", "..", inside))
|
|
if err != nil {
|
|
t.Skipf("NOT JUDGED: no catalogue beside this checkout and no MESH_CHECK_BESIDE; the merge check judges it")
|
|
}
|
|
return raw
|
|
}
|
|
|
|
// **Genesis installs the build agent the catalogue holds** (novox/hq issue 596, ADR 0190): the
|
|
// one-holder `builder` left the catalogue on 2026-10-03, and genesis stopped at step 13 asking for it.
|
|
// The catalogue's own manifest is read, and the image the installer carried is put where the manifest
|
|
// names the image it builds.
|
|
func TestGenesisInstallsTheCataloguesBuildAgent(t *testing.T) {
|
|
manifest := theCataloguesModule(t, BuilderModule)
|
|
const reference = "127.0.0.1:5000/mesh-builder@sha256:" + "4444444444444444444444444444444444444444444444444444444444444444"
|
|
pinned, places, err := pinTheCarriedBuilder(manifest, reference)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var m struct {
|
|
Module string `json:"module"`
|
|
Claims []struct {
|
|
Name string `json:"name"`
|
|
} `json:"claims"`
|
|
Resources []map[string]any `json:"resources"`
|
|
}
|
|
if err := json.Unmarshal(pinned, &m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if m.Module != BuilderModule || len(m.Claims) == 0 || m.Claims[0].Name != "node-build-agent" {
|
|
t.Fatalf("genesis installs %q claiming %v; the build agent holds node-build-agent (ADR 0190)", m.Module, m.Claims)
|
|
}
|
|
running := 0
|
|
for _, r := range m.Resources {
|
|
if r["type"] != "container" {
|
|
continue
|
|
}
|
|
if _, still := r["artifact"]; still {
|
|
t.Errorf("the container %v still names an artifact nothing has built yet", r["name"])
|
|
}
|
|
if r["image"] == reference {
|
|
running++
|
|
}
|
|
}
|
|
if places != 1 || running != 1 {
|
|
t.Errorf("the carried builder runs in %d container(s) (%d place(s) said); the build agent runs one", running, places)
|
|
}
|
|
}
|
|
|
|
// A manifest that runs nothing from an image it builds has nowhere to put the carried builder, and is
|
|
// refused rather than registered running nothing.
|
|
func TestABuildAgentManifestWithNowhereForTheCarriedBuilderIsRefused(t *testing.T) {
|
|
_, _, err := pinTheCarriedBuilder([]byte(`{"module":"build-agent","resources":[{"id":"server","type":"container","name":"x","artifact":"server"}],"build":{"artifacts":[{"name":"server","kind":"bundle"}]}}`),
|
|
"127.0.0.1:5000/mesh-builder@sha256:"+strings.Repeat("4", 64))
|
|
if err == nil || !strings.Contains(err.Error(), "nowhere to put") {
|
|
t.Fatalf("a manifest with no image artifact was pinned: %v", err)
|
|
}
|
|
}
|