mesh/merge-gate head d3e182503c19 merged into trunk c8b07242a009 — pass: [release feat/57-config-and-provisioning, judged by its controller fcdecb436117] t…
mesh/repo-check head d3e182503c19 merged into trunk c8b07242a009 — pass: [release feat/57-config-and-provisioning, judged by its controller fcdecb436117] i…
mesh/delivery delivered
Providers, the state bus, templates and the environment collection that the feature's modules build on (feature 57, config and provisioning).
91 lines
3.5 KiB
Go
91 lines
3.5 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, design 57 S1): 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
|
|
}
|