Files
mesh-catalog/modules/mesh-config/cmd/mesh-config/list.go
T
jschoubben 224a7a66a5 mesh-config: hold a value for the open pull request that introduces its key (issue 1826)
A key a pull request adds is declared by no registered manifest, so its value was refused and the
group's composed check could not pass. A proposal may now name the pull request: judged against what
the controller told of its head, asked of the operator, kept apart on Approve, applied at the merge,
dropped at a close.
2026-10-22 10:58:28 +02:00

151 lines
4.5 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"`
// 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
}
if call.Module != "" {
if _, ok := s.Declared[call.Module]; !ok {
return nil, fmt.Errorf("%s is not a registered module, so it declares no config", call.Module)
}
}
out := &Listed{Resolved: []Resolved{}, Set: []SetValue{}, Withheld: withheld}
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
}