A setting reaches only what declares it, the mesh places its own files, registration refuses a name
Three of novox/hq's group-4 leftovers, one branch.
Issue 173: a module's settings reached every route it contributed, every database it asked for and
every served fact its consumers read — a mail server's site name arrived at the proxy as a route
fact. A setting now overrides a key a contribution or served fact declares and adds none; a file
still merges any key, and a key nothing takes is named as stray instead of dropped silently.
Issue 174: the mesh's own files for a module — its bus credential, its merged config, its bindings —
were placed by the definition under /var/lib/mesh/<module>, 232 host paths in 50 definitions. A
directory may now say `place: "mesh"` and resolves to <root>/mesh/<module>; a directory beneath a
placed one may state its path as `${dir:<id>}/<rest>` and moves with it. The proof test resolves
both catalogues and compares: 48 definitions, no path moved. The controller's own manifest is
converted here; the catalogue in mesh-catalog.
ADR 0155: the installation check moves to registration. `module add` and a build's result both
refuse a definition that names an installation, in the check's words, with the way out; the build
stays recorded.
This commit is contained in:
@@ -85,17 +85,41 @@ func ApplySettings(resource map[string]any, layers []Layer) (map[string]any, err
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Settle lays settings over a module's own values. Exported for what a provider serves, which is
|
||||
// settled where the mesh is walked rather than where a node is declared.
|
||||
// Settle lays settings over what a provider serves. Exported because a served fact is settled where
|
||||
// the mesh is walked rather than where a node is declared.
|
||||
//
|
||||
// **A setting overrides a served key; it never adds one** (novox/hq 04-ISSUES/173). What a consumer
|
||||
// is told is the provider's contract, and a setting made for one of the provider's files — a site
|
||||
// name, a public address — is not part of it. Before this, every setting of a module reached every
|
||||
// consumer of every provision it served.
|
||||
func Settle(base map[string]any, layers []Layer) (map[string]any, error) {
|
||||
return settle(base, layers, nil, "what is served")
|
||||
return overridden(base, layers, "what is served")
|
||||
}
|
||||
|
||||
// overridden lays settings over a map whose keys are its contract: a contribution, a served fact.
|
||||
// Only the keys the map already declares are touched; the rest of a layer is somebody else's
|
||||
// business (a file's, another destination's) and is left to reach it there.
|
||||
func overridden(base map[string]any, layers []Layer, what string) (map[string]any, error) {
|
||||
kept := make([]Layer, 0, len(layers))
|
||||
for _, layer := range layers {
|
||||
values := map[string]any{}
|
||||
for key, value := range layer.Values {
|
||||
if _, declared := base[key]; declared {
|
||||
values[key] = value
|
||||
}
|
||||
}
|
||||
kept = append(kept, Layer{From: layer.From, Values: values})
|
||||
}
|
||||
return settle(base, kept, nil, what)
|
||||
}
|
||||
|
||||
// settle lays the layers over a module's own values, in order.
|
||||
//
|
||||
// Shared by a file's content and a module's contributions, because they are the same act: the
|
||||
// module says what it means by default, and somebody says what it means here. A contribution that
|
||||
// could not be settled would have to be edited to be reused anywhere else.
|
||||
// could not be settled would have to be edited to be reused anywhere else. The two differ in one
|
||||
// respect, and the caller decides it: a file takes keys it did not declare (a setting may add to a
|
||||
// configuration), a contribution or served fact does not (overridden).
|
||||
func settle(base map[string]any, layers []Layer, protected map[string]bool, what string) (
|
||||
map[string]any, error) {
|
||||
merged := deepCopy(base)
|
||||
@@ -149,23 +173,22 @@ func deepCopy(in map[string]any) map[string]any {
|
||||
return out
|
||||
}
|
||||
|
||||
// UnusedSettings names settings that reached no file.
|
||||
// UnusedSettings names settings that reach nothing.
|
||||
//
|
||||
// Somebody who sets a key on a module with nothing mergeable, or misspells one, has changed
|
||||
// nothing — and would find out by the machine not behaving differently, which is the slowest
|
||||
// way there is. This is what makes that visible at the moment they set it.
|
||||
//
|
||||
// Where a key can land: any mergeable file takes any key; a file asking for `${setting:<key>}`
|
||||
// takes that key (ADR 0155); a contribution or a served fact takes a key it declares, and no other
|
||||
// (novox/hq 04-ISSUES/173); and the mesh's own words — `expose`, `ports`, `reach`, `endpoints` —
|
||||
// are read by the mesh. A key none of those takes is stray, and is said so rather than dropped.
|
||||
func UnusedSettings(m Manifest, layers []Layer) []string {
|
||||
for _, r := range m.Resources {
|
||||
if how, _ := r["merge"].(string); how != "" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// A contribution is a destination too. A route's hostname is exactly the kind of thing that
|
||||
// differs between one mesh and the next, and calling it stray would refuse the one setting
|
||||
// most modules that publish anything will have.
|
||||
if len(m.Contributes) > 0 {
|
||||
return nil
|
||||
}
|
||||
// A computed module has no resources here to look at — they are worked out per node, and
|
||||
// whether a setting lands is not knowable until then. Silence rather than a wrong answer:
|
||||
// claiming every setting on the private network is stray would be worse than saying nothing.
|
||||
@@ -173,12 +196,28 @@ func UnusedSettings(m Manifest, layers []Layer) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// A key a file's content asks for with ${setting:<key>} is a destination too (ADR 0155).
|
||||
asked := settingKeysUsedBy(m)
|
||||
lands := settingKeysUsedBy(m)
|
||||
for _, values := range m.Contributes {
|
||||
for key := range values {
|
||||
lands[key] = true
|
||||
}
|
||||
}
|
||||
for _, locals := range m.ContributesMany {
|
||||
for _, values := range locals {
|
||||
for key := range values {
|
||||
lands[key] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, served := range m.Serves {
|
||||
for key := range served {
|
||||
lands[key] = true
|
||||
}
|
||||
}
|
||||
var unused []string
|
||||
for _, layer := range layers {
|
||||
for key := range layer.Values {
|
||||
if asked[key] {
|
||||
if lands[key] {
|
||||
continue
|
||||
}
|
||||
// `expose` is a real destination for a module that listens: it overrides a port's
|
||||
@@ -203,8 +242,9 @@ func UnusedSettings(m Manifest, layers []Layer) []string {
|
||||
continue
|
||||
}
|
||||
unused = append(unused, fmt.Sprintf(
|
||||
"%s sets %q, and %s has no file or contribution to merge it into",
|
||||
layer.From, key, m.Module))
|
||||
"%s sets %q, and %s has no file that merges it, asks for no ${setting:%s}, and "+
|
||||
"declares no %q in what it contributes or serves",
|
||||
layer.From, key, m.Module, key, key))
|
||||
}
|
||||
}
|
||||
sort.Strings(unused)
|
||||
|
||||
Reference in New Issue
Block a user