A board that reads through the same interfaces and holds nothing
novox/hq 03-DESIGN/01-to-be/11-a-board.md, built. The board being replaced is one service reading every context's database directly — ADR 0008 violated by the one component with a reason to violate it. The cost is not hypothetical: a boundary nothing may cross can move, and one thing crossing it is enough to freeze it. A board that reads the provisioning tables breaks when provisioning changes them, and the change then gets weighed against the board. So the three questions are read once, by one function, for all three ways of saying them — a person's status, its JSON, and this page. Three implementations of "which machine is not doing what it was told" would be three chances to disagree. Refused and failed stay distinct all the way to the page: refused means the machine is exactly as it was and what is wrong is in what was sent; failed means it is in a state nobody declared. Different places to fix, so one word for both would send half the readers to the wrong one. It stores nothing, changes nothing, and every action it might offer already exists as a command. A board that cannot reach the mesh says so rather than rendering an empty page — an empty page says "nothing is wrong" in the one situation where nobody can know that. One test earns its place twice: a machine's own words are the whole reason the page is useful and the one thing on it nobody in this repository wrote, so they are shown and are not markup.
This commit is contained in:
+59
-32
@@ -71,6 +71,8 @@ func run() error {
|
||||
return buildCommand(ctx, args[1:])
|
||||
case "builder":
|
||||
return builderCommand(ctx, args[1:])
|
||||
case "board":
|
||||
return boardCommand(ctx, args[1:])
|
||||
case "licence":
|
||||
return licenceCommand(ctx, args[1:])
|
||||
case "rotate":
|
||||
@@ -140,6 +142,7 @@ func usage() {
|
||||
module moved <name> <commit> the source has a newer commit than the mesh built
|
||||
module forget <name> remove one, unless a node is running it
|
||||
status [--json] what is wrong, what is quiet, and what is out of date
|
||||
board [--listen ADDR] the same three questions, as a page that holds nothing
|
||||
assign <node> <module> put a module on a node
|
||||
unassign <node> <module> take it off
|
||||
settings set <module> <file> what a module's config should say, for the whole mesh
|
||||
@@ -1791,41 +1794,12 @@ func statusCommand(ctx context.Context, args []string) error {
|
||||
}
|
||||
defer inv.Close()
|
||||
|
||||
// Three questions, in the order somebody asks them: is anything broken, is anything not
|
||||
// answering, is anything out of date. The first has consequences now, the second may, and
|
||||
// the third is a plan for later — and a status that led with the third would bury the first.
|
||||
//
|
||||
// All three are gathered before anything is said, so the two ways of saying it answer the
|
||||
// same questions from the same reads rather than being two implementations.
|
||||
wrong, err := inv.NotDoingWhatTheyWereTold(ctx)
|
||||
asked, err := theThreeQuestions(ctx, inv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodes, err := inv.Nodes(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var quiet []inventory.Node
|
||||
for _, n := range nodes {
|
||||
// Never heard from, or not lately. Different from failing: a machine that says nothing
|
||||
// may be new, switched off, or unreachable, and none of those is a machine that tried
|
||||
// and could not.
|
||||
if n.LastSeen.IsZero() || time.Since(n.LastSeen) > time.Hour {
|
||||
quiet = append(quiet, n)
|
||||
}
|
||||
}
|
||||
behind, err := inv.Behind(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sources := map[string]inventory.Source{}
|
||||
for module := range behind {
|
||||
from, err := inv.SourceOf(ctx, module)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sources[module] = from
|
||||
}
|
||||
wrong, nodes, quiet := asked.wrong, asked.nodes, asked.quiet
|
||||
behind, sources := asked.behind, asked.sources
|
||||
|
||||
if *asJSON {
|
||||
body, err := statusAsJSON(wrong, nodes, quiet, behind, sources)
|
||||
@@ -2533,3 +2507,56 @@ func buildAndShow(ctx context.Context, repository, ref string, wait time.Duratio
|
||||
fmt.Println(string(body))
|
||||
return nil
|
||||
}
|
||||
|
||||
// answers is what the three questions came back with, read once.
|
||||
type answers struct {
|
||||
wrong []inventory.Doing
|
||||
nodes []inventory.Node
|
||||
quiet []inventory.Node
|
||||
behind map[string][]string
|
||||
sources map[string]inventory.Source
|
||||
}
|
||||
|
||||
// theThreeQuestions reads what anything answering "is the mesh alright" needs.
|
||||
//
|
||||
// **One reading, for every way of saying it** (novox/hq 03-DESIGN/01-to-be/11-a-board.md). There
|
||||
// are three now — a person's status, its JSON, and a page — and three implementations of "which
|
||||
// machine is not doing what it was told" would be three chances to disagree about it.
|
||||
//
|
||||
// The order is the design and not a convenience: is anything broken, is anything not answering, is
|
||||
// anything out of date. The first has consequences now, the second may, the third is a plan for
|
||||
// later — and anything that led with the third would bury the first.
|
||||
func theThreeQuestions(ctx context.Context, inv *inventory.Inventory) (answers, error) {
|
||||
var out answers
|
||||
var err error
|
||||
|
||||
out.wrong, err = inv.NotDoingWhatTheyWereTold(ctx)
|
||||
if err != nil {
|
||||
return answers{}, err
|
||||
}
|
||||
out.nodes, err = inv.Nodes(ctx)
|
||||
if err != nil {
|
||||
return answers{}, err
|
||||
}
|
||||
for _, n := range out.nodes {
|
||||
// Never heard from, or not lately. Different from failing: a machine that says nothing
|
||||
// may be new, switched off, or unreachable, and none of those is a machine that tried
|
||||
// and could not.
|
||||
if n.LastSeen.IsZero() || time.Since(n.LastSeen) > time.Hour {
|
||||
out.quiet = append(out.quiet, n)
|
||||
}
|
||||
}
|
||||
out.behind, err = inv.Behind(ctx)
|
||||
if err != nil {
|
||||
return answers{}, err
|
||||
}
|
||||
out.sources = map[string]inventory.Source{}
|
||||
for module := range out.behind {
|
||||
from, err := inv.SourceOf(ctx, module)
|
||||
if err != nil {
|
||||
return answers{}, err
|
||||
}
|
||||
out.sources[module] = from
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user