H-2: every service and container is started with MESH_FINGERPRINT, the fingerprint of the want it applies
Builds: design 47 G §201, §205, §239 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -746,7 +746,10 @@ func TestNatsTheBusServerIsTakenOverAndReplacedOnlyWhenItsBuildChangesIt(t *test
|
||||
}
|
||||
folded, err := declaration.Parse([]byte(`{"declaration":1,"resources":[` +
|
||||
`{"id":"nats.server-conf","type":"file","path":"` + conf + `","mode":"0644","content":"listen: old\n"},` +
|
||||
`{"id":"nats.server","type":"container","name":"` + server + `","image":"` + image + `"}]}`))
|
||||
`{"id":"nats.server","type":"container","name":"` + server + `","image":"` + image + `",` +
|
||||
// As the node-engine starts every container since the fingerprint handover (47/G W13.1): the fingerprint of the
|
||||
// want it applies, here the first want's, so that want changes the server's spec in nothing.
|
||||
`"env":{"MESH_FINGERPRINT":"` + fpIt("e") + `"}}]}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -165,4 +165,50 @@ func TestComparingCompositions(t *testing.T) {
|
||||
if c := CompareComposed(map[string]Composed{"k": comp(file("conf", "a"))}, nil); len(c.Added) != 1 || c.Added[0].Module != "nats" || c.Added[0].Composed == "" {
|
||||
t.Errorf("a composition not run is added: %+v", c)
|
||||
}
|
||||
|
||||
// Design 47, *The fingerprint handover* (the composed resources are split here): a daemon or a container of a module
|
||||
// is started with MESH_FINGERPRINT, the fingerprint of the want it applies, over any value its module declared of that
|
||||
// name; a file, or a resource of no module, carries none.
|
||||
split, err := Split("novox", []json.RawMessage{
|
||||
json.RawMessage(`{"id":"d","type":"process","module":"nats","run":["x"],"env":{"A":"1","MESH_FINGERPRINT":"stale"}}`),
|
||||
json.RawMessage(`{"id":"c","type":"container","module":"nats","image":"i"}`),
|
||||
file("conf", "a"),
|
||||
json.RawMessage(`{"id":"own","type":"process","run":["x"]}`),
|
||||
}, map[string]string{"nats": fp}, nil, NodeOwn{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(split) != 2 {
|
||||
t.Fatalf("split into %d compositions, want the module's and the node's own", len(split))
|
||||
}
|
||||
if n := len(split[mustKey(t, "novox", "nats", fp)].Resources); n != 3 {
|
||||
t.Fatalf("the module's composition holds %d resources, want 3", n)
|
||||
}
|
||||
for _, raw := range split[mustKey(t, "novox", "nats", fp)].Resources {
|
||||
var r struct {
|
||||
ID string
|
||||
Env map[string]string
|
||||
}
|
||||
_ = json.Unmarshal(raw, &r)
|
||||
want := fp
|
||||
if r.ID == "conf" {
|
||||
want = ""
|
||||
}
|
||||
if r.Env[FingerprintVar] != want || (r.ID == "d" && r.Env["A"] != "1") {
|
||||
t.Errorf("%s starts with %v, want %s=%q and its own words kept", r.ID, r.Env, FingerprintVar, want)
|
||||
}
|
||||
}
|
||||
for _, raw := range split["novox"].Resources {
|
||||
if strings.Contains(string(raw), FingerprintVar) {
|
||||
t.Errorf("the node's own carries %s: %s", FingerprintVar, raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mustKey(t *testing.T, node, module, fp string) string {
|
||||
k, err := Key(node, module, fp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
@@ -234,6 +234,11 @@ func Split(node string, resources []json.RawMessage, fps map[string]string, fill
|
||||
if head.Type == string(declaration.TypeFile) && head.Module != "" {
|
||||
pathOf[head.Path] = key
|
||||
}
|
||||
if head.Module != "" {
|
||||
if raw, err = withFingerprint(raw, head.Type, fps[head.Module]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c := out[key]
|
||||
c.Resources = append(c.Resources, raw)
|
||||
out[key] = c
|
||||
@@ -249,3 +254,34 @@ func Split(node string, resources []json.RawMessage, fps map[string]string, fill
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// FingerprintVar is the environment value every process the node-engine starts, a daemon or a container, holds: the
|
||||
// fingerprint of the want it applies. mesh-sdk's runtime reads it once at the process's start as the process's own build
|
||||
// (novox/hq ADR 0656 §1, design 47 *The fingerprint handover*).
|
||||
const FingerprintVar = "MESH_FINGERPRINT"
|
||||
|
||||
// withFingerprint is raw, when it is a daemon or a container, with FingerprintVar in its env set to fp. The value is
|
||||
// the node-engine's to say, so one a module declared of that name is replaced. Another resource, or no fingerprint, is
|
||||
// returned as it is.
|
||||
func withFingerprint(raw json.RawMessage, kind, fp string) (json.RawMessage, error) {
|
||||
if fp == "" || (kind != string(declaration.TypeProcess) && kind != string(declaration.TypeContainer)) {
|
||||
return raw, nil
|
||||
}
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &fields); err != nil {
|
||||
return nil, fmt.Errorf("compositions: a %s resource is not JSON: %w", kind, err)
|
||||
}
|
||||
env := map[string]string{}
|
||||
if had, ok := fields["env"]; ok {
|
||||
if err := json.Unmarshal(had, &env); err != nil {
|
||||
return nil, fmt.Errorf("compositions: the env of a %s resource is not words by name: %w", kind, err)
|
||||
}
|
||||
}
|
||||
env[FingerprintVar] = fp
|
||||
body, err := json.Marshal(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields["env"] = body
|
||||
return json.Marshal(fields)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user