ADR 0593 §7: the composer with its refusals leaves mesh-sdk, so mesh-sdk carries no composition rule. The catalogue functions only the node-engine's composition reaches (Resolve, Resolution's Compose, Declaration, DataItems, LeftOut, Rules and the rest, the settings, placeholder and template refusals, the rendering helpers) are copied with their tests into internal/compose/resolution; a method of a mesh-sdk shape becomes a function taking it first. The composition calls its own copies, and the captured catalogue its tests read lives here now, shared by mesh-compose's tests. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
82 lines
4.1 KiB
Go
82 lines
4.1 KiB
Go
package resolution
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
// novox/hq ADR 0310 §5: the holder is told, by the composition, where its node keeps a secret — the directory
|
|
// of every module's own secret, every credential file and grant directory, the vault's directories and the
|
|
// place the operator-sealed secrets are kept — and no other module is told.
|
|
func TestTheHolderOfNodeReadsIsToldWhereItsNodeKeepsASecret(t *testing.T) {
|
|
with := mcat.Rendering{ArtifactStore: "anchor.internal:5101",
|
|
Needed: map[string]map[string]string{mcat.RuntimeModule: {"broker": "sealed-credential"}, "reads": {"broker": "own"}, "desk": {"broker": "own"}}}
|
|
goRuntime := mcat.Manifest{Module: mcat.RuntimeModule, Version: "1",
|
|
OwnSecrets: mcat.OwnSecrets{"broker": {Path: "/var/lib/mesh/" + mcat.RuntimeModule + "/broker"}},
|
|
Build: &mcat.Build{Artifacts: []mcat.Artifact{{Name: "runtime", Kind: mcat.ArtifactBundle, Language: "go",
|
|
System: "arch", From: "cmd/node-tools"}}}}
|
|
goRuntime, err := goRuntime.Resolve([]mcat.Built{{Name: "runtime", Kind: mcat.ArtifactBundle,
|
|
Reference: mcat.ArtifactStoreScheme + mcat.RuntimeModule + "/runtime/blobs/" + bundleDigest, Digest: bundleDigest}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reads := aToolsModule(t, "reads", "tools/index.js")
|
|
reads.RunsAs, reads.SecretsOwner = "mesh-reads", "mesh-reads"
|
|
reads.Claims = []mcat.Claim{{Name: mcat.ReadsSeat}}
|
|
reads.OwnSecrets = mcat.OwnSecrets{"broker": {Path: "/var/lib/mesh-reads/broker"}}
|
|
desk := aToolsModule(t, "desk", "tools/index.js")
|
|
desk.RunsAs, desk.SecretsOwner = "desk", "desk"
|
|
desk.OwnSecrets = mcat.OwnSecrets{"broker": {Path: "/var/lib/desk/broker"}}
|
|
// The vault as the catalogue declares it: its directories have no path of their own, and the node places them.
|
|
vault, err := placedManifest(catalogueResolved(t, "mesh-vault", mcat.Built{Name: "code", Kind: mcat.ArtifactBundle,
|
|
Reference: mcat.ArtifactStoreScheme + "mesh-vault/code/blobs/" + bundleDigest, Digest: bundleDigest}), with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
app := mcat.Manifest{Module: "app", Version: "1", Secrets: map[string]string{"postgres-database": "/etc/app/db.secret"},
|
|
OwnSecrets: mcat.OwnSecrets{"token": {Path: "/home/ops/.config/app/token"}}}
|
|
r := mcat.Resolution{Node: "anchor", Account: "ops", Modules: []mcat.Manifest{reads, desk, vault, app, goRuntime}}
|
|
owns, err := resolutionOwnRuntimes(r, with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var given map[string]map[string]string
|
|
for _, p := range owns {
|
|
if p["id"] == "reads."+mcat.OwnRuntimeID() {
|
|
if err := json.Unmarshal([]byte(p["env"].(map[string]string)[mcat.RuntimeToolEnv]), &given); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if p["id"] == "desk."+mcat.OwnRuntimeID() && strings.Contains(p["env"].(map[string]string)[mcat.RuntimeToolEnv], mcat.ReadsSecretPlaces) {
|
|
t.Error("a module not holding node-reads is told the node's secret places")
|
|
}
|
|
}
|
|
var places []string
|
|
if err := json.Unmarshal([]byte(given["reads"][mcat.ReadsSecretPlaces]), &places); err != nil {
|
|
t.Fatalf("the holder is not told its node's secret places: %v (%v)", err, given)
|
|
}
|
|
want := []string{"/etc/app/db.secret", "/home/ops/.config/app", "/var/lib/desk", "/var/lib/mesh-reads",
|
|
"/var/lib/mesh-vault", "/var/lib/mesh/" + mcat.RuntimeModule}
|
|
if strings.Join(places, " ") != strings.Join(want, " ") {
|
|
t.Errorf("secret places:\n got %v\nwant %v", places, want)
|
|
}
|
|
}
|
|
|
|
// The review of #257: a secret place the controller cannot resolve fails the composition, naming the module,
|
|
// rather than leaving the list one place shorter.
|
|
func TestASecretPlaceThatCannotBeResolvedFailsTheComposition(t *testing.T) {
|
|
r := mcat.Resolution{Node: "anchor", Modules: []mcat.Manifest{
|
|
{Module: "app", Version: "1", Keeps: "${dir:keep}/sealed"},
|
|
}}
|
|
if _, err := resolutionSecretPlaces(r, mcat.Rendering{}); err == nil || !strings.Contains(err.Error(), "app") {
|
|
t.Errorf("an unresolved place was dropped: %v", err)
|
|
}
|
|
r.Modules[0].Keeps = "relative/place"
|
|
if _, err := resolutionSecretPlaces(r, mcat.Rendering{}); err == nil {
|
|
t.Error("a relative place was dropped")
|
|
}
|
|
}
|