Merge pull request 'The node-engine applies every module but the held ones and marks only those (group fix/1126-apply-culprit, issue 1126)' (#165) from fix/1126-apply-culprit into main
This commit was merged in pull request #165.
This commit is contained in:
@@ -551,9 +551,9 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, sess
|
||||
}
|
||||
return got.SealedAt, got.Sealed, nil
|
||||
},
|
||||
refused: func(why string) {
|
||||
refused: func(why string, held map[string]string) {
|
||||
if now := own.get(); now != nil && run.Err() == nil {
|
||||
now.NotComposed(run, why)
|
||||
now.NotComposedEach(run, why, held) // a module held alone marks that module, never the others (issue 1126)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -883,7 +883,7 @@ func applyContext(ctx context.Context) (context.Context, context.CancelFunc) {
|
||||
type composerHooks struct {
|
||||
running func() []delivery.Composed
|
||||
unseal func(string) ([]byte, error)
|
||||
refused func(string)
|
||||
refused func(why string, held map[string]string)
|
||||
held func(module string) (delivery.Seals, []string, error)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ module github.com/novox/mesh-host
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028130759-f387581b6bfb
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028162235-0de30064156a
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
golang.org/x/crypto v0.57.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028130759-f387581b6bfb h1:RioCDIPNJCv09/UJ3z1ocK0zFAf+Yuk47ICkfPfpEYo=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028130759-f387581b6bfb/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028162235-0de30064156a h1:BYJrXqPfeNSF0eypX3UnczLq/geXNkfJbiUK9JHlHNA=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028162235-0de30064156a/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/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
|
||||
+79
-10
@@ -24,6 +24,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/declaration"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -162,7 +163,10 @@ type Agent struct {
|
||||
said map[string]string
|
||||
// notComposed is the modules whose have says the node could not compose them: why, and the have before (NotComposed).
|
||||
notComposed map[string]notComposedMark
|
||||
retry chan []string
|
||||
// heldAlone is the modules the composer holds alone (NotComposedEach): what runs of each in the node's own
|
||||
// composition is held out of the node's own apply until a composition of its own is handed (heldOut).
|
||||
heldAlone map[string]bool
|
||||
retry chan []string
|
||||
// tries counts the transient failures in a row of each module; again carries a retry whose wait ran out, and
|
||||
// transient asks for every transiently failed module at once. stopped is closed when Run returns.
|
||||
tries map[string]int
|
||||
@@ -364,23 +368,45 @@ var readAgain = time.Second
|
||||
// fingerprint, the digest and the sealed values the agent runs, or, before it ran anything since its start, those its
|
||||
// last have said (a refusal at the first compose after a start). Called with "" once the node composes again: each
|
||||
// module so marked has its have back as it was, unless an apply wrote it since.
|
||||
func (a *Agent) NotComposed(ctx context.Context, why string) {
|
||||
func (a *Agent) NotComposed(ctx context.Context, why string) { a.NotComposedEach(ctx, why, nil) }
|
||||
|
||||
// NotComposedEach is NotComposed for the node refused whole (why), or, with why empty, for the modules the node holds
|
||||
// alone (held, each with its own why: refused alone, the rest composed and applied, issue 1126): only those are marked,
|
||||
// each naming its own why, and every other module marked before has its have back. Both empty, every mark is cleared.
|
||||
func (a *Agent) NotComposedEach(ctx context.Context, why string, held map[string]string) {
|
||||
a.mu.Lock()
|
||||
var modules []string
|
||||
if why != "" {
|
||||
for module, w := range a.wants {
|
||||
if w.Present && !w.Want.Removed && module != NodeModule && a.notComposed[module].why != why {
|
||||
modules = append(modules, module)
|
||||
}
|
||||
if why == "" {
|
||||
a.heldAlone = make(map[string]bool, len(held))
|
||||
for m := range held {
|
||||
a.heldAlone[m] = true
|
||||
}
|
||||
} else {
|
||||
for module := range a.notComposed {
|
||||
}
|
||||
whyOf := map[string]string{} // the mark each module is to carry; a marked module not in it is cleared
|
||||
for module, w := range a.wants {
|
||||
if !w.Present || w.Want.Removed || module == NodeModule {
|
||||
continue
|
||||
}
|
||||
if why != "" {
|
||||
whyOf[module] = why
|
||||
} else if h, ok := held[module]; ok && h != "" {
|
||||
whyOf[module] = h
|
||||
}
|
||||
}
|
||||
var modules []string
|
||||
for module, w := range whyOf {
|
||||
if a.notComposed[module].why != w {
|
||||
modules = append(modules, module)
|
||||
}
|
||||
}
|
||||
for module := range a.notComposed {
|
||||
if _, still := whyOf[module]; !still && why == "" { // the node refused whole marks; it clears none
|
||||
modules = append(modules, module)
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
sort.Strings(modules)
|
||||
for _, module := range modules {
|
||||
why := whyOf[module]
|
||||
if ctx.Err() != nil {
|
||||
return // the run ended: what it would mark or clear is the next run's to say (issue 1126)
|
||||
}
|
||||
@@ -806,6 +832,49 @@ func (a *Agent) heldOut(except string) []delivery.Composed {
|
||||
sort.Strings(names)
|
||||
for _, m := range names {
|
||||
out = append(out, waiting[m].C)
|
||||
held[m] = true
|
||||
}
|
||||
// A module the composer holds alone keeps what runs of it in the node's own composition, until a composition of its
|
||||
// own is handed: held out as one of its own, so the node's own apply never removes it (issue 1126, review of the
|
||||
// apply, round 1: de-spiegel leaving the fold with its names unset; issue 1945).
|
||||
if len(a.heldAlone) == 0 {
|
||||
return out
|
||||
}
|
||||
var own *delivery.Composed
|
||||
for _, c := range a.others("\x00") {
|
||||
if c.Module == "" {
|
||||
own = &c
|
||||
break
|
||||
}
|
||||
}
|
||||
alone := make([]string, 0, len(a.heldAlone))
|
||||
for m := range a.heldAlone {
|
||||
if !held[m] && m != except && own != nil {
|
||||
alone = append(alone, m)
|
||||
}
|
||||
}
|
||||
sort.Strings(alone)
|
||||
for _, m := range alone {
|
||||
var rs []json.RawMessage
|
||||
for _, raw := range own.Resources {
|
||||
var head struct {
|
||||
ID string `json:"id"`
|
||||
Module string `json:"module"`
|
||||
}
|
||||
if json.Unmarshal(raw, &head) != nil {
|
||||
continue
|
||||
}
|
||||
owner := head.Module
|
||||
if owner == "" {
|
||||
owner, _ = declaration.ModuleGuess(head.ID)
|
||||
}
|
||||
if owner == m {
|
||||
rs = append(rs, raw)
|
||||
}
|
||||
}
|
||||
if len(rs) > 0 {
|
||||
out = append(out, delivery.Composed{Node: a.env.Node, Module: m, Resources: rs})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -838,6 +838,37 @@ func TestTheNodesOwnApplyHoldsOutAModuleThatMovedToItsOwnComposition(t *testing.
|
||||
if !strings.Contains(strings.Join(names, ","), "systemd-resolved") {
|
||||
t.Fatalf("the others of the node's own apply were %v: systemd-resolved's resources would be removed", names)
|
||||
}
|
||||
|
||||
// Issue 1126, review of the apply, round 1: a module the composer holds alone (de-spiegel leaving the fold with its
|
||||
// names unset) has no composition of its own in the bucket: what runs of it in the node's own is held out of the
|
||||
// node's own apply until one is handed.
|
||||
b2 := newBed(t)
|
||||
spiegel := file("de-spiegel", "server-env", "A=1")
|
||||
var bare map[string]any
|
||||
_ = json.Unmarshal(spiegel, &bare)
|
||||
delete(bare, "module")
|
||||
spiegel, _ = json.Marshal(bare)
|
||||
b2.env.Kept = func() map[string][]json.RawMessage { return map[string][]json.RawMessage{NodeModule: {spiegel}} }
|
||||
nk2, nraw2, ndig2 := composition(t, NodeModule, "", false)
|
||||
nwraw2, _ := delivery.EncodeNodeWant(delivery.NodeWant{Composed: ndig2})
|
||||
b2.comps.before(nk2, nraw2)
|
||||
b2.wants.before(nwk, nwraw2)
|
||||
ctx2, cancel2 := context.WithCancel(context.Background())
|
||||
b2.ag = New(b2.env)
|
||||
b2.ag.NotComposedEach(ctx2, "", map[string]string{"de-spiegel": "de-spiegel says ${config:praktijk-name}, and nothing sets it"})
|
||||
done2 := make(chan struct{})
|
||||
go func() { defer close(done2); _ = b2.ag.Run(ctx2) }()
|
||||
defer func() { cancel2(); <-done2 }()
|
||||
eventually(t, "the node's own not applied beside a held module", func() bool { return len(b2.appliedList()) == 1 })
|
||||
b2.mu.Lock()
|
||||
defer b2.mu.Unlock()
|
||||
heldOut := false
|
||||
for _, c := range b2.others[0] {
|
||||
heldOut = heldOut || (c.Module == "de-spiegel" && len(c.Resources) == 1)
|
||||
}
|
||||
if !heldOut {
|
||||
t.Errorf("the others of the node's own apply were %+v: held de-spiegel's resources would be removed", b2.others[0])
|
||||
}
|
||||
}
|
||||
|
||||
// A module the assigner removed holds nothing back, its compositions still in the bucket or not: its resources are its
|
||||
@@ -1134,6 +1165,20 @@ func TestTheNodesOwnCompositionIsAppliedWhenItDiffersAndARecordMoveWaitsForAYes(
|
||||
t.Errorf("%s's have once the node composes again: %+v", row.module, h)
|
||||
}
|
||||
}
|
||||
// Issue 1126, the applying path: a module the node holds alone (its own refusal: de-spiegel's unset route names on
|
||||
// novox) is marked alone, naming its own words; web, which composes, keeps running, unmarked. Composed again, idle's
|
||||
// have is what it runs.
|
||||
b.ag.NotComposedEach(context.Background(), "", map[string]string{"idle": "idle says ${config:name}, and nothing sets it"})
|
||||
if h := b.haves.last("idle"); h.Phase != string(delivery.PhaseFailed) || h.Composed != idleDig || !strings.Contains(h.Detail, "${config:name}") {
|
||||
t.Errorf("idle's have while it alone is held: %+v", h)
|
||||
}
|
||||
if h := b.haves.last("web"); h.Phase != string(delivery.PhaseRunning) || h.Composed != dig {
|
||||
t.Errorf("web, which composes, is marked beside idle: %+v", h)
|
||||
}
|
||||
b.ag.NotComposedEach(context.Background(), "", nil)
|
||||
if h := b.haves.last("idle"); h.Phase != string(delivery.PhaseRunning) || h.Composed != idleDig {
|
||||
t.Errorf("idle's have once it composes again: %+v", h)
|
||||
}
|
||||
// A revision at the same build (a config value changed) is applied.
|
||||
_, craw2, dig2 := unsigned("web", fp, file("web", "x", "2"))
|
||||
b.comps.put(ck, craw2)
|
||||
|
||||
@@ -52,9 +52,11 @@ type Env struct {
|
||||
// Hear subscribes a subject: here the vault's word that a secret changed (VaultSecretChanged), on which the node
|
||||
// composes again, so a rotated secret or credential reaches it at once (ADR 0547 §2). Nil hears nothing.
|
||||
Hear func(subject string) (<-chan []byte, func(), error)
|
||||
// Refused is told why the node cannot compose its modules, and "" once it composes again, so each wanted module's
|
||||
// have says it (agent.NotComposed): a refusal is loud where haves are read, not one journal line. Nil tells nobody.
|
||||
Refused func(why string)
|
||||
// Refused is told why the node cannot compose its modules (why, the node refused whole), or, of a node that composes,
|
||||
// each module it holds and why (held: refused alone, the rest composed and applied, frombus.Compose), and both empty
|
||||
// once it composes every module again, so each module's have says it (agent.NotComposedEach): a refusal is loud where
|
||||
// haves are read, not one journal line, and one module's never marks the others (issue 1126). Nil tells nobody.
|
||||
Refused func(why string, held map[string]string)
|
||||
// Running is what the node runs now (agent.Everything), and Unseal opens a value sealed to this machine: a secret
|
||||
// the vault seals again for each ask is kept as the box the node already runs at the same slot (the same field of
|
||||
// the same resource of the same module, delivery.KeepSeals) when that box opens to the same value, so neither the
|
||||
@@ -497,7 +499,7 @@ func (c *Composer) compose(ctx context.Context) []agent.Update {
|
||||
}
|
||||
scope := frombus.Scope{Node: c.env.Node, KeptAt: kept, InOwn: inOwn, Running: running}
|
||||
missed := c.missed.Load()
|
||||
compositions, _, err := frombus.Compose(ctx, r, c.env.Mesh, scope, fps)
|
||||
compositions, _, held, err := frombus.Compose(ctx, r, c.env.Mesh, scope, fps)
|
||||
err = c.missedIn(missed, err)
|
||||
// A bus that does not answer for a moment (a reload of its accounts pauses it a few seconds) is no refusal: the pass is
|
||||
// tried again after a growing wait before anything is marked (issue 1126).
|
||||
@@ -509,7 +511,7 @@ func (c *Composer) compose(ctx context.Context) []agent.Update {
|
||||
}
|
||||
if ctx.Err() == nil {
|
||||
missed = c.missed.Load()
|
||||
compositions, _, err = frombus.Compose(ctx, r, c.env.Mesh, scope, fps)
|
||||
compositions, _, held, err = frombus.Compose(ctx, r, c.env.Mesh, scope, fps)
|
||||
err = c.missedIn(missed, err)
|
||||
}
|
||||
}
|
||||
@@ -521,7 +523,7 @@ func (c *Composer) compose(ctx context.Context) []agent.Update {
|
||||
if err != nil {
|
||||
// Said before the agent marks the modules: the marks read each have first, which a paused bus delays.
|
||||
c.sayOnce(fmt.Sprintf("the node cannot compose its modules, and keeps running what it runs: %v", err))
|
||||
c.refused(fmt.Sprintf("%v", err))
|
||||
c.refused(fmt.Sprintf("%v", err), nil)
|
||||
// A bus that did not answer past every try is no answer: composed again after a bound (issue 1126, 2026-10-28).
|
||||
c.owed.Store(BusFailed(err))
|
||||
return nil
|
||||
@@ -539,8 +541,19 @@ func (c *Composer) compose(ctx context.Context) []agent.Update {
|
||||
return nil
|
||||
}
|
||||
c.owed.Store(false)
|
||||
c.refused("")
|
||||
c.sayOnce("")
|
||||
// A module refused alone is held: it keeps what it runs, its have says why, and every other module is applied (issue
|
||||
// 1126: one module's refusal froze the whole node).
|
||||
c.refused("", held)
|
||||
if len(held) == 0 {
|
||||
c.sayOnce("")
|
||||
} else {
|
||||
var lines []string
|
||||
for _, m := range heldModules(held) {
|
||||
lines = append(lines, m+": "+held[m])
|
||||
}
|
||||
c.sayOnce("the node composes its modules but holds " + strings.Join(heldModules(held), ", ") +
|
||||
", which keep running what they run: " + strings.Join(lines, "; "))
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
var out []agent.Update
|
||||
@@ -574,6 +587,11 @@ func (c *Composer) compose(ctx context.Context) []agent.Update {
|
||||
out = append(out, agent.Update{Key: key, Value: raw, Revision: c.rev})
|
||||
}
|
||||
for key := range c.handed {
|
||||
if _, module, _, _, err := delivery.ParseComposedKey(key); err == nil && module != "" {
|
||||
if _, isHeld := held[module]; isHeld {
|
||||
continue // held: what was handed of it stays, so the node keeps running it
|
||||
}
|
||||
}
|
||||
if !now[key] {
|
||||
c.rev++
|
||||
delete(c.handed, key)
|
||||
@@ -583,14 +601,29 @@ func (c *Composer) compose(ctx context.Context) []agent.Update {
|
||||
return out
|
||||
}
|
||||
|
||||
// refused tells the agent why the node cannot compose, or "" that it composes again, once per change.
|
||||
func (c *Composer) refused(why string) {
|
||||
// heldModules is the modules of held, sorted.
|
||||
func heldModules(held map[string]string) []string {
|
||||
out := make([]string, 0, len(held))
|
||||
for m := range held {
|
||||
out = append(out, m)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// refused tells the agent why the node cannot compose (why), or which modules it holds (held), or both empty that it
|
||||
// composes every module again, once per change.
|
||||
func (c *Composer) refused(why string, held map[string]string) {
|
||||
said := why
|
||||
for _, m := range heldModules(held) {
|
||||
said += "\x00" + m + "\x00" + held[m]
|
||||
}
|
||||
c.mu.Lock()
|
||||
same := c.refusal == why
|
||||
c.refusal = why
|
||||
same := c.refusal == said
|
||||
c.refusal = said
|
||||
c.mu.Unlock()
|
||||
if !same && c.env.Refused != nil {
|
||||
c.env.Refused(why)
|
||||
c.env.Refused(why, held)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -249,7 +250,12 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
var refusals []string
|
||||
e := b.env()
|
||||
e.Get = get
|
||||
e.Refused = func(why string) { refusals = append(refusals, why) }
|
||||
e.Refused = func(why string, held map[string]string) {
|
||||
for _, m := range heldModules(held) {
|
||||
why += "held " + m + ": " + held[m]
|
||||
}
|
||||
refusals = append(refusals, why)
|
||||
}
|
||||
c2 := New(e)
|
||||
c.mu.Lock()
|
||||
for k, v := range c.wants {
|
||||
@@ -307,6 +313,57 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
Transient(fmt.Errorf("the revisions of it2.mailer.domain: %w", context.DeadlineExceeded)) {
|
||||
t.Error("Transient misjudges a bus that does not answer, or a refusal")
|
||||
}
|
||||
// Issue 1126, the applying path (review of the seed): one module's own refusal holds that module alone. On novox,
|
||||
// de-spiegel's unset route names (its manifest at the indexer's commit e25e1beb, captured) refused the whole node and
|
||||
// marked all its modules not composed. Now lemurs is handed; de-spiegel is held, and proxy with it, which receives its
|
||||
// routes: what was handed of de-spiegel stays handed, so the node keeps running it, and the agent is told those two
|
||||
// alone, de-spiegel in its config form's words.
|
||||
spiegel, err := os.ReadFile("testdata/de-spiegel-e25e1beb.module.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
proxy := `{"module":"proxy","version":"1","capabilities":["systemd"],"provides":[{"name":"route","scope":"mesh"},{"name":"smtp","scope":"mesh"}],` +
|
||||
`"resources":[{"id":"service","type":"service","unit":"proxy.service","state":"running","boot":"enabled"}]}`
|
||||
c3, told := fresh(env.Get)
|
||||
for m, raw := range map[string]string{"de-spiegel": string(spiegel), "proxy": proxy} {
|
||||
dig := delivery.ManifestDigest([]byte(raw))
|
||||
enc, _ := delivery.EncodeManifest(delivery.Manifest{Module: m, JSON: raw})
|
||||
k, _ := delivery.ManifestsKey(dig)
|
||||
b.mu.Lock()
|
||||
b.kv[delivery.ManifestsBucket+"/"+k] = enc
|
||||
b.mu.Unlock()
|
||||
mfp := delivery.SourcePrefix2 + strings.Repeat(map[string]string{"de-spiegel": "d", "proxy": "e"}[m], 64)
|
||||
c3.modules[m] = delivery.Module{FP2: mfp, Manifest: dig, Repository: "novox/" + m, Commit: "c"}
|
||||
c3.wants[m] = delivery.Want{FP: mfp, Self: true}
|
||||
}
|
||||
p3 := c3.placements["n1"]
|
||||
p3.Modules = append(append([]delivery.PlacedModule{}, p3.Modules...), delivery.PlacedModule{Module: "proxy"}, delivery.PlacedModule{Module: "de-spiegel"})
|
||||
c3.placements["n1"] = p3
|
||||
r3 := c3.records["n1"]
|
||||
r3.Capabilities = []string{"systemd", "container-runtime"}
|
||||
c3.records["n1"] = r3
|
||||
spiegelKey, _ := delivery.ComposedKey("n1", "de-spiegel", c3.wants["de-spiegel"].FP)
|
||||
proxyKey, _ := delivery.ComposedKey("n1", "proxy", c3.wants["proxy"].FP)
|
||||
c3.handed[spiegelKey] = "sha256:" + strings.Repeat("5", 64) // what the node runs of it, handed before
|
||||
handed3 := map[string]agent.Update{}
|
||||
for _, u := range c3.compose(ctx) {
|
||||
handed3[u.Key] = u
|
||||
}
|
||||
lemursKey3, _ := delivery.ComposedKey("n1", "lemurs", c3.wants["lemurs"].FP)
|
||||
if u, ok := handed3[lemursKey3]; !ok || u.Deleted {
|
||||
t.Errorf("lemurs, which composes, is not handed beside de-spiegel's refusal: %v (told %q)", handed3, *told)
|
||||
}
|
||||
if u, ok := handed3[proxyKey]; ok {
|
||||
t.Errorf("proxy is handed without held de-spiegel's routes: %+v", u)
|
||||
}
|
||||
if u, ok := handed3[spiegelKey]; ok {
|
||||
t.Errorf("held de-spiegel is handed %+v, want what was handed of it kept", u)
|
||||
}
|
||||
if len(*told) != 1 || !strings.HasPrefix((*told)[0], "held de-spiegel: ") || !strings.Contains((*told)[0], "${config:praktijk-name}") ||
|
||||
!strings.Contains((*told)[0], "held proxy: ") || strings.Contains((*told)[0], "held lemurs") {
|
||||
t.Errorf("the agent was told %q, want de-spiegel held in its config form's words, and proxy", *told)
|
||||
}
|
||||
|
||||
// A read the bus did not answer, timed out or not, is tried again later; the run's own end and a refusal are not.
|
||||
if !BusFailed(fmt.Errorf("the revisions of it2.mailer.domain: %w", context.DeadlineExceeded)) || !BusFailed(fmt.Errorf("x: %w", nats.ErrNoResponders)) ||
|
||||
!BusFailed(fmt.Errorf("the have of web: %w", nats.ErrTimeout)) || BusFailed(fmt.Errorf("x: %w", context.Canceled)) ||
|
||||
@@ -589,7 +646,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
return nil, fmt.Errorf("not sealed to n1: %s", box)
|
||||
}
|
||||
var refusals []string
|
||||
pairEnv.Refused = func(why string) { refusals = append(refusals, why) }
|
||||
pairEnv.Refused = func(why string, held map[string]string) { refusals = append(refusals, why) }
|
||||
pc := New(pairEnv)
|
||||
consumer := []compose.CredentialKey{{Provision: "pg", Consumer: "n1", ConsumerModule: "web", Provider: "n2"}}
|
||||
provider := []compose.CredentialKey{{Provision: "s3", Consumer: "n3", ConsumerModule: "files", Provider: "n1"}}
|
||||
@@ -607,9 +664,9 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
t.Errorf("web's credential composed as %s, want the box that runs", got)
|
||||
}
|
||||
// A node that cannot compose tells the agent once, and once again when it composes (the agent's haves say it).
|
||||
pc.refused("a stand-in for web's password")
|
||||
pc.refused("a stand-in for web's password")
|
||||
pc.refused("")
|
||||
pc.refused("a stand-in for web's password", nil)
|
||||
pc.refused("a stand-in for web's password", nil)
|
||||
pc.refused("", nil)
|
||||
if len(refusals) != 2 || refusals[0] == "" || refusals[1] != "" {
|
||||
t.Errorf("the agent was told %q", refusals)
|
||||
}
|
||||
@@ -640,7 +697,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
return json.RawMessage(`{"node":"n1","key":"sk","own":[]}`), nil // nothing issued for keeper: it is not placed here
|
||||
}
|
||||
// What n1 runs: lemurs and its own composition, as its composer composes them from the trunk.
|
||||
running, _, err := frombus.Compose(ctx, fixedReader{placements: map[string]delivery.Placement{"n1": trunk},
|
||||
running, _, _, err := frombus.Compose(ctx, fixedReader{placements: map[string]delivery.Placement{"n1": trunk},
|
||||
records: map[string]delivery.NodeRecord{"n1": {Node: "n1", TunnelKey: "k", SealingKey: "sk", Capabilities: []string{"systemd"}}},
|
||||
modules: mods, manifests: map[string][]byte{digest: []byte(lemurs), kdigest: []byte(keeper)}}, frombus.Mesh{}, frombus.Scope{Node: "n1"}, map[string]string{"lemurs": fp})
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
{
|
||||
"module": "de-spiegel",
|
||||
"version": "1",
|
||||
"slug": "spiegel",
|
||||
"capabilities": [
|
||||
"container-runtime"
|
||||
],
|
||||
"requires": [
|
||||
"route",
|
||||
"smtp"
|
||||
],
|
||||
"contributes": {
|
||||
"route": {
|
||||
"site": {
|
||||
"label": "de-spiegel",
|
||||
"port": 35621
|
||||
},
|
||||
"praktijk": {
|
||||
"name": "${config:praktijk-name}",
|
||||
"port": 35621
|
||||
},
|
||||
"praktijk-www": {
|
||||
"name": "${config:praktijk-www-name}",
|
||||
"redirect": "https://${config:praktijk-name}"
|
||||
},
|
||||
"psychologie": {
|
||||
"name": "${config:psychologie-name}",
|
||||
"port": 35621
|
||||
},
|
||||
"psychologie-www": {
|
||||
"name": "${config:psychologie-www-name}",
|
||||
"redirect": "https://${config:psychologie-name}"
|
||||
}
|
||||
},
|
||||
"smtp": {
|
||||
"account": "de-spiegel"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
"route": "/var/lib/de-spiegel/route.json",
|
||||
"smtp": "/var/lib/de-spiegel/smtp.json"
|
||||
},
|
||||
"config": {
|
||||
"praktijk-name": {
|
||||
"type": "string",
|
||||
"description": "The public name the practice's site is served on; the operator's, with no default (novox/hq ADR 0112).",
|
||||
"trusted": true,
|
||||
"required": true
|
||||
},
|
||||
"praktijk-www-name": {
|
||||
"type": "string",
|
||||
"description": "The public name that redirects to the practice's site (its www form); the operator's, with no default (novox/hq ADR 0112).",
|
||||
"trusted": true,
|
||||
"required": true
|
||||
},
|
||||
"psychologie-name": {
|
||||
"type": "string",
|
||||
"description": "The public name the psychology practice's site is served on; the operator's, with no default (novox/hq ADR 0112).",
|
||||
"trusted": true,
|
||||
"required": true
|
||||
},
|
||||
"psychologie-www-name": {
|
||||
"type": "string",
|
||||
"description": "The public name that redirects to the psychology practice's site (its www form); the operator's, with no default (novox/hq ADR 0112).",
|
||||
"trusted": true,
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"port": 35621,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "the de-spiegel site and its /contact endpoint over http; the practice's three public names are route grants, and the proxy reaches every one of them here"
|
||||
}
|
||||
],
|
||||
"resources": [
|
||||
{
|
||||
"id": "state",
|
||||
"type": "directory",
|
||||
"path": "/var/lib/de-spiegel",
|
||||
"mode": "0700"
|
||||
},
|
||||
{
|
||||
"id": "server-env",
|
||||
"type": "file",
|
||||
"path": "/var/lib/de-spiegel/server.env",
|
||||
"mode": "0600",
|
||||
"content": "SMTP_HOST=${bound:smtp:at}\nSMTP_PORT=${bound:smtp:port}\nSMTP_AUTH_USER=de-spiegel@${bound:smtp:domain}\nSMTP_AUTH_PASS=${secret:smtp}\n"
|
||||
},
|
||||
{
|
||||
"id": "net",
|
||||
"type": "network",
|
||||
"name": "de-spiegel"
|
||||
},
|
||||
{
|
||||
"id": "server",
|
||||
"type": "container",
|
||||
"name": "de-spiegel",
|
||||
"artifact": "site",
|
||||
"network": "de-spiegel",
|
||||
"env-file": [
|
||||
"/var/lib/de-spiegel/server.env"
|
||||
],
|
||||
"ports": [
|
||||
"35621"
|
||||
],
|
||||
"secrets-in-environment": "the application's own code reads SMTP_AUTH_USER/PASS from the environment (server/index.js); converting is this repository's change"
|
||||
}
|
||||
],
|
||||
"build": {
|
||||
"on": [
|
||||
{
|
||||
"arg": "NODE_BASE",
|
||||
"image": "node@sha256:48e4b67d85f87bd551df43704e24d252f56cc5f8e9718841aace50f19948f0f9"
|
||||
}
|
||||
],
|
||||
"artifacts": [
|
||||
{
|
||||
"name": "site",
|
||||
"kind": "image",
|
||||
"from": "Dockerfile"
|
||||
}
|
||||
]
|
||||
},
|
||||
"secrets": {
|
||||
"smtp": "/var/lib/de-spiegel/smtp.secret"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user