Files
mesh-catalog/modules/mesh-config/cmd/mesh-config/list.go
T
jschoubben 96a37f58ca
mesh/delivery closed: the pull request closed unmerged
mesh/check pass 96a37f58ca76 on 030f9ee4e780 | unit 1173 run, 3272 kept, 13 skipped | integration 1212 run, 3274 kept, 22 skipped | over bound: unit, …
mesh-config: read declarations from each placed module's own manifests as built, and placement alone, so a module from its own repository resolves (issue 1953)
The controller's declared-config mirror never held de-spiegel's keys, and Resolve skipped
an assigned module it held no declarations of, silently: de-spiegel stayed held on novox
with its values set. Declarations now come from the indexer's trunk manifest with the
wanted build's laid over it per node; which module runs where from the seat
mesh-placement alone. A placed module with no readable manifest is listed as unresolved
with why, logged, and keeps its last resolved values.
2026-10-29 15:19:24 +01:00

159 lines
4.9 KiB
Go

package main
import (
"encoding/json"
"fmt"
)
// Listing the values and reading their history, narrowed by module and node (ADR 0348 §1).
// Listed is what a listing answers.
type Listed struct {
// Resolved is each module's value on each node the call narrows to, with where it came from.
Resolved []Resolved `json:"resolved"`
// Set is every value set at a level the call reaches, with who set it and why.
Set []SetValue `json:"set"`
// Predefined is the published list of predefined keys, for a call that names no module.
Predefined map[string]Published `json:"predefined,omitempty"`
// Withheld is why the newest list of predefined keys is not published, when it is not.
Withheld string `json:"withheld,omitempty"`
// Unresolved is each placed module the call narrows to whose declarations cannot be read, with why (issue 1953).
Unresolved []Unresolved `json:"unresolved,omitempty"`
// Held is the approved values held for open pull requests, not yet set (novox/hq ADR 0502).
Held []HeldValue `json:"held,omitempty"`
}
// SetValue is one value set, at its level.
type SetValue struct {
Level Level `json:"level"`
Key string `json:"key"`
Value
}
// List answers the resolved values and the values set, narrowed by module, node and key.
func (c *Config) List(call Call) (*Listed, error) {
for _, name := range []string{call.Node, call.Module} {
if name != "" && !nodeOrModule.MatchString(name) {
return nil, fmt.Errorf("%q is not a node's or a module's name", name)
}
}
c.mu.Lock()
withheld := c.withheld
c.mu.Unlock()
s, err := c.sources()
if err != nil {
return nil, err
}
out := &Listed{Resolved: []Resolved{}, Set: []SetValue{}, Withheld: withheld}
for _, u := range s.Unresolved {
if (call.Module == "" || u.Module == call.Module) && (call.Node == "" || u.Node == call.Node) {
out.Unresolved = append(out.Unresolved, u)
}
}
if call.Module != "" {
if _, ok := s.Declared[call.Module]; !ok && len(out.Unresolved) == 0 {
return nil, fmt.Errorf("%s is placed on no node with a manifest the indexer holds, so it declares no "+
"config", call.Module)
}
}
resolved := Resolve(s)
for _, k := range sortedKeys(resolved) {
r := resolved[k]
if (call.Module == "" || r.Module == call.Module) && (call.Node == "" || r.Node == call.Node) &&
(call.Key == "" || r.Key == call.Key || r.Predefined == call.Key) {
out.Resolved = append(out.Resolved, r)
}
}
// The predefined keys the narrowed modules fall back to: their mesh and node values are listed too.
fallsBack := map[string]bool{}
for _, r := range out.Resolved {
if r.Predefined != "" {
fallsBack[r.Predefined] = true
}
}
for _, k := range sortedKeys(s.Values) {
level, key, ok := parseValueKey(k)
if !ok {
continue
}
if call.Key != "" && key != call.Key {
continue
}
reached := false
switch level.Kind {
case LevelMesh:
reached = call.Module == "" || fallsBack[key]
case LevelNode:
reached = (call.Node == "" || level.Node == call.Node) && (call.Module == "" || fallsBack[key])
case LevelModule:
reached = (call.Node == "" || level.Node == call.Node) && (call.Module == "" || level.Module == call.Module)
}
if reached {
out.Set = append(out.Set, SetValue{Level: level, Key: key, Value: s.Values[k]})
}
}
if call.Module == "" {
out.Predefined = s.Predefined
}
out.Held = heldListed(s, call)
if len(out.Held) == 0 {
out.Held = nil
}
return out, nil
}
// HistoryEntry is one change as the history keeps it.
type HistoryEntry struct {
Key string `json:"key"`
Change
}
// History is the changes the call narrows to, newest last, at most its limit (100 when it gives none).
func (c *Config) History(call Call) ([]HistoryEntry, error) {
entries, err := c.Store.Read(HistoryLog)
if err != nil {
return nil, fmt.Errorf("the history cannot be read: %w", err)
}
limit := call.Limit
if limit <= 0 {
limit = 100
}
out := []HistoryEntry{}
for _, e := range entries {
var ch Change
if err := json.Unmarshal(e.Value, &ch); err != nil {
continue
}
if !reaches(call, e.Key, ch) {
continue
}
out = append(out, HistoryEntry{Key: e.Key, Change: ch})
}
if len(out) > limit {
out = out[len(out)-limit:]
}
return out, nil
}
// reaches is whether a change concerns what the call narrows to: its level, or a value it moved.
func reaches(call Call, key string, ch Change) bool {
if call.Module == "" && call.Node == "" && call.Key == "" {
return true
}
if level, k, ok := parseValueKey(key); ok {
if (call.Key == "" || k == call.Key) && (call.Node == "" || level.Node == call.Node) &&
(call.Module == "" || level.Module == call.Module) {
return true
}
}
// A change at another level reaches the call through what it moved there: mesh config moving a module's
// value on the node named.
for _, m := range ch.Moved {
if (call.Module == "" || m.Module == call.Module) && (call.Node == "" || m.Node == call.Node) &&
(call.Key == "" || m.Key == call.Key || ch.Key == call.Key) {
return true
}
}
return false
}