One name per thing, per the HQ glossary: the module/container/image/binary/repo becomes mesh-controller, the seat the-controller, and the store+broker pair the foundation (embedded base bundles, default template and example lock renamed with their go:embed directives). No behaviour change — a pure vocabulary rename. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
72 lines
3.2 KiB
Go
72 lines
3.2 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The module that provides the artifact store may not be delivered through it.
|
|
//
|
|
// Building publishes to the store and the builder will not start without one, so a module that
|
|
// provides the store and also builds something asks the mesh to put an artifact into the thing
|
|
// that artifact is needed to create. On a mesh new enough to have no registry, that is a build
|
|
// that never returns (novox/hq 04-ISSUES/029).
|
|
func TestTheArtifactStoreCannotBeDeliveredThroughItself(t *testing.T) {
|
|
_, err := ParseManifest([]byte(`{"module":"registry","version":"1",` +
|
|
`"provides":[{"name":"artifact-store","scope":"mesh"}],` +
|
|
`"build":{"artifacts":[{"name":"registry","kind":"upstream","from":"registry:2"}]},` +
|
|
`"resources":[{"id":"store","type":"container","name":"mesh-registry",` +
|
|
`"artifact":"registry","ports":["5000:5000"]}]}`))
|
|
if err == nil {
|
|
t.Fatal("a registry module that builds its own image was accepted; the build has " +
|
|
"nowhere to publish until the module it belongs to is already running")
|
|
}
|
|
if !strings.Contains(err.Error(), "artifact-store") {
|
|
t.Fatalf("refused without naming the provision the cycle turns on: %v", err)
|
|
}
|
|
}
|
|
|
|
// Naming the image directly is the way out, and must stay accepted.
|
|
func TestAnArtifactStoreThatNamesItsImageIsAccepted(t *testing.T) {
|
|
_, err := ParseManifest([]byte(`{"module":"registry","version":"1",` +
|
|
`"provides":[{"name":"artifact-store","scope":"mesh"}],` +
|
|
`"resources":[{"id":"store","type":"container","name":"mesh-registry",` +
|
|
`"image":"registry@sha256:` +
|
|
`266f282fabd7cd3df053ee7c658c77b42380d1a2f0d8e5a1c0d7a6d5b5c4a3b2",` +
|
|
`"ports":["5000:5000"]}]}`))
|
|
if err != nil {
|
|
t.Fatalf("the one way an artifact store can be delivered was refused: %v", err)
|
|
}
|
|
}
|
|
|
|
// And an ordinary module still builds whatever it likes.
|
|
func TestAModuleThatDoesNotProvideTheStoreStillBuilds(t *testing.T) {
|
|
_, err := ParseManifest([]byte(`{"module":"forge","version":"1",` +
|
|
`"build":{"artifacts":[{"name":"forge","kind":"upstream","from":"gitea/gitea:1.22"}]},` +
|
|
`"resources":[{"id":"run","type":"container","name":"forge","artifact":"forge"}]}`))
|
|
if err != nil {
|
|
t.Fatalf("an ordinary module was caught by a rule about the artifact store: %v", err)
|
|
}
|
|
}
|
|
|
|
// A mapping that names an address still names the port, and the machine side is still the middle.
|
|
//
|
|
// The foundation bundle writes "127.0.0.1:5432:5432" today, so the shape is not hypothetical.
|
|
// Found in review: the first cut split on the first colon, read "127.0.0.1" as the machine port,
|
|
// failed to parse it, and silently skipped the mapping — which put the filter back on the
|
|
// declared port, the exact fault MachineSide was written to end.
|
|
func TestAnAddressedMappingStillNamesThePort(t *testing.T) {
|
|
m := Manifest{Module: "store", Resources: []map[string]any{
|
|
{"type": "container", "id": "server", "ports": []any{"127.0.0.1:8080:80"}},
|
|
}}
|
|
for _, named := range []int{8080, 80} {
|
|
at, mayAssign := m.MachineSide(named)
|
|
if mayAssign {
|
|
t.Fatalf("%d was reassigned though the manifest published it explicitly", named)
|
|
}
|
|
if at != 8080 {
|
|
t.Fatalf("naming %d gave %d; the machine side of 127.0.0.1:8080:80 is 8080", named, at)
|
|
}
|
|
}
|
|
}
|