Merge pull request 'L3: a provider's have counts only from a build that offers the provision; the gate takes a merged message with no trees (ADR 0603 §5, issue 1367)' (#603) from feat/l3b-bus into main
This commit was merged in pull request #603.
This commit is contained in:
@@ -5,6 +5,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The holder, end to end over fakes (novox/hq ADR 0239 *How it is checked*, cut by ADR 0539 §17): a pull request's head
|
// The holder, end to end over fakes (novox/hq ADR 0239 *How it is checked*, cut by ADR 0539 §17): a pull request's head
|
||||||
@@ -238,6 +240,17 @@ func TestAMergedGroupEndsMergedWithSourceMainMoved(t *testing.T) {
|
|||||||
if w.source.main == nil || w.source.main.Heads["novox/a"] != strings.Repeat("p", 12) || w.source.main.Heads["novox/b"] != strings.Repeat("q", 12) {
|
if w.source.main == nil || w.source.main.Heads["novox/a"] != strings.Repeat("p", 12) || w.source.main.Heads["novox/b"] != strings.Repeat("q", 12) {
|
||||||
t.Fatalf("source.main is %+v, wanted both merges", w.source.main)
|
t.Fatalf("source.main is %+v, wanted both merges", w.source.main)
|
||||||
}
|
}
|
||||||
|
// A merge that leaves no module.json carries no trees (issue 1367): the gate reads it, and refuses nothing.
|
||||||
|
msg := sdk.Forge{Kind: sdk.ForgeMerged, Repository: "novox/a", Number: 9, Head: strings.Repeat("e", 40),
|
||||||
|
Merge: strings.Repeat("f", 40), Root: "r"}
|
||||||
|
body, err := sdk.EncodeForge(msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("a merged message with no trees is refused: %v", err)
|
||||||
|
}
|
||||||
|
w.h.Forged("mesh-delivery-forge-bridge.novox.a.merged", body)
|
||||||
|
if len(w.h.refusals) != 0 {
|
||||||
|
t.Errorf("the gate refused a merged message with no trees: %v", w.h.refusals)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ADR 0419 §2, issue 978: a description's `after:` line is not read, and says so once on its pull request. The
|
// ADR 0419 §2, issue 978: a description's `after:` line is not read, and says so once on its pull request. The
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module mesh-delivery-gate
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128 h1:aI1DhZAuqVBXbybSqhLNOGX5rUOrn9T0cHNCDUQF7IQ=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 h1:RtAFbUJRP1qO9AIMM2ue+cXNzqj7WZ7IOlSqNvttwuU=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929/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 h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module mesh-provisioner-client
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
golang.org/x/sys v0.48.0
|
golang.org/x/sys v0.48.0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128 h1:aI1DhZAuqVBXbybSqhLNOGX5rUOrn9T0cHNCDUQF7IQ=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 h1:RtAFbUJRP1qO9AIMM2ue+cXNzqj7WZ7IOlSqNvttwuU=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929/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 h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"scope": "node"
|
"scope": "node"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"core-credentials": true,
|
||||||
"own-secrets": {
|
"own-secrets": {
|
||||||
"provisioner-broker": "${dir:state}/broker"
|
"provisioner-broker": "${dir:state}/broker"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -68,7 +68,10 @@ const (
|
|||||||
ModulesRead = delivery.IndexerModule + ".modules"
|
ModulesRead = delivery.IndexerModule + ".modules"
|
||||||
ManifestsRead = delivery.IndexerModule + ".manifests"
|
ManifestsRead = delivery.IndexerModule + ".manifests"
|
||||||
HaveRead = "node-engine.have"
|
HaveRead = "node-engine.have"
|
||||||
ComposedRead = "mesh-host.composed"
|
// BuildsRead is the builder's builds, `<module>.<fingerprint part>`: the key of the build a provider runs names the
|
||||||
|
// digest of the manifest it was built from (novox/hq ADR 0603 clause 5).
|
||||||
|
BuildsRead = delivery.BuilderModule + ".builds"
|
||||||
|
ComposedRead = "mesh-host.composed"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BoundReads are the providers' entries this module's manifest reads, `<module>.bound`, whose changes it hears. Which
|
// BoundReads are the providers' entries this module's manifest reads, `<module>.bound`, whose changes it hears. Which
|
||||||
@@ -819,6 +822,14 @@ func (h *Holder) haveOutcome(p *provisions.Provision) {
|
|||||||
case have.Phase == delivery.PhaseFailed || have.Health == delivery.HealthFailed:
|
case have.Phase == delivery.PhaseFailed || have.Health == delivery.HealthFailed:
|
||||||
p.State, p.Why = provisions.Failed, orSaid(have.Detail, where+" failed")
|
p.State, p.Why = provisions.Failed, orSaid(have.Detail, where+" failed")
|
||||||
case have.Phase == delivery.PhaseRunning && have.Health == delivery.HealthOK && (wantFP == "" || have.FP == wantFP):
|
case have.Phase == delivery.PhaseRunning && have.Health == delivery.HealthOK && (wantFP == "" || have.FP == wantFP):
|
||||||
|
// A provider's have counts as its outcome only from a build whose manifest offers the provision (ADR 0603 clause 5).
|
||||||
|
// Refused only when that manifest is known and does not offer it: a have with no builder key (an old-form build, an
|
||||||
|
// adopted module) or an unreadable manifest keeps the outcome it had.
|
||||||
|
if offers, known := h.buildOffers(p.Provider.Module, have.FP, p.Provision); known && !offers {
|
||||||
|
p.State, p.Why = provisions.Asked, "waits for "+where+" to run a build that offers "+p.Provision+": the build it runs ("+
|
||||||
|
have.FP+") does not"
|
||||||
|
return
|
||||||
|
}
|
||||||
p.State, p.Why = provisions.Provided, ""
|
p.State, p.Why = provisions.Provided, ""
|
||||||
default:
|
default:
|
||||||
p.State, p.Why = provisions.Asked, fmt.Sprintf("waits for %s to run healthy at the build its want names (it is %s, "+
|
p.State, p.Why = provisions.Asked, fmt.Sprintf("waits for %s to run healthy at the build its want names (it is %s, "+
|
||||||
@@ -826,6 +837,56 @@ func (h *Holder) haveOutcome(p *provisions.Provision) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildOffers says whether the build of a module at a fingerprint, as the builder keyed it, was made from a manifest that
|
||||||
|
// offers the provision; known is false when the builder holds no such key, the key names no manifest, or the indexer holds
|
||||||
|
// no such manifest (the caller then does not refuse).
|
||||||
|
func (h *Holder) buildOffers(module, fingerprint, provision string) (offers, known bool) {
|
||||||
|
key, err := delivery.BuilderBuildKey(module, fingerprint)
|
||||||
|
if err != nil {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
be, err := h.Store.Get(BuildsRead, key)
|
||||||
|
if err != nil || be == nil {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
b, err := delivery.DecodeBuilderBuild(be.Value)
|
||||||
|
if err != nil || b.Manifest == "" {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
mk, err := delivery.ManifestsKey(b.Manifest)
|
||||||
|
if err != nil {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
me, err := h.Store.Get(ManifestsRead, mk)
|
||||||
|
if err != nil || me == nil {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
man, err := delivery.DecodeManifest(me.Value)
|
||||||
|
if err != nil {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
var decl struct {
|
||||||
|
Provides []json.RawMessage `json:"provides"`
|
||||||
|
}
|
||||||
|
if json.Unmarshal([]byte(man.JSON), &decl) != nil {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
for _, raw := range decl.Provides {
|
||||||
|
var name string
|
||||||
|
if json.Unmarshal(raw, &name) != nil {
|
||||||
|
var full struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
_ = json.Unmarshal(raw, &full)
|
||||||
|
name = full.Name
|
||||||
|
}
|
||||||
|
if name == provision {
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
|
||||||
// keepsEntries says whether a provider's outcome for one provision is its entries (its manifest at the trunk declares the
|
// keepsEntries says whether a provider's outcome for one provision is its entries (its manifest at the trunk declares the
|
||||||
// state `bound`) rather than its own have. When any offer of the provider says `answered-by: helper`, only those offers
|
// state `bound`) rather than its own have. When any offer of the provider says `answered-by: helper`, only those offers
|
||||||
// are answered by its entries: a module running the helper for one provision and making another some other way (gitea:
|
// are answered by its entries: a module running the helper for one provision and making another some other way (gitea:
|
||||||
|
|||||||
@@ -224,6 +224,34 @@ func TestTheHolderPlansProvidesAndAsksOnTheBus(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
settled(HaveRead, node+".postgres")
|
settled(HaveRead, node+".postgres")
|
||||||
|
// The running build's manifest, as the builder keyed it: one that does not offer the provision keeps the key asked
|
||||||
|
// (ADR 0603 clause 5), one that does lets the have count.
|
||||||
|
buildsKey := mustKey(delivery.BuilderBuildKey("postgres", fpsOf["postgres"]))
|
||||||
|
offered := func(provides string) {
|
||||||
|
raw := `{"module":"postgres","version":"1","provides":` + provides + `}`
|
||||||
|
digest := delivery.ManifestDigest([]byte(raw))
|
||||||
|
value, _ := delivery.EncodeManifest(delivery.Manifest{Module: "postgres", JSON: raw})
|
||||||
|
if err := s.Put(ManifestsRead, mustKey(delivery.ManifestsKey(digest)), json.RawMessage(value)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
build, err := delivery.EncodeBuilderBuild(delivery.BuilderBuild{State: delivery.BuildDone, Manifest: digest})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := s.Put(BuildsRead, buildsKey, json.RawMessage(build)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offered(`["other"]`)
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
if _, err := h.ComposeNode(ctx, node); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p := state(key); p.State != provisions.Asked {
|
||||||
|
t.Fatalf("a build not offering the provision turned it %s", p.State)
|
||||||
|
}
|
||||||
|
offered(`["postgres-database"]`)
|
||||||
for i := 0; i < 2; i++ { // the credential judged first, then what depends on it
|
for i := 0; i < 2; i++ { // the credential judged first, then what depends on it
|
||||||
if _, err := h.ComposeNode(ctx, node); err != nil {
|
if _, err := h.ComposeNode(ctx, node); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
@@ -189,6 +189,28 @@ func TestAKeysStateFollowsItsProvidersOutcome(t *testing.T) {
|
|||||||
t.Errorf("the provider's have %s: %+v, want %s", row.have, p, row.state)
|
t.Errorf("the provider's have %s: %+v, want %s", row.have, p, row.state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// A have counts as the provider's outcome only from a build whose manifest offers the provision (ADR 0603 clause 5):
|
||||||
|
// refused when the build's manifest is known and does not offer it, provided when it does.
|
||||||
|
bfp := "src2:" + strings.Repeat("bb", 32)
|
||||||
|
buildKey := mustKey(delivery.BuilderBuildKey("systemd-resolved", bfp))
|
||||||
|
for _, row := range []struct{ name, provides, state, why string }{
|
||||||
|
{"a build whose manifest does not offer it", `["other"]`, provisions.Asked, "offers split-dns"},
|
||||||
|
{"a build whose manifest offers it", `[{"name":"split-dns"}]`, provisions.Provided, ""},
|
||||||
|
} {
|
||||||
|
raw := `{"module":"systemd-resolved","version":"1","provides":` + row.provides + `}`
|
||||||
|
digest := delivery.ManifestDigest([]byte(raw))
|
||||||
|
value, _ := delivery.EncodeManifest(delivery.Manifest{Module: "systemd-resolved", JSON: raw})
|
||||||
|
_ = m.s.Put(ManifestsRead, mustKey(delivery.ManifestsKey(digest)), json.RawMessage(value))
|
||||||
|
build, err := delivery.EncodeBuilderBuild(delivery.BuilderBuild{State: delivery.BuildDone, Manifest: digest})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_ = m.s.Put(BuildsRead, buildKey, json.RawMessage(build))
|
||||||
|
m.compose(t, "g14")
|
||||||
|
if p := m.state(t, split); p.State != row.state || !strings.Contains(p.Why, row.why) {
|
||||||
|
t.Errorf("%s: %+v, want %s", row.name, p, row.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
if e, _ := m.s.Get(PlansState, "g14.gitea"); e == nil {
|
if e, _ := m.s.Get(PlansState, "g14.gitea"); e == nil {
|
||||||
t.Error("the plan is not written")
|
t.Error("the plan is not written")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// memStore is the bus as mesh-provisioner reaches it, in memory: the states it keeps.
|
// memStore is the bus as mesh-provisioner reaches it, in memory: the states it keeps.
|
||||||
@@ -326,13 +324,15 @@ func TestTheManifestDeclaresWhatTheCodeKeeps(t *testing.T) {
|
|||||||
t.Errorf("the manifest lists %s, which is not served", tool)
|
t.Errorf("the manifest lists %s, which is not served", tool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, r := range append(slices.Clone(WatchedReads), ManifestsRead) {
|
// The builds read serves only haveOutcome's offer check (ADR 0603 clause 5: which manifest the build a provider runs
|
||||||
|
// was made from) and makes no plan, so ADR 0593 §8 ("it composes nothing") holds.
|
||||||
|
for _, r := range append(slices.Clone(WatchedReads), ManifestsRead, BuildsRead) {
|
||||||
if !slices.Contains(m.Reads, r) {
|
if !slices.Contains(m.Reads, r) {
|
||||||
t.Errorf("the manifest does not read %s, which the holder reads", r)
|
t.Errorf("the manifest does not read %s, which the holder reads", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// It composes nothing (ADR 0593 §8): no build, and no config value, is read to make a plan.
|
// It composes nothing (ADR 0593 §8): no config value is read to make a plan.
|
||||||
for _, r := range []string{delivery.BuilderModule + ".builds", "mesh-config.resolved"} {
|
for _, r := range []string{"mesh-config.resolved"} {
|
||||||
if slices.Contains(m.Reads, r) {
|
if slices.Contains(m.Reads, r) {
|
||||||
t.Errorf("the manifest reads %s, which only a composer reads", r)
|
t.Errorf("the manifest reads %s, which only a composer reads", r)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module mesh-provisioner
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128 h1:aI1DhZAuqVBXbybSqhLNOGX5rUOrn9T0cHNCDUQF7IQ=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 h1:RtAFbUJRP1qO9AIMM2ue+cXNzqj7WZ7IOlSqNvttwuU=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929/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 h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"mesh-delivery-indexer.modules",
|
"mesh-delivery-indexer.modules",
|
||||||
"mesh-delivery-indexer.manifests",
|
"mesh-delivery-indexer.manifests",
|
||||||
"node-engine.have",
|
"node-engine.have",
|
||||||
|
"mesh-delivery-builder.builds",
|
||||||
"mesh-host.composed",
|
"mesh-host.composed",
|
||||||
"gitea.bound",
|
"gitea.bound",
|
||||||
"keycloak.bound",
|
"keycloak.bound",
|
||||||
@@ -45,6 +46,7 @@
|
|||||||
"plans",
|
"plans",
|
||||||
"refused"
|
"refused"
|
||||||
],
|
],
|
||||||
|
"core-credentials": true,
|
||||||
"own-secrets": {
|
"own-secrets": {
|
||||||
"broker": "${dir:state}/broker"
|
"broker": "${dir:state}/broker"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,10 +24,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CoreModules are the core's modules whose own secrets the controller carries sealed by the vault: the same fixed
|
|
||||||
// list as the controller's (ADR 0349 §16). The node-engine and the controller have no own secrets of their own here.
|
|
||||||
var CoreModules = []string{"mesh-provisioner", "mesh-provisioner-client", "nats"}
|
|
||||||
|
|
||||||
// nodeKeyFile is a machine's sealing key as its engine stated it.
|
// nodeKeyFile is a machine's sealing key as its engine stated it.
|
||||||
type nodeKeyFile struct {
|
type nodeKeyFile struct {
|
||||||
Node string `json:"node"`
|
Node string `json:"node"`
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -56,13 +55,6 @@ func clientsBroker(sealingKey string) MoveOf {
|
|||||||
Generation: "g-1", SealingKey: sealingKey}
|
Generation: "g-1", SealingKey: sealingKey}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The list is the controller's (mesh-controller's core list, ADR 0349 §16): named here so a change to either is seen.
|
|
||||||
func TestTheCoreIsTheBusTheProvisionerAndItsClient(t *testing.T) {
|
|
||||||
if want := []string{"mesh-provisioner", "mesh-provisioner-client", "nats"}; !reflect.DeepEqual(CoreModules, want) {
|
|
||||||
t.Fatalf("the core's modules are %v, want %v", CoreModules, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A node-engine states its machine's sealing key in its own name at the move, and only its own machine's.
|
// A node-engine states its machine's sealing key in its own name at the move, and only its own machine's.
|
||||||
func TestAMoveRecordsTheSealingKeyItsNodeEngineStates(t *testing.T) {
|
func TestAMoveRecordsTheSealingKeyItsNodeEngineStates(t *testing.T) {
|
||||||
v := newVault(t, nil)
|
v := newVault(t, nil)
|
||||||
|
|||||||
@@ -292,7 +292,10 @@ func (v *Vault) StateNodeKey(caller, key string) error {
|
|||||||
}
|
}
|
||||||
for _, r := range all {
|
for _, r := range all {
|
||||||
parts := strings.Split(r.Name, "/")
|
parts := strings.Split(r.Name, "/")
|
||||||
if len(parts) == 4 && parts[0] == "own" && parts[1] == node && slices.Contains(CoreModules, parts[2]) {
|
// Every own secret of the machine is said changed: which modules carry theirs sealed by the vault is what their
|
||||||
|
// manifests say (core-credentials), read by the node-engine, which composes again and reseals those alone; the vault
|
||||||
|
// keeps no list of modules (issue 774, the operator 2026-11-03).
|
||||||
|
if len(parts) == 4 && parts[0] == "own" && parts[1] == node {
|
||||||
v.announce(Changed{Name: r.Name, Generation: r.Generation, At: v.Now().UTC(), By: caller})
|
v.announce(Changed{Name: r.Name, Generation: r.Generation, At: v.Now().UTC(), By: caller})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package main
|
|||||||
|
|
||||||
// Issue 786 (ADR 0372): a machine's node-engine states its own sealing key to the vault, in its own name, at start and
|
// Issue 786 (ADR 0372): a machine's node-engine states its own sealing key to the vault, in its own name, at start and
|
||||||
// when it changes, whether or not it ever moved a secret in. The caller is the bus user the runtime proved; a changed
|
// when it changes, whether or not it ever moved a secret in. The caller is the bus user the runtime proved; a changed
|
||||||
// key (or a first one) raises `secret-changed` for the machine's core secrets, the same key says nothing.
|
// key (or a first one) raises `secret-changed` for the machine's own secrets, the same key says nothing.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||||
@@ -76,7 +76,8 @@ func TestANodeEngineStatesItsOwnKeyAndAChangeRaisesItsCoreSecrets(t *testing.T)
|
|||||||
t.Error("a key was recorded for g14 by ace's engine")
|
t.Error("a key was recorded for g14 by ace's engine")
|
||||||
}
|
}
|
||||||
got := changedNames(v.bus)[before:]
|
got := changedNames(v.bus)[before:]
|
||||||
want := []string{"own/ace/mesh-provisioner-client/provisioner-broker", "own/ace/nats/broker"}
|
// Every own secret of the machine: which carry the vault's box their manifests say, read by the node-engine (issue 774).
|
||||||
|
want := []string{"own/ace/mesh-provisioner-client/provisioner-broker", "own/ace/nats/broker", "own/ace/postgres/superuser-password"}
|
||||||
if strings.Join(got, ",") != strings.Join(want, ",") {
|
if strings.Join(got, ",") != strings.Join(want, ",") {
|
||||||
t.Errorf("secret-changed was raised for %v, want %v", got, want)
|
t.Errorf("secret-changed was raised for %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
@@ -85,16 +86,16 @@ func TestANodeEngineStatesItsOwnKeyAndAChangeRaisesItsCoreSecrets(t *testing.T)
|
|||||||
if _, err := tool.runAs("node.ace", map[string]any{"key": pub}); err != nil {
|
if _, err := tool.runAs("node.ace", map[string]any{"key": pub}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if n := len(changedNames(v.bus)); n != before+2 {
|
if n := len(changedNames(v.bus)); n != before+3 {
|
||||||
t.Errorf("the same key raised %d more", n-before-2)
|
t.Errorf("the same key raised %d more", n-before-3)
|
||||||
}
|
}
|
||||||
// A different key raises them again.
|
// A different key raises them again.
|
||||||
other, _ := aSealingKey(t)
|
other, _ := aSealingKey(t)
|
||||||
if _, err := tool.runAs("node.ace", map[string]any{"key": other}); err != nil {
|
if _, err := tool.runAs("node.ace", map[string]any{"key": other}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if n := len(changedNames(v.bus)); n != before+4 {
|
if n := len(changedNames(v.bus)); n != before+6 {
|
||||||
t.Errorf("a changed key raised %d, want 2", n-before-2)
|
t.Errorf("a changed key raised %d, want 3", n-before-3)
|
||||||
}
|
}
|
||||||
if got, _, _ := v.Store.NodeKey("ace"); got != other {
|
if got, _, _ := v.Store.NodeKey("ace"); got != other {
|
||||||
t.Error("the changed key was not recorded")
|
t.Error("the changed key was not recorded")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module mesh-vault
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103142843-f8bd12e4b7b1
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
golang.org/x/crypto v0.57.0
|
golang.org/x/crypto v0.57.0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103142843-f8bd12e4b7b1 h1:T66tg9T/1hHg6KN/bDScHPT41IHkxLAGGO8VJiAOlb8=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 h1:RtAFbUJRP1qO9AIMM2ue+cXNzqj7WZ7IOlSqNvttwuU=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103142843-f8bd12e4b7b1/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929/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 h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ module nats-tools
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929
|
||||||
github.com/klauspost/compress v1.20.0
|
github.com/klauspost/compress v1.20.0
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128 h1:aI1DhZAuqVBXbybSqhLNOGX5rUOrn9T0cHNCDUQF7IQ=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 h1:RtAFbUJRP1qO9AIMM2ue+cXNzqj7WZ7IOlSqNvttwuU=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929/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 h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "1",
|
"version": "1",
|
||||||
"upgrade": {
|
"upgrade": {
|
||||||
"policy": "record",
|
"policy": "record",
|
||||||
"why": "the bus: replaced only as a planned step a person starts (`bus upgrade`), its streams snapshotted first and checked after (hq ADR 0236); the controller holds this whatever is said here"
|
"why": "the bus keeps the streams of the whole mesh: each move waits for a person's yes, which the operator's Approve of the release that carries its build gives, and its node backs its streams up before it applies (hq ADR 0236, ADR 0601)"
|
||||||
},
|
},
|
||||||
"provides": [
|
"provides": [
|
||||||
{
|
{
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bus-users": "/var/lib/nats-module/conf/accounts.conf",
|
"bus-users": "/var/lib/nats-module/conf/accounts.conf",
|
||||||
|
"core-credentials": true,
|
||||||
"own-secrets": {
|
"own-secrets": {
|
||||||
"broker": "${dir:mesh-state}/broker",
|
"broker": "${dir:mesh-state}/broker",
|
||||||
"broker-nats-tools": "${dir:tools-secret}/broker-nats-tools"
|
"broker-nats-tools": "${dir:tools-secret}/broker-nats-tools"
|
||||||
|
|||||||
Reference in New Issue
Block a user