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 `:` 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) } }