mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
One commit with exactly the tree of the reviewed head35b4509c0a, on main12c2574c9d, so two compiled programs added and removed on the branch leave no trace in history (issue 1577).
156 lines
7.5 KiB
Go
156 lines
7.5 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 seat's: where a module runs, its directories, accesses, ports, seats and the machine's facts (ADR
|
|
// 0349 clause 6), in the provisioning plan this seat composes of each module on each node from the bus, its state
|
|
// `plans` (novox/hq ADR 0570 clauses 1, 2): a client never reads the node's declaration, which the node-engine keeps for
|
|
// root alone, nor the controller's assignments.
|
|
func placement(kind, what string) Answer {
|
|
return Answer{Kind: kind, Domain: Module, From: FromBucket, Bucket: Module + "_" + PlansState,
|
|
Key: "<node>.<module>",
|
|
Why: what + ": placement, in the provisioning plan the seat mesh-provisioner composes of the module on the node " +
|
|
"(plans, plan.placement)", Decision: "novox/hq ADR 0570 clause 1"}
|
|
}
|
|
|
|
// 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)
|
|
}
|