157 lines
7.6 KiB
Go
157 lines
7.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"slices"
|
|
)
|
|
|
|
// The registry: which domain answers which kind of placeholder (novox/hq ADR 0349 clauses 1 and 3). A kind is the
|
|
// namespace of a placeholder, `${<kind>:…}`; its answer says which module owns the values and where a node's
|
|
// provisioner client finds them for its node. Compiled here and written to the bucket mesh-provisioner_registry,
|
|
// which this module alone writes and every client reads: a domain that comes to answer a kind is a change to this
|
|
// table, through the design that moves it (design 57).
|
|
|
|
// The buckets this module keeps (its manifest's `state`), by local name.
|
|
const (
|
|
Registry = "registry"
|
|
Needs = "needs"
|
|
Applied = "applied"
|
|
Collect = "collect"
|
|
)
|
|
|
|
// Where a client finds a kind's values.
|
|
const (
|
|
// FromBucket is a domain's bucket, watched for the node's keys: a watch delivers the current values first.
|
|
FromBucket = "bucket"
|
|
// FromAsk is a seat's verb asked on request and reply: a value never kept in a bucket (ADR 0201 §13).
|
|
FromAsk = "ask"
|
|
)
|
|
|
|
// Answer is one kind's entry in the registry.
|
|
type Answer struct {
|
|
// Kind is the placeholder's namespace.
|
|
Kind string `json:"kind"`
|
|
// Domain is the module that owns the kind's values; for bound values, the module providing the provision
|
|
// the placeholder names, which is not one module but whichever the mesh resolved.
|
|
Domain string `json:"domain"`
|
|
// From is where a client finds the values: FromBucket or FromAsk.
|
|
From string `json:"from"`
|
|
// Bucket and Key, for a value in a bucket: the bucket, and the shape of a key in it, the node first.
|
|
Bucket string `json:"bucket,omitempty"`
|
|
Key string `json:"key,omitempty"`
|
|
// Ask, for a value asked for: the seat's verb, `<seat>.<verb>`.
|
|
Ask string `json:"ask,omitempty"`
|
|
// Why says the answer in a sentence, and Decision the record that decided it.
|
|
Why string `json:"why"`
|
|
Decision string `json:"decision"`
|
|
}
|
|
|
|
// placement is the controller's: where a module runs, its directories, accesses, ports, seats and the machine's
|
|
// facts (ADR 0349 clause 6). On the bus, in the provisioner's plan of each module the controller handed to it, which
|
|
// its assignment carries (mesh-controller internal/inventory Provisioning, issue 671), or for a module only pulled in
|
|
// by another, its value of its own under the same key (issue 765): a client never reads the node's declaration,
|
|
// which the node-engine keeps for root alone.
|
|
func placement(kind, what string) Answer {
|
|
return Answer{Kind: kind, Domain: "mesh-controller", From: FromBucket, Bucket: "mesh-controller_assignments",
|
|
Key: "<node>.<module>",
|
|
Why: what + ": placement, the controller's, in the provisioner's plan its assignment carries (provisioning." +
|
|
"placement)", Decision: "novox/hq ADR 0349 clause 6"}
|
|
}
|
|
|
|
// oldCollection is a namespace read as a collection of its own name (novox/hq ADR 0349 clause 13, design 57 L1).
|
|
func oldCollection(kind, what string) Answer {
|
|
return Answer{Kind: kind, Domain: "mesh-provisioner", From: FromBucket, Bucket: "mesh-provisioner_collect",
|
|
Key: "<node>.<collection>.<contributor>",
|
|
Why: what + ": read as the collection `" + kind + ":<name>`, its items recorded by the client of the node " +
|
|
"its contributors are placed on", Decision: "novox/hq ADR 0349 clause 13"}
|
|
}
|
|
|
|
// TheRegistry is every kind a moved module's template may name, and its answer, in the order of ADR 0349 clause 2.
|
|
var TheRegistry = []Answer{
|
|
{Kind: "config", Domain: "mesh-config", From: FromBucket, Bucket: "mesh-config_resolved", Key: "<node>.<module>.<key>",
|
|
Why: "a config value, resolved for each module on each node by mesh-config", Decision: "novox/hq ADR 0348"},
|
|
{Kind: "secret", Domain: "mesh-vault", From: FromAsk, Ask: "mesh-vault.get",
|
|
Why: "a shared secret, handed by the vault sealed to the client's key, on request and reply only",
|
|
Decision: "novox/hq ADR 0349 clause 11"},
|
|
// The provider's bucket and its keys, as the providers' helper writes them (mesh-sdk go/provider, issue 678): the
|
|
// bucket of the module the consumer's plan names as its provider, and the consumer's machine first.
|
|
{Kind: "bound", Domain: "provider", From: FromBucket, Bucket: "<provider-module>_bound",
|
|
Key: "<node>.<module>.<provision>[.<local>]",
|
|
Why: "a consumer's values, written by the module providing the provision to its own bucket, one entry per " +
|
|
"consumer, the bucket of the provider module the consumer's plan names", Decision: "novox/hq ADR 0349 clause 12"},
|
|
// A provider's consumers' credentials, each consumer's pair credential, which the provider machine's client asks
|
|
// the vault for and fills into a file the provider alone reads (issue 679).
|
|
{Kind: "consumers", Domain: "mesh-vault", From: FromAsk, Ask: "mesh-vault.get",
|
|
Why: "each bound consumer's credential, asked of the vault by the provider machine's client and filled into a " +
|
|
"file only the provider reads, one entry per consumer", Decision: "novox/hq ADR 0373"},
|
|
{Kind: "collected", Domain: "mesh-provisioner", From: FromBucket, Bucket: "mesh-provisioner_collect",
|
|
Key: "<node>.<collection>.<contributor>",
|
|
Why: "the items of a collection, recorded by the client of the node its contributors are placed on",
|
|
Decision: "novox/hq ADR 0349 clause 13"},
|
|
// The three namespaces the controller fills for a module not handed to the provisioner, read in a handed
|
|
// collector's template as the collection of the same name (design 57 L1): `${shell:zsh:normal}` is the
|
|
// collection `shell:zsh:normal`.
|
|
oldCollection("shell", "a shell's code in one slot, or a session file's or a power moment's"),
|
|
oldCollection("environment", "the account's environment in one format"),
|
|
oldCollection("contribution", "what a seat's holder receives of one kind"),
|
|
placement("dir", "a module's directory"),
|
|
placement("access", "a directory a module reaches of another's"),
|
|
placement("port", "a module's port"),
|
|
placement("seat", "where a seat's holder is reached"),
|
|
placement("machine", "a fact of the machine"),
|
|
placement("backup", "what the machine's modules back up: `lines` and `data`, as the controller derives them from each module's declared data"),
|
|
}
|
|
|
|
// Publish brings the registry bucket to TheRegistry: a kind whose answer differs is written, a kind no longer in
|
|
// the table is deleted, and a pass that finds them equal writes nothing. It answers the kinds it wrote and
|
|
// deleted.
|
|
func Publish(s Store) (written, deleted []string, err error) {
|
|
known := map[string]bool{}
|
|
for _, a := range TheRegistry {
|
|
known[a.Kind] = true
|
|
want, err := json.Marshal(a)
|
|
if err != nil {
|
|
return written, deleted, err
|
|
}
|
|
have, err := s.Get(Registry, a.Kind)
|
|
if err != nil {
|
|
return written, deleted, fmt.Errorf("reading the registry's %s: %w", a.Kind, err)
|
|
}
|
|
if have != nil && sameJSON(have.Value, want) {
|
|
continue
|
|
}
|
|
if err := s.Put(Registry, a.Kind, a); err != nil {
|
|
return written, deleted, fmt.Errorf("writing the registry's %s: %w", a.Kind, err)
|
|
}
|
|
written = append(written, a.Kind)
|
|
}
|
|
keys, err := s.Keys(Registry)
|
|
if err != nil {
|
|
return written, deleted, fmt.Errorf("listing the registry: %w", err)
|
|
}
|
|
for _, k := range keys {
|
|
if known[k] {
|
|
continue
|
|
}
|
|
if err := s.Delete(Registry, k); err != nil {
|
|
return written, deleted, fmt.Errorf("deleting the registry's %s, a kind no domain answers: %w", k, err)
|
|
}
|
|
deleted = append(deleted, k)
|
|
}
|
|
slices.Sort(deleted)
|
|
return written, deleted, nil
|
|
}
|
|
|
|
// sameJSON says two JSON texts hold the same value, whatever their spacing or the order of their keys.
|
|
func sameJSON(a, b []byte) bool {
|
|
var x, y any
|
|
if json.Unmarshal(a, &x) != nil || json.Unmarshal(b, &y) != nil {
|
|
return bytes.Equal(a, b)
|
|
}
|
|
ja, _ := json.Marshal(x)
|
|
jb, _ := json.Marshal(y)
|
|
return bytes.Equal(ja, jb)
|
|
}
|