Judge a revision that changes only the bus's user list as no move, and say so beside it (issue 1945; the review's A2)
The user list is a file in a directory the bus's container mounts, so it read as moving data and held every new credential for a person's yes, against ADR 0539 clause 7; mesh-sdk's UserListOnly lets the assigner write such a revision at once.
This commit is contained in:
@@ -67,10 +67,45 @@ func judgeMove(ctx context.Context, at composedPlace, key string, to sdkdelivery
|
||||
out.Words = fmt.Sprintf("the composition %s runs on %s (%s) is gone", to.Module, at.node, short(strings.TrimPrefix(have.Composed, "sha256:")))
|
||||
return out, nil
|
||||
}
|
||||
if onlyTheUserList(from, to) {
|
||||
// Only the bus's user list (ADR 0539 clause 7): a file change and a reload of the bus, no move of it, written
|
||||
// into its want at once.
|
||||
out.Judgement, out.UserListOnly, out.Words = sdkdelivery.JudgedNeither, true, "only the bus's user list changes"
|
||||
return out, nil
|
||||
}
|
||||
out.Judgement, out.Words = judged(from, to)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// busUsersSuffix ends the id of the bus module's user list as the composer writes it: `<module>.bus-users`.
|
||||
const busUsersSuffix = ".bus-users"
|
||||
|
||||
// onlyTheUserList says two compositions of a module differ, and only in its file of the bus's user list.
|
||||
func onlyTheUserList(from, to sdkdelivery.Composed) bool {
|
||||
if from.Data != to.Data {
|
||||
return false
|
||||
}
|
||||
before, after := resourcesByID(from), resourcesByID(to)
|
||||
if len(before) != len(after) {
|
||||
return false
|
||||
}
|
||||
changed := false
|
||||
for id, now := range after {
|
||||
was, ok := before[id]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if bytes.Equal(was.raw, now.raw) {
|
||||
continue
|
||||
}
|
||||
if !strings.HasSuffix(id, busUsersSuffix) || now.kind != string(sdkdecl.TypeFile) {
|
||||
return false
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
// composedByDigest finds, in the node's bucket, the composition of module at fingerprint fp whose digest is digest: the
|
||||
// key's current value or one of the revisions it keeps.
|
||||
func composedByDigest(ctx context.Context, at composedPlace, module, fp, digest string) (sdkdelivery.Composed, bool, error) {
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/compose"
|
||||
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// natsComposedWith is the bus module's composition on novox with the given user list: the catalogue's nats manifest as
|
||||
// captured (testdata/beside/mesh-catalog/modules/nats/module.json.captured), its archives, which only a registration
|
||||
// turns into references, left out, and its container's built image given as a reference.
|
||||
func natsComposedWith(t *testing.T, users string) sdkdelivery.Composed {
|
||||
t.Helper()
|
||||
raw, err := os.ReadFile("../../testdata/beside/mesh-catalog/modules/nats/module.json.captured")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var m catalogue.Manifest
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var kept []map[string]any
|
||||
for _, r := range m.Resources {
|
||||
if _, templated := r["template"]; templated {
|
||||
continue // a template is rendered from the build's templates archive, which only a registration fetches
|
||||
}
|
||||
switch r["type"] {
|
||||
case "archive":
|
||||
continue
|
||||
case "container":
|
||||
// Its image is named by a build's artifact, which only a registration records: given here as a reference.
|
||||
delete(r, "artifact")
|
||||
r["image"] = "nats@sha256:" + strings.Repeat("1", 64)
|
||||
}
|
||||
kept = append(kept, r)
|
||||
}
|
||||
m.Resources = kept
|
||||
key := base64.StdEncoding.EncodeToString(make([]byte, 32))
|
||||
in := compose.Input{Catalogue: map[string]catalogue.Manifest{m.Module: m}, BusUsers: users,
|
||||
OwnSecrets: []compose.OwnSecretRow{{Node: "novox", Module: m.Module, Name: "broker", Origin: "made", SealedTo: key,
|
||||
Sealed: "SEALED-BY-THE-TEST"}},
|
||||
Nodes: []compose.Node{{Name: "novox", Assigned: []string{m.Module}, Stated: true, SealingKey: key,
|
||||
Capabilities: map[string]bool{"container-runtime": true, "systemd": true}}}}
|
||||
fp := sdkdelivery.SourcePrefix2 + strings.Repeat("e", 64)
|
||||
all, _, err := compose.Compositions(in, "novox", map[string]string{m.Module: fp}, compose.Foreseeing)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
k, _ := sdkdelivery.ComposedKey("novox", m.Module, fp)
|
||||
c, ok := all[k]
|
||||
if !ok {
|
||||
t.Fatalf("no composition of %s: %v", m.Module, all)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// 1945/AC12 and the review's A2 (ADR 0539 clause 7): a revision of the bus module's composition that changes only its
|
||||
// user list, a file in a directory its container mounts, is judged neither and said to change only the user list, so
|
||||
// a new credential never waits for a person's yes; a revision that changes anything else beside it is judged as any.
|
||||
func TestARevisionOfOnlyTheUserListMovesNothing(t *testing.T) {
|
||||
at, _, _, haves := aPlace(t, "novox")
|
||||
ctx := context.Background()
|
||||
before := natsComposedWith(t, "authorization { users = [ {user: a} ] }\n")
|
||||
after := natsComposedWith(t, "authorization { users = [ {user: a}, {user: b} ] }\n")
|
||||
if d1, _ := sdkdelivery.ComposedDigestOf(before); d1 == func() string { d, _ := sdkdelivery.ComposedDigestOf(after); return d }() {
|
||||
t.Fatal("the two user lists composed the same")
|
||||
}
|
||||
key, _ := sdkdelivery.ComposedKey("novox", before.Module, before.FP)
|
||||
if _, err := writeComposed(ctx, at, key, before); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runs, _ := sdkdelivery.ComposedDigestOf(before)
|
||||
haves[before.Module] = sdkdelivery.Have{FP: before.FP, Composed: runs, Phase: sdkdelivery.PhaseRunning, Health: sdkdelivery.HealthOK}
|
||||
digest, _ := sdkdelivery.ComposedDigestOf(after)
|
||||
v, err := judgeMove(ctx, at, key, after, digest)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v.Judgement != sdkdelivery.JudgedNeither || !v.UserListOnly || v.Against != runs {
|
||||
t.Errorf("a user-list-only revision is judged %+v", v)
|
||||
}
|
||||
// The user list and a container's image beside it: judged as any move, and not user-list-only.
|
||||
other := after
|
||||
other.Resources = append([]json.RawMessage{}, after.Resources...)
|
||||
for i, r := range other.Resources {
|
||||
var head map[string]any
|
||||
_ = json.Unmarshal(r, &head)
|
||||
if head["type"] == "container" {
|
||||
head["image"] = "nats@sha256:" + strings.Repeat("9", 64)
|
||||
other.Resources[i], _ = json.Marshal(head)
|
||||
}
|
||||
}
|
||||
d2, _ := sdkdelivery.ComposedDigestOf(other)
|
||||
v, err = judgeMove(ctx, at, key, other, d2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v.UserListOnly || v.Judgement != sdkdelivery.JudgedRecreates {
|
||||
t.Errorf("a new image beside the user list is judged %+v", v)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ module github.com/novox/mesh-controller
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024150354-5da87a0dda89
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024161532-a523a8cbb0c8
|
||||
github.com/jackc/pgx/v5 v5.10.0
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
|
||||
@@ -12,6 +12,8 @@ git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024144241-e64486b0e6ab h1:D5VBezf/
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024144241-e64486b0e6ab/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024150354-5da87a0dda89 h1:9FENYqhynDUf7ByotHEdDjmvyFEPCYmteHjN8gRE3ao=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024150354-5da87a0dda89/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024161532-a523a8cbb0c8 h1:aFfcEXmd24GpY5na6Xaic0A9xmAAa/jbrrX5wErPDdU=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024161532-a523a8cbb0c8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
||||
Vendored
+1
-1
@@ -9,7 +9,7 @@ a second place to clean besides the catalogue itself.
|
||||
|
||||
mesh-catalog 2246ccc492e59579a140752e6f574b2121d296af modules/*/module.json, modules/*/files, modules/*/templates, predefined.json
|
||||
mesh-host 9d9e3a46c4f5943cfffb413d962ce48b75e2fd39 examples/foundation-first-node.lock
|
||||
mesh-sdk 5da87a0dda89f2b48b23fc0cb7765f0312f7eb92 conformance/events/module-event.json
|
||||
mesh-sdk a523a8cbb0c8b1a83fcccd1790224e1d94380785 conformance/events/module-event.json
|
||||
|
||||
To move them, from this repository's root, with the two repositories checked out beside it:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user