package main import ( "encoding/json" "fmt" "sort" "strings" "time" "git.novox.be/novox/mesh-sdk/go/delivery" ) // The four sources (novox/hq ADR 0348 §2, §3, §9): a module's value on a node resolves, the most specific winning, // from the default in its manifest, mesh config for the predefined key it falls back to, node config for that key // on the node, and the module's own value on that node. A value naming no predefined key resolves from its default // and the module's own value only. // The states this module keeps, and those it reads (its manifest's `state` and `reads`). const ( ValuesState = "values" ResolvedState = "resolved" PredefinedState = "predefined" ProposalsState = "proposals" // PlacementRead is the seat mesh-placement's state, held by the assigner (novox/hq ADR 0567 §1, §8): each // node's key `placement.` with the modules placed there. It is the one source of which module runs where // (issue 1953): the controller's assignments are no longer read. PlacementRead = delivery.PlacementRead // WantsRead is the assigner's wants, `want..`: the build each node is to run (ADR 0539 §1). WantsRead = delivery.AssignerModule + ".wants" // BuildsRead is the builder's builds, `.`: each build's manifest digest (ADR 0525). BuildsRead = delivery.BuilderModule + ".builds" // ModulesRead is the indexer's modules, ``: the trunk's manifest digest of each module, from whichever // repository holds it (ADR 0437 §5, ADR 0478 §9); ManifestsRead its manifests by digest (ADR 0429 §16). ModulesRead = delivery.IndexerModule + ".modules" ManifestsRead = delivery.IndexerModule + ".manifests" // HistoryLog is the log of every set and clear, with the proven caller and why (ADR 0348 §9). HistoryLog = "history" ) // The sources a resolved value says it came from. const ( SourceDefault = "default" SourceMesh = "mesh" SourceNode = "node" SourceModule = "module" // SourceUnset is a required value nothing sets: mesh-config answers it unset (ADR 0348 §4). SourceUnset = "unset" ) // The levels a value is set at, and how each is keyed in mesh-config_values (ADR 0348 §9). const ( LevelMesh = "mesh" LevelNode = "node" LevelModule = "module" ) // Level is where a value is set: the mesh, a node, or a module on a node. type Level struct { Kind string `json:"level"` Node string `json:"node,omitempty"` Module string `json:"module,omitempty"` } // ValueKey is the level's key for a config key in mesh-config_values. func (l Level) ValueKey(key string) string { switch l.Kind { case LevelNode: return "node." + l.Node + "." + key case LevelModule: return "module." + l.Node + "." + l.Module + "." + key } return "mesh." + key } func (l Level) String() string { switch l.Kind { case LevelNode: return "node config on " + l.Node case LevelModule: return l.Module + "'s own value on " + l.Node } return "mesh config" } // parseValueKey is the level and the config key a key of mesh-config_values names. func parseValueKey(k string) (Level, string, bool) { parts := strings.SplitN(k, ".", 4) switch { case len(parts) >= 2 && parts[0] == LevelMesh: return Level{Kind: LevelMesh}, strings.Join(parts[1:], "."), true case len(parts) >= 3 && parts[0] == LevelNode: return Level{Kind: LevelNode, Node: parts[1]}, strings.Join(parts[2:], "."), true case len(parts) == 4 && parts[0] == LevelModule: return Level{Kind: LevelModule, Node: parts[1], Module: parts[2]}, parts[3], true } return Level{}, "", false } // Value is one key of mesh-config_values: the value set, who set it and why, and when. type Value struct { Value json.RawMessage `json:"value"` // By is who set it, as proven: the caller, or for a value given at the controller's terminal, who gave it there. By string `json:"by"` // Via is the bus account the call came from. Via string `json:"via"` Why string `json:"why"` // Approved is who approved it, for a value set on the operator's warrant (ADR 0348 §8, ADR 0277 §15): told from // one given at the terminal, which leaves it empty. Approved string `json:"approved,omitempty"` At time.Time `json:"at"` } // Resolved is one key of mesh-config_resolved, `..`: the value a module on a node takes for a // key, and where it came from. Built from the sources alone, so the same sources give the same bytes and a pass // over unchanged sources writes nothing. type Resolved struct { Node string `json:"node"` Module string `json:"module"` Key string `json:"key"` Value json.RawMessage `json:"value,omitempty"` Source string `json:"source"` // Predefined is the predefined key the value falls back to, when it names one. Predefined string `json:"predefined,omitempty"` // Required says an unset value is the operator's own to set. Required bool `json:"required,omitempty"` Trusted bool `json:"trusted,omitempty"` } // ResolvedKey is a resolved value's key in mesh-config_resolved. func ResolvedKey(node, module, key string) string { return node + "." + module + "." + key } // Sources is everything a resolution reads: where each module is placed, every module's declarations, the published // list of predefined keys, and every value set. type Sources struct { Assignments []Assignment // Declared is each module's declarations as its manifests declare them: the trunk's, with every key only a wanted // build still declares (declarationsOf). It is what a set is judged against. Declared map[string]ModuleDeclarations // OnNode is, by `.`, the declarations of the build the node's want names laid over the trunk's: what // that node resolves. A placed module absent here resolves from Declared. OnNode map[string]ModuleDeclarations // Unresolved is each placed module whose declarations could not be read, with why: never skipped silently // (issue 1953). Its resolved values are kept as they were until its declarations read again. Unresolved []Unresolved Predefined map[string]Published Values map[string]Value // Pulls is what the controller told of each open pull request's declarations, and Kept the approved values held // for them (pulls.go); neither is resolved. Pulls map[string]PullDecl Kept map[string]KeptValue } // Unresolved is a module placed on a node whose values are not resolved, and why. type Unresolved struct { Node string `json:"node"` Module string `json:"module"` Why string `json:"why"` } // declarationsOn is the declarations a module on a node resolves from, and whether any are held. func (s Sources) declarationsOn(node, module string) (ModuleDeclarations, bool) { if d, ok := s.OnNode[node+"."+module]; ok { return d, true } d, ok := s.Declared[module] return d, ok } // unresolved is whether a module on a node is unresolved. func (s Sources) unresolved(node, module string) bool { for _, u := range s.Unresolved { if u.Node == node && u.Module == module { return true } } return false } // Resolve is every placed module's every declared value on its node, keyed as mesh-config_resolved is. A placed // module with no declarations is in s.Unresolved, said there (sources). func Resolve(s Sources) map[string]Resolved { out := map[string]Resolved{} for _, a := range s.Assignments { decl, ok := s.declarationsOn(a.Node, a.Module) if !ok { continue } for _, key := range sortedKeys(decl.Config) { r := resolveOne(s, a.Node, a.Module, key, decl.Config[key]) out[ResolvedKey(a.Node, a.Module, key)] = r } } return out } func resolveOne(s Sources, node, module, key string, d Declaration) Resolved { r := Resolved{Node: node, Module: module, Key: key, Source: SourceUnset, Predefined: d.Predefined, Required: d.Required, Trusted: d.Trusted} if d.HasDefault() { r.Value, r.Source = compact(d.Default), SourceDefault } if d.Predefined != "" { if p, ok := s.Predefined[d.Predefined]; ok && p.Trusted { r.Trusted = true } if v, ok := s.Values[Level{Kind: LevelMesh}.ValueKey(d.Predefined)]; ok { r.Value, r.Source = compact(v.Value), SourceMesh } if v, ok := s.Values[Level{Kind: LevelNode, Node: node}.ValueKey(d.Predefined)]; ok { r.Value, r.Source = compact(v.Value), SourceNode } } if v, ok := s.Values[Level{Kind: LevelModule, Node: node, Module: module}.ValueKey(key)]; ok { r.Value, r.Source = compact(v.Value), SourceModule } return r } // compact is a JSON value encoded the one way: objects' keys sorted, no spaces. func compact(raw json.RawMessage) json.RawMessage { var v any if err := json.Unmarshal(raw, &v); err != nil { return raw } out, err := json.Marshal(v) if err != nil { return raw } return out } // Moved is one resolved value that changed: a module on a node whose value for a key moved, from what to what. type Moved struct { Node string `json:"node"` Module string `json:"module"` Key string `json:"key"` From json.RawMessage `json:"from,omitempty"` To json.RawMessage `json:"to,omitempty"` Source string `json:"source"` // Gone says the module no longer takes the key there: no longer placed, or no longer declared. Gone bool `json:"gone,omitempty"` } // diff is what moved between the resolved values held and those wanted, in key order. func diff(held, want map[string]Resolved) []Moved { var out []Moved for _, k := range sortedKeys(want) { w := want[k] h, had := held[k] if had && string(encodeResolved(h)) == string(encodeResolved(w)) { continue } m := Moved{Node: w.Node, Module: w.Module, Key: w.Key, To: w.Value, Source: w.Source} if had { m.From = h.Value } out = append(out, m) } for _, k := range sortedKeys(held) { if _, ok := want[k]; !ok { h := held[k] out = append(out, Moved{Node: h.Node, Module: h.Module, Key: h.Key, From: h.Value, Gone: true}) } } sort.SliceStable(out, func(i, j int) bool { return ResolvedKey(out[i].Node, out[i].Module, out[i].Key) < ResolvedKey(out[j].Node, out[j].Module, out[j].Key) }) return out } func encodeResolved(r Resolved) []byte { raw, err := json.Marshal(r) if err != nil { panic(fmt.Sprintf("a resolved value cannot be encoded: %v", err)) } return raw }