Files
mesh-catalog/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/asks.go
T

120 lines
4.9 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"github.com/nats-io/nats.go"
)
// A module's code asks at runtime (novox/hq ADR 0349 clause 14 as ADR 0370 changes it, design 57 S1, issue 685).
//
// While a module's code runs in its machine's tool runner, its asks reach the bus under the tool runner's account,
// and the bus proves only that account. So the client is asked on AskSubject, which the controller grants its own
// machine's tool runner alone to publish (ADR 0370 §3), and the module is the one the tool runner names on the ask,
// taken from the bundle that asks (§4). The client answers that module's config values, for what its manifest
// declares — mesh-config resolves a module's declared keys and no other — and only when the composition places the
// module on this machine, where the tool runner carries every module assigned to it. It never answers a secret on
// the tool runner's word (§2): the vault hands a secret only to a caller the bus proves (ADR 0349 clause 11), and a
// module in the tool runner reads its secrets from its files. A binding's non-secret values are its provider's
// (ADR 0349 clause 12); this client fills none yet, so it answers none yet either, and says so.
//
// A watch is the tool runner's: the client says on ChangedSubject which value of a module moved, never the value,
// and the tool runner asks again for the watches it holds.
// AskSubject is where this machine's tool runner asks (mesh-controller internal/broker ProvisionerAskSubject).
func AskSubject(node string) string { return "prov.ask." + node }
// ChangedSubject is where the client says a value of one module moved (mesh-controller internal/broker
// ProvisionerChangedSubject).
func ChangedSubject(node, module string) string { return "prov.changed." + node + "." + module }
// The kinds of value an ask names.
const (
AskConfig = "config"
AskBound = "bound"
AskSecret = "secret"
)
// Ask is what the tool runner asks: the module it names, from the bundle that asks; the kind; and the key, a
// config key, or `<provision>:<key>` of a binding.
type Ask struct {
Module string `json:"module"`
Kind string `json:"kind"`
Key string `json:"key"`
}
// Answered is the client's answer: the value as mesh-config resolved it (JSON), or why it is refused.
type Answered struct {
Value json.RawMessage `json:"value,omitempty"`
Error string `json:"error,omitempty"`
}
// Changed is what the client says moved of one module: a kind, and the key, or every key of the kind when empty.
type Changed struct {
Kind string `json:"kind"`
Key string `json:"key,omitempty"`
}
// answer is the answer to one ask from the state the client holds.
func (c *Client) answer(s *state, body []byte) Answered {
var a Ask
dec := json.NewDecoder(bytes.NewReader(body))
dec.DisallowUnknownFields()
if err := dec.Decode(&a); err != nil || a.Module == "" || a.Kind == "" || a.Key == "" {
return Answered{Error: "an ask names its module, its kind and its key: {module, kind, key}"}
}
switch a.Kind {
case AskSecret:
return Answered{Error: fmt.Sprintf("%s's secret %s is not answered on the tool runner's word: the vault hands "+
"a secret only to a caller the bus proves (novox/hq ADR 0370 §2, ADR 0349 §11); while the module's code "+
"runs in the tool runner, its secrets reach it through its files", a.Module, a.Key)}
case AskConfig, AskBound:
default:
return Answered{Error: fmt.Sprintf("%q is not a kind the client answers: config or bound", a.Kind)}
}
if !s.assigned[a.Module] {
return Answered{Error: fmt.Sprintf("%s is not placed on %s, so this machine's tool runner carries no code of "+
"it to answer (novox/hq ADR 0370 §3)", a.Module, c.Node)}
}
if a.Kind == AskBound {
return Answered{Error: fmt.Sprintf("%s's bound value %s is not answered yet: a binding's values come from its "+
"provider (novox/hq ADR 0349 §12), which this client fills into the module's files (issue 679) and does not "+
"answer on an ask yet", a.Module, a.Key)}
}
v, ok := s.config[a.Module][a.Key]
if !ok {
return Answered{Error: fmt.Sprintf("%s declares no config value %s, or mesh-config holds none for it on %s",
a.Module, a.Key, c.Node)}
}
return Answered{Value: v}
}
// respond answers one ask on its reply.
func (c *Client) respond(s *state, m *nats.Msg) {
if m.Reply == "" {
return
}
a := c.answer(s, m.Data)
body, err := json.Marshal(a)
if err != nil {
return
}
if err := m.Respond(body); err != nil {
logf("[provisioner-client] an ask could not be answered: %v", err)
}
}
// tell says a value of a module moved, so its code's watches are asked again.
func (c *Client) tell(module string, ch Changed) {
if module == "" || strings.ContainsAny(module, "*> ") {
return
}
body, _ := json.Marshal(ch)
if err := c.Conn.Publish(ChangedSubject(c.Node, module), body); err != nil {
logf("[provisioner-client] what moved of %s could not be said: %v", module, err)
}
}