91 lines
3.6 KiB
Go
91 lines
3.6 KiB
Go
package stdio
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"time"
|
|
)
|
|
|
|
// A module's code reads its values at runtime (novox/hq ADR 0349 clause 14, ADR 0370, and the part of the feature 57 design that lets a running module read, rather than be handed in a file, its provisioned values): its config
|
|
// values and its bindings' values that are not secret, for what its manifest declares, and watches them. The
|
|
// runtime that launched this bundle asks its machine's provisioner client **as this bundle's module** — the module it
|
|
// launched, never a name this code passes — and the client answers only what that module declares, only while the
|
|
// mesh places it on that machine. A secret is refused 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 there, so a module's
|
|
// secrets reach it through its files until it runs under a bus account of its own (novox/hq issue 569).
|
|
|
|
// ProvisionChange is what a watch hands over: the value now, or — once it is no longer answered, the module taken
|
|
// off the machine or the key no longer declared — Refused, why.
|
|
type ProvisionChange struct {
|
|
Kind string `json:"kind"`
|
|
Key string `json:"key"`
|
|
Value json.RawMessage `json:"value,omitempty"`
|
|
Refused string `json:"refused,omitempty"`
|
|
}
|
|
|
|
const provisionTimeout = 30 * time.Second
|
|
|
|
func provisionGet(kind, key string) (json.RawMessage, error) {
|
|
c, err := live()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c.ask("mesh/provision.get", map[string]any{"kind": kind, "key": key}, provisionTimeout)
|
|
}
|
|
|
|
func provisionWatch(kind, key string, handle func(ProvisionChange) error) error {
|
|
if handle == nil {
|
|
return errors.New("a watch needs a handler")
|
|
}
|
|
c, err := live()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id := c.newID()
|
|
c.mu.Lock()
|
|
c.provisioned[id] = handle
|
|
c.mu.Unlock()
|
|
if _, err := c.askAs(id, "mesh/provision.watch", map[string]any{"kind": kind, "key": key}, provisionTimeout); err != nil {
|
|
c.mu.Lock()
|
|
delete(c.provisioned, id)
|
|
c.mu.Unlock()
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Config is one of this module's config values as mesh-config resolved it for this machine, as JSON: the key as
|
|
// its manifest declares it under `config`. A key it does not declare is refused, with the reason.
|
|
func Config(key string) (json.RawMessage, error) { return provisionGet("config", key) }
|
|
|
|
// WatchConfig hands handle the config value now and then every change, in order, and returns once the value now is
|
|
// handled. The watch lasts as long as the process.
|
|
func WatchConfig(key string, handle func(ProvisionChange) error) error {
|
|
return provisionWatch("config", key, handle)
|
|
}
|
|
|
|
// Bound is one value, not a secret, of what this module is bound to: the provision as its manifest requires it,
|
|
// and the key its provider answers (`${bound:<provision>:<key>}`).
|
|
func Bound(provision, key string) (json.RawMessage, error) {
|
|
return provisionGet("bound", provision+":"+key)
|
|
}
|
|
|
|
// WatchBound is WatchConfig for a bound value.
|
|
func WatchBound(provision, key string, handle func(ProvisionChange) error) error {
|
|
return provisionWatch("bound", provision+":"+key, handle)
|
|
}
|
|
|
|
// Secret is one of this module's secrets. Refused, with the reason, while the module's code runs in the tool
|
|
// runner (novox/hq ADR 0349 §11, ADR 0370 §2): its secrets reach it through its files.
|
|
func Secret(name string) ([]byte, error) {
|
|
raw, err := provisionGet("secret", name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var s string
|
|
if err := json.Unmarshal(raw, &s); err != nil {
|
|
return nil, errors.New("the runtime answered a secret that is not a string")
|
|
}
|
|
return []byte(s), nil
|
|
}
|