Three readers did not follow a moved foundation port (novox/hq 04-ISSUES/102),
and each took the control-node down in its own way: the control plane's own
store and broker connections, sealed at genesis with the port inside; and every
build the mesh ever recorded, kept as `<registry>:<port>/<module>/<artifact>@…`.
The control plane cannot open its own sealed connections to move a port, and it
cannot bind the store as a consumer would — a binding mints a credential. So its
settings get a third twin, `NAME_PORT`, read on top of the sealed value by the
store, the broker, the management API and the bus connection, and filled into
its container by a placeholder that names a seat, `${seat:mesh-store:5432}`,
from the node's given or mesh-assigned ports — never the manifest's number, and
empty when the mesh has nothing to add, so what genesis wrote stands. A value
that is still a placeholder is nothing said, aloud: the manifest naming it lands
in the next commit, once every control plane that composes it knows it.
A build is now recorded by digest and path — `artifact-store://<module>/<artifact>@…`
— and the store's address is composed in where a reference is used: the
declaration, the trust file, the bases a build is handed, a replay to the
catalogue. Over the network as `<node>.internal:<port>`; on the store's own node
before any network exists — every genesis push before its "network" step — by
loopback. A reference recorded before this, with an address, is re-routed the
same way when the mesh built it. The trust file and every provider's address
come from one derivation: the node's given port, over the mesh's assignment,
over the manifest's number.
novox/hq 04-ISSUES/102
138 lines
6.5 KiB
Go
138 lines
6.5 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A build is recorded by what it is; where it is pushed is composed where it is used (novox/hq
|
|
// 04-ISSUES/102).
|
|
|
|
var digest = "sha256:" + strings.Repeat("d", 64)
|
|
|
|
func TestAReferenceIsRecordedWithoutTheStoresAddress(t *testing.T) {
|
|
cases := map[string]string{
|
|
"anchor.internal:5100/gitea/server@" + digest: ArtifactStoreScheme + "gitea/server@" + digest,
|
|
"localhost:5000/gitea/server@" + digest: ArtifactStoreScheme + "gitea/server@" + digest,
|
|
"http://anchor.internal:5100/v2/gitea/config/blobs/" + digest: ArtifactStoreScheme + "gitea/config/blobs/" + digest,
|
|
ArtifactStoreScheme + "gitea/server@" + digest: ArtifactStoreScheme + "gitea/server@" + digest,
|
|
digest: digest,
|
|
"@novox/sdk@1.2.3": "@novox/sdk@1.2.3",
|
|
"gitea/gitea@" + digest: "gitea/gitea@" + digest,
|
|
"https://registry.example/v2/gitea/config/blobs/" + digest: "https://registry.example/v2/gitea/config/blobs/" + digest,
|
|
}
|
|
for announced, want := range cases {
|
|
if got := Recorded(announced); got != want {
|
|
t.Errorf("Recorded(%q) = %q, want %q", announced, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestARecordedReferenceIsRoutedThroughTheStoreAsItIsNow(t *testing.T) {
|
|
if got := Routed(ArtifactStoreScheme+"gitea/server@"+digest, "anchor.internal:5101"); got != "anchor.internal:5101/gitea/server@"+digest {
|
|
t.Errorf("an image is fetched as %q", got)
|
|
}
|
|
if got := Routed(ArtifactStoreScheme+"gitea/config/blobs/"+digest, "anchor.internal:5101"); got != "http://anchor.internal:5101/v2/gitea/config/blobs/"+digest {
|
|
t.Errorf("an archive is fetched as %q", got)
|
|
}
|
|
if got := Routed("gitea/gitea@"+digest, "anchor.internal:5101"); got != "gitea/gitea@"+digest {
|
|
t.Errorf("a reference that is not the store's was routed: %q", got)
|
|
}
|
|
// One recorded before references were kept without their address follows the store too.
|
|
if got := Rerouted("anchor.internal:5100/gitea/server@"+digest, "anchor.internal:5101"); got != "anchor.internal:5101/gitea/server@"+digest {
|
|
t.Errorf("a reference recorded with the old address stays there: %q", got)
|
|
}
|
|
}
|
|
|
|
// **The address is composed into a declaration, and the record never carries it.**
|
|
func TestAnImageTheMeshBuiltIsRoutedThroughTheStoreWhenDeclared(t *testing.T) {
|
|
m := Manifest{Module: "gitea", Version: "1", Resources: []map[string]any{
|
|
{"id": "server", "type": "container", "name": "mesh-gitea", "artifact": "server"},
|
|
{"id": "config", "type": "archive", "path": "/etc/gitea", "artifact": "config"},
|
|
{"id": "cache", "type": "container", "name": "mesh-gitea-cache", "image": "valkey/valkey@" + digest},
|
|
}, Build: &Build{Artifacts: []Artifact{
|
|
{Name: "server", Kind: ArtifactImage, From: "Dockerfile"},
|
|
{Name: "config", Kind: ArtifactArchive, From: "config"},
|
|
}}}
|
|
resolved, err := m.Resolve([]Built{
|
|
{Name: "server", Kind: ArtifactImage, Reference: ArtifactStoreScheme + "gitea/server@" + digest},
|
|
{Name: "config", Kind: ArtifactArchive, Reference: ArtifactStoreScheme + "gitea/config/blobs/" + digest, Digest: digest},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r := Resolution{Node: "anchor", Modules: []Manifest{resolved}}
|
|
|
|
out, err := r.Declaration(Rendering{ArtifactStore: "anchor.internal:5101"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := fileNamed(out, "gitea.server")["image"]; got != "anchor.internal:5101/gitea/server@"+digest {
|
|
t.Errorf("the image the mesh built is fetched as %v", got)
|
|
}
|
|
if got := fileNamed(out, "gitea.config")["source"]; got != "http://anchor.internal:5101/v2/gitea/config/blobs/"+digest {
|
|
t.Errorf("the archive the mesh built is fetched from %v", got)
|
|
}
|
|
if got := fileNamed(out, "gitea.cache")["image"]; got != "valkey/valkey@"+digest {
|
|
t.Errorf("an image from a public registry was routed through the store: %v", got)
|
|
}
|
|
// The manifest the mesh holds still says what it is, not where it was fetched from.
|
|
if got := resolved.Resources[0]["image"]; got != ArtifactStoreScheme+"gitea/server@"+digest {
|
|
t.Errorf("composing wrote the address into the catalogue's copy: %v", got)
|
|
}
|
|
|
|
// And the store moves: the same record, another address, without a rebuild.
|
|
out, err = r.Declaration(Rendering{ArtifactStore: "laptop.internal:5000"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := fileNamed(out, "gitea.server")["image"]; got != "laptop.internal:5000/gitea/server@"+digest {
|
|
t.Errorf("after the store moved, the image is still fetched as %v", got)
|
|
}
|
|
}
|
|
|
|
func TestAnImageInTheStoreWithNoStoreToFetchItFromIsRefused(t *testing.T) {
|
|
m := Manifest{Module: "gitea", Version: "1", Resources: []map[string]any{
|
|
{"id": "server", "type": "container", "name": "mesh-gitea",
|
|
"image": ArtifactStoreScheme + "gitea/server@" + digest},
|
|
}}
|
|
_, err := Resolution{Node: "anchor", Modules: []Manifest{m}}.Declaration(Rendering{})
|
|
if err == nil || !strings.Contains(err.Error(), "no artifact store") {
|
|
t.Fatalf("a reference nothing can fetch was sent to a machine: %v", err)
|
|
}
|
|
}
|
|
|
|
// **A reference recorded with an address before this follows the store too** — when the mesh
|
|
// built it. A module running an image straight from a public registry under its own name is left
|
|
// exactly where it says: `quay.io/keycloak/keycloak` is not the mesh's, whatever it is called.
|
|
func TestAReferenceRecordedWithAnAddressFollowsTheStoreWhenTheMeshBuiltIt(t *testing.T) {
|
|
m := Manifest{Module: "keycloak", Version: "1", Resources: []map[string]any{
|
|
{"id": "server", "type": "container", "name": "mesh-keycloak",
|
|
"image": "anchor.internal:5100/keycloak/server@" + digest},
|
|
{"id": "upstream", "type": "container", "name": "mesh-keycloak-upstream",
|
|
"image": "quay.io/keycloak/keycloak@" + digest},
|
|
}}
|
|
r := Resolution{Node: "anchor", Modules: []Manifest{m}}
|
|
out, err := r.Declaration(Rendering{
|
|
ArtifactStore: "anchor.internal:5101",
|
|
Built: map[string]bool{"keycloak/server": true},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := fileNamed(out, "keycloak.server")["image"]; got != "anchor.internal:5101/keycloak/server@"+digest {
|
|
t.Errorf("an image the mesh built, recorded with the old address, is fetched as %v", got)
|
|
}
|
|
if got := fileNamed(out, "keycloak.upstream")["image"]; got != "quay.io/keycloak/keycloak@"+digest {
|
|
t.Errorf("a public image was re-routed through the store: %v", got)
|
|
}
|
|
// Nothing known to be built: nothing re-routed, nothing refused.
|
|
out, err = r.Declaration(Rendering{ArtifactStore: "anchor.internal:5101"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := fileNamed(out, "keycloak.server")["image"]; got != "anchor.internal:5100/keycloak/server@"+digest {
|
|
t.Errorf("with no build record, a reference was rewritten: %v", got)
|
|
}
|
|
}
|