A strict read of the stored catalogue fails every plan and send once a manifest uses a field an older controller lacks; registration stays strict. A node named default lost its layer to the name check. Judge the operator's keys by whole words, and scan a default under any key.
196 lines
7.4 KiB
Go
196 lines
7.4 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// A preference has a default; the operator's own value has none (novox/hq ADR 0262, extending ADR
|
|
// 0112 and taking the default half of ADR 0164).
|
|
//
|
|
// `${setting:<key>}` was refused whenever no layer set the key, and `settings set` refused a key no
|
|
// file asked for yet. Together they meant a running module could never gain a setting: the file that
|
|
// asks for it fails to compose until somebody sets it, and nobody can set it until the file asks.
|
|
// A definition may now give a key a default, with why, when the value is a **preference** — a font
|
|
// size, a width, a number of workers: something true of the software that a machine may tune. A
|
|
// value that is inherently the operator's — a domain, a public name, an identity, a secret — gets no
|
|
// default and stays refused by name until a layer sets it, which is what ADR 0112 and ADR 0155 were
|
|
// written for.
|
|
//
|
|
// The default is the lowest layer. The mesh's layer, then the node's, override it. It fills only
|
|
// `${setting:<key>}`: a mergeable file's content already is its defaults, and laying a default over
|
|
// it would reach every mergeable file of the module (novox/hq issue 168).
|
|
|
|
// SettingDeclaration is one key's default, as the definition gives it.
|
|
type SettingDeclaration struct {
|
|
// Kind says what sort of value this is. `preference` is the only kind with a default; it is
|
|
// stated rather than assumed so a reviewer sees the claim being made.
|
|
Kind string `json:"kind"`
|
|
// Default is the value when no layer sets the key: a string, a number or a boolean.
|
|
Default any `json:"default"`
|
|
// Why this default: one sentence for whoever wonders whether to change it.
|
|
Why string `json:"why"`
|
|
}
|
|
|
|
// KindPreference is the kind of a setting that may have a default.
|
|
const KindPreference = "preference"
|
|
|
|
// DefaultLayer is what the layer of a module's own defaults is called where a value's source is said.
|
|
// Only said: the layer is recognised by Layer.Default, never by this name, which a node may also have.
|
|
const DefaultLayer = "default"
|
|
|
|
// meshWords are the settings keys the mesh reads itself; a module declares none of them.
|
|
var meshWords = map[string]bool{
|
|
PortsSetting: true, ExposeSetting: true, ReachSetting: true, EndpointsSetting: true,
|
|
PlacesSetting: true, AccessesSetting: true, NetworksSetting: true,
|
|
}
|
|
|
|
// operatorsOwn are words that, as a whole word of a key's name, say its value is the operator's and
|
|
// never a preference: a default for one would be the literal ADR 0112 removed from definitions. Whole
|
|
// words, split at dashes, underscores and dots, so `max-tokens`, `show-hostname` and `mailbox-size` are
|
|
// preferences and `api-token`, `host` and `mail-domain` are not.
|
|
var operatorsOwn = map[string]bool{
|
|
"domain": true, "host": true, "fqdn": true, "zone": true, "realm": true, "tenant": true,
|
|
"issuer": true, "url": true, "uri": true, "webhook": true, "origin": true, "dsn": true, "ip": true,
|
|
"email": true, "mail": true, "phone": true, "address": true,
|
|
"identity": true, "login": true, "user": true, "username": true, "account": true, "owner": true,
|
|
"uid": true, "gid": true, "puid": true, "pgid": true,
|
|
"password": true, "pass": true, "passwd": true, "passphrase": true, "secret": true, "token": true,
|
|
"key": true, "credential": true,
|
|
}
|
|
|
|
// operatorsWord is the word of a key's name that says its value is the operator's, or "".
|
|
func operatorsWord(key string) string {
|
|
words := strings.FieldsFunc(key, func(r rune) bool { return r == '-' || r == '_' || r == '.' })
|
|
for i, w := range words {
|
|
if operatorsOwn[w] {
|
|
return w
|
|
}
|
|
// An identifier a client is known by: `client-id`, `oauth-client-id`.
|
|
if w == "client" && i+1 < len(words) && words[i+1] == "id" {
|
|
return "client-id"
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// SettingProblems is every way a definition's setting defaults are wrong, in its own words.
|
|
func SettingProblems(m Manifest) []string {
|
|
if len(m.Settings) == 0 {
|
|
return nil
|
|
}
|
|
used := settingKeysUsedBy(m)
|
|
var problems []string
|
|
for _, key := range sortedSettingKeys(m.Settings) {
|
|
d := m.Settings[key]
|
|
say := func(format string, args ...any) {
|
|
problems = append(problems, fmt.Sprintf("%s's setting %q: ", m.Module, key)+fmt.Sprintf(format, args...))
|
|
}
|
|
if !settingRef.MatchString("${setting:" + key + "}") {
|
|
say("not a usable key: lower-case letters, digits, dots, dashes and underscores")
|
|
continue
|
|
}
|
|
if meshWords[key] {
|
|
say("the mesh reads %q itself, and a module gives it no default", key)
|
|
continue
|
|
}
|
|
if d.Kind != KindPreference {
|
|
say("kind is %q, and only a %q has a default (novox/hq ADR 0262)", d.Kind, KindPreference)
|
|
}
|
|
if word := operatorsWord(key); word != "" {
|
|
say("a key naming %q is the operator's value, and has no default — it is the "+
|
|
"assignment's, never the definition's (novox/hq ADR 0112, ADR 0262)", word)
|
|
}
|
|
switch v := d.Default.(type) {
|
|
case string:
|
|
if strings.TrimSpace(v) == "" {
|
|
say("an empty default is no default; give the value, or declare nothing")
|
|
}
|
|
case float64, bool:
|
|
case nil:
|
|
say("no default: a key without one is the operator's, and is not declared here")
|
|
default:
|
|
say("a default is a string, a number or a boolean, and this is %T", d.Default)
|
|
}
|
|
if strings.TrimSpace(d.Why) == "" {
|
|
say("no why: say in one sentence why this default")
|
|
}
|
|
if !used[key] {
|
|
say("nothing asks for ${setting:%s}, so the default reaches nothing", key)
|
|
}
|
|
}
|
|
return problems
|
|
}
|
|
|
|
// Defaults is the layer a module's own defaults make, or nothing when it gives none.
|
|
func Defaults(m Manifest) (Layer, bool) {
|
|
if len(m.Settings) == 0 {
|
|
return Layer{}, false
|
|
}
|
|
values := map[string]any{}
|
|
for key, d := range m.Settings {
|
|
if d.Default != nil {
|
|
values[key] = d.Default
|
|
}
|
|
}
|
|
return Layer{From: DefaultLayer, Values: values, Default: true}, len(values) > 0
|
|
}
|
|
|
|
// WithDefaults is a module's layers with its defaults under them, for filling `${setting:<key>}`.
|
|
func WithDefaults(m Manifest, layers []Layer) []Layer {
|
|
d, has := Defaults(m)
|
|
if !has {
|
|
return layers
|
|
}
|
|
return append([]Layer{d}, layers...)
|
|
}
|
|
|
|
// SettingSource is one key's effective value and the layer it came from.
|
|
type SettingSource struct {
|
|
Key string
|
|
Value any
|
|
// From is DefaultLayer, MeshWideLayer or the node's name.
|
|
From string
|
|
// FromDefault is whether the value is the module's default, whatever From reads.
|
|
FromDefault bool
|
|
// Default is the module's default, when it gives one.
|
|
Default any
|
|
HasDefault bool
|
|
}
|
|
|
|
// Effective is every key a module gives a default or a layer sets, with its value and where it came
|
|
// from: the default, then the mesh's layer, then the node's — later wins.
|
|
func Effective(m Manifest, layers []Layer) []SettingSource {
|
|
byKey := map[string]*SettingSource{}
|
|
for key, d := range m.Settings {
|
|
byKey[key] = &SettingSource{Key: key, Value: d.Default, From: DefaultLayer, FromDefault: true,
|
|
Default: d.Default, HasDefault: true}
|
|
}
|
|
for _, layer := range layers {
|
|
for key, v := range layer.Values {
|
|
s, ok := byKey[key]
|
|
if !ok {
|
|
s = &SettingSource{Key: key}
|
|
byKey[key] = s
|
|
}
|
|
s.Value, s.From, s.FromDefault = v, layer.From, layer.Default
|
|
}
|
|
}
|
|
out := make([]SettingSource, 0, len(byKey))
|
|
for _, s := range byKey {
|
|
out = append(out, *s)
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].Key < out[j].Key })
|
|
return out
|
|
}
|
|
|
|
func sortedSettingKeys(in map[string]SettingDeclaration) []string {
|
|
keys := make([]string, 0, len(in))
|
|
for k := range in {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
return keys
|
|
}
|