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.
151 lines
4.5 KiB
Go
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
|
|
}
|