package runtime import ( "bytes" "encoding/json" "errors" "fmt" "sync" "github.com/novox/mesh-tools/node-tools/internal/bus" ) // A module's code reads its values at runtime (novox/hq ADR 0349 clause 14 as ADR 0370 changes it, issue 685). // // A bundle asks `mesh/provision.get` and `mesh/provision.watch` with `{kind, key}`; the runtime asks its machine's // provisioner client **as the module whose bundle asks** — the module this bus was made for, from the bundle it // launched, never a name the bundle sent (ADR 0370 §4) — and hands the answer back. A watch hands the current value // first and then each change: the client says which value moved, and the runtime asks again. A secret is refused // here, with the reason, and never asked: the client serves no secret on the tool runner's word (§2), and a module // under an account of its own asks the vault itself, which waits on issue 569. // The kinds a bundle may ask. const ( provisionConfig = "config" provisionBound = "bound" provisionSecret = "secret" ) // provisionAsked is what a bundle names. Anything else it sends, a module's name among it, is not read. type provisionAsked struct { Kind string `json:"kind"` Key string `json:"key"` } // ProvisionChange is what a watch hands a bundle: the value, or, when it is no longer answered, why. type ProvisionChange struct { Kind string `json:"kind"` Key string `json:"key"` Value json.RawMessage `json:"value,omitempty"` // Refused is why the value is no longer answered: the module taken off the machine, the key no longer declared. Refused string `json:"refused,omitempty"` } func readProvisionAsked(verb string, params json.RawMessage) (provisionAsked, error) { var asked provisionAsked if err := json.Unmarshal(params, &asked); err != nil || asked.Kind == "" || asked.Key == "" { return asked, fmt.Errorf("mesh/provision.%s names no value: {kind, key}", verb) } switch asked.Kind { case provisionConfig, provisionBound: return asked, nil case provisionSecret: return asked, fmt.Errorf("the secret %s is not asked for at runtime while the module's code runs in the "+ "tool runner: the vault hands a secret only to a caller the bus proves, and the bus proves only the tool "+ "runner here (novox/hq ADR 0370 §2, ADR 0349 §11); the module's secrets reach it through its files, "+ "until it runs under a bus account of its own (issue 569)", asked.Key) } return asked, fmt.Errorf("mesh/provision.%s: %q is not a kind of value a module reads at runtime: config or bound", verb, asked.Kind) } // Provision answers a bundle's `get` of one of its module's values. func (b *moduleBus) Provision(verb string, params json.RawMessage) (json.RawMessage, error) { if verb != "get" { return nil, fmt.Errorf("the runtime answers no mesh/provision.%s", verb) } asked, err := readProvisionAsked(verb, params) if err != nil { return nil, err } return b.all.conn.AskProvisioner(b.module, asked.Kind, asked.Key) } // ProvisionWatch hands deliver the value now and each change after, until stopped. A value not answered now is // refused now, with the client's reason. func (b *moduleBus) ProvisionWatch(params json.RawMessage, deliver func(json.RawMessage) error) (func(), error) { asked, err := readProvisionAsked("watch", params) if err != nil { return nil, err } value, err := b.all.conn.AskProvisioner(b.module, asked.Kind, asked.Key) if err != nil { return nil, err } w := &provisionWatch{bus: b, asked: asked, deliver: deliver, last: value} if err := w.hand(ProvisionChange{Kind: asked.Kind, Key: asked.Key, Value: value}); err != nil { return nil, err } stop, err := b.all.provisioned.add(b.all, b.module, w) if err != nil { return nil, err } return stop, nil } // provisionWatch is one watch a bundle holds: what it asked, and what it was handed last. type provisionWatch struct { bus *moduleBus asked provisionAsked deliver func(json.RawMessage) error mu sync.Mutex // one change at a time, in order last json.RawMessage refused string stopped bool } func (w *provisionWatch) hand(change ProvisionChange) error { raw, err := json.Marshal(change) if err != nil { return err } return w.deliver(raw) } // again asks once more after the client said something moved, and hands the bundle what changed. func (w *provisionWatch) again() { w.mu.Lock() defer w.mu.Unlock() if w.stopped { return } value, err := w.bus.all.conn.AskProvisioner(w.bus.module, w.asked.Kind, w.asked.Key) change := ProvisionChange{Kind: w.asked.Kind, Key: w.asked.Key} var refused *bus.ProvisionRefused switch { case errors.As(err, &refused): if w.refused == refused.Reason { return } change.Refused = refused.Reason case err != nil: // The client not reached: asked again on its next word, which it says whenever it comes up. w.bus.all.logf("[mesh-tools] %s's watch of its %s %s could not ask again: %v", w.bus.module, w.asked.Kind, w.asked.Key, err) return default: if w.refused == "" && bytes.Equal(value, w.last) { return } change.Value = value } if err := w.hand(change); err != nil { w.bus.all.logf("[mesh-tools] %s did not take a change of its %s %s: %v", w.bus.module, w.asked.Kind, w.asked.Key, err) return } w.last, w.refused = value, change.Refused } // provisionWatches is every watch on this runtime, by module, and the one subscription that hears the client. type provisionWatches struct { mu sync.Mutex of map[string]map[*provisionWatch]bool stop func() } func (p *provisionWatches) add(all *consumers, module string, w *provisionWatch) (func(), error) { p.mu.Lock() defer p.mu.Unlock() if p.stop == nil { stop, err := all.conn.OnProvisionChanged(func(module string, changed bus.ProvisionChanged) { for _, w := range p.told(module, changed) { go w.again() } }) if err != nil { return nil, fmt.Errorf("the runtime cannot hear its machine's provisioner client: %w", err) } p.stop = stop } if p.of == nil { p.of = map[string]map[*provisionWatch]bool{} } if p.of[module] == nil { p.of[module] = map[*provisionWatch]bool{} } p.of[module][w] = true return func() { w.mu.Lock() w.stopped = true w.mu.Unlock() p.mu.Lock() defer p.mu.Unlock() delete(p.of[module], w) }, nil } // told is the watches of module that what moved concerns. func (p *provisionWatches) told(module string, changed bus.ProvisionChanged) []*provisionWatch { p.mu.Lock() defer p.mu.Unlock() var out []*provisionWatch for w := range p.of[module] { if w.asked.Kind == changed.Kind && (changed.Key == "" || changed.Key == w.asked.Key) { out = append(out, w) } } return out } func (p *provisionWatches) stopAll() { p.mu.Lock() defer p.mu.Unlock() for _, ws := range p.of { for w := range ws { w.mu.Lock() w.stopped = true w.mu.Unlock() } } p.of = nil if p.stop != nil { p.stop() p.stop = nil } }