mesh/delivery-group group fix/403-fix-time checking: composed check out of date: novox/mesh-controller@7ca5a45ecc5a was judged against trunk ?, the trunk is no…
mesh/merge-gate out of date: judged against trunk 84deb530ee5a, the trunk is now 4dbd4066ca54; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 84deb530ee5a, the trunk is now 4dbd4066ca54; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
217 lines
8.2 KiB
Go
217 lines
8.2 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"path"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
// The change plan (novox/hq ADR 0238): **one commit, one plan** — what a change does to the mesh, computed
|
|
// from its diffset (a repository, the branch it merges into, the commit at hand) by the planner, never by
|
|
// a mapping of its own. reachOfMerge answers what moves and what follows it; this adds where each module
|
|
// goes — the deploy plan, machine by machine in the build plan's order — and what is not an ordinary
|
|
// send. A pull request's check posts it with its verdict; the release a merge makes follows the same
|
|
// planner, so the two can be compared.
|
|
|
|
// policyOf is a module's upgrade policy, as the controller holds it; false when it cannot be read.
|
|
type policyOf func(module string) (inventory.Upgrade, bool)
|
|
|
|
// changePlanOf is the change plan of a reach: pure, so it is tested without a store.
|
|
func changePlanOf(repository, base, head string, r mergeReach, entries []inventory.Entry, policy policyOf) link.ChangePlan {
|
|
p := link.ChangePlan{Repository: repository, Base: base, Head: head, Moved: r.Moved(), Dependents: r.Dependents(),
|
|
New: r.Added, Unread: r.Unread, Tiers: r.Plan.Tiers}
|
|
byName := map[string]inventory.Entry{}
|
|
for _, e := range entries {
|
|
byName[e.Manifest.Module] = e
|
|
}
|
|
// The machine holding the bus, whose declaration carries every module's grants (novox/hq issue 490).
|
|
holder := ""
|
|
for _, e := range entries {
|
|
if e.Manifest.BusUsers != "" && e.Manifest.ClaimsSeat(catalogue.BrokerSeat) && len(e.On) > 0 {
|
|
holder = e.On[0]
|
|
}
|
|
}
|
|
var granting []string
|
|
machines := map[string]*link.MachinePlan{}
|
|
machine := func(name string) *link.MachinePlan {
|
|
if machines[name] == nil {
|
|
machines[name] = &link.MachinePlan{Machine: name}
|
|
}
|
|
return machines[name]
|
|
}
|
|
for _, tier := range r.Plan.Tiers {
|
|
for _, name := range tier {
|
|
e, held := byName[name]
|
|
if !held {
|
|
continue
|
|
}
|
|
u, known := policy(name)
|
|
waits := known && !u.RollOut
|
|
for _, on := range e.On {
|
|
if waits {
|
|
machine(on).Waits = append(machine(on).Waits, name)
|
|
} else {
|
|
machine(on).Receives = append(machine(on).Receives, name)
|
|
}
|
|
}
|
|
if !waits && holder != "" && !(len(e.On) == 1 && e.On[0] == holder) && len(e.On) > 0 &&
|
|
!slices.Contains(granting, name) {
|
|
granting = append(granting, name)
|
|
}
|
|
switch {
|
|
case (name == "nats" || catalogue.ProvidesBus(e.Manifest)) && len(e.On) > 0:
|
|
// Said before the merge (novox/hq issue 336): a new bus build holds every send to the bus's machine
|
|
// until a person takes the step, so merging it is a promise to take it.
|
|
p.Steps = append(p.Steps, fmt.Sprintf("%s: %s is never sent by an ordinary send, and until %s runs, "+
|
|
"nothing else is sent to %s either — unless the new build turns out the same as the one running there "+
|
|
"(issue 280)", busUpgradeNeeded, name, busUpgradeVerb, strings.Join(e.On, ", ")))
|
|
case name == "nats" || catalogue.ProvidesBus(e.Manifest):
|
|
p.Steps = append(p.Steps, "a planned bus step: the bus is upgraded by `bus upgrade`, never by an ordinary send")
|
|
case waits && len(e.On) > 0:
|
|
p.Steps = append(p.Steps, fmt.Sprintf("%s waits for a person: its policy records (%s)", name,
|
|
orNone(u.From)))
|
|
}
|
|
if len(e.Manifest.Provides) > 0 && len(e.On) > 0 {
|
|
var offers []string
|
|
for _, o := range e.Manifest.Provides {
|
|
offers = append(offers, o.Name)
|
|
}
|
|
p.Steps = append(p.Steps, fmt.Sprintf("%s provides %s: its consumers are sent again after it",
|
|
name, strings.Join(offers, ", ")))
|
|
}
|
|
if e.Manifest.Data != nil && len(e.On) > 0 {
|
|
p.Steps = append(p.Steps, fmt.Sprintf("%s keeps data (ADR 0233): what it holds is backed up before it moves", name))
|
|
}
|
|
}
|
|
}
|
|
if len(granting) > 0 {
|
|
// Said before the merge (novox/hq issue 490): what the walk does when the change alters the bus's
|
|
// user list. Whether it does is known only once the builds are registered, so it is said as a rule.
|
|
p.Steps = append(p.Steps, fmt.Sprintf("%s: if this changes what the bus's user list says (a new tool, "+
|
|
"subject or user), %s is sent that list alone, every build there kept, before %s is judged on its first "+
|
|
"machine", grantsStepWord, holder, strings.Join(granting, ", ")))
|
|
}
|
|
var names []string
|
|
for name := range machines {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
p.Machines = append(p.Machines, *machines[name])
|
|
}
|
|
p.Summary = summaryOf(p)
|
|
p.Class = changeClassOf(p, entries)
|
|
return p
|
|
}
|
|
|
|
// classNone is the class of a change that moves no module: nothing for a walk to move (novox/hq ADR 0313 §2).
|
|
const classNone = "none"
|
|
|
|
// changeClassOf is a change plan's class (novox/hq ADR 0313 §2): that of the slowest-budgeted module it moves,
|
|
// itself or as a dependent — core when one is on the controller's own path or holds the mesh's resolver, leaf
|
|
// otherwise — and none when it moves nothing. A module it adds a directory for counts by its own class: the
|
|
// catalogue names a module's directory by the module, so the directory's last element is its name. The graph holds
|
|
// no manifest of it yet, so a new module is core by its name alone (on the controller's own path), never by a
|
|
// resolver claim its manifest makes.
|
|
func changeClassOf(p link.ChangePlan, entries []inventory.Entry) string {
|
|
if len(p.Moved) == 0 && len(p.Dependents) == 0 && len(p.New) == 0 {
|
|
return classNone
|
|
}
|
|
modules := append(append([]string{}, p.Moved...), p.Dependents...)
|
|
for _, dir := range p.New {
|
|
if name := path.Base(strings.Trim(dir, "/")); name != "." && name != "/" && name != "" {
|
|
modules = append(modules, name)
|
|
}
|
|
}
|
|
return classOfModules(modules, entries)
|
|
}
|
|
|
|
// grantsStepWord names the grants step (novox/hq issue 490): the bus's user list sent alone to the machine
|
|
// holding the bus ahead of a walk's gate. Not the bus step, which replaces the bus itself.
|
|
const grantsStepWord = "grants step"
|
|
|
|
// busUpgradeNeeded is how a change plan says that merging it holds the bus's machine for a person's step.
|
|
const busUpgradeNeeded = "merging this needs a person's bus upgrade"
|
|
|
|
// summaryOf is a change plan in one line: what it builds, where it goes, and whether the bus moves.
|
|
func summaryOf(p link.ChangePlan) string {
|
|
if len(p.Moved) == 0 && len(p.New) == 0 {
|
|
return "builds nothing: the change touches no module of the mesh's graph"
|
|
}
|
|
var parts []string
|
|
what := strings.Join(p.Moved, ", ")
|
|
if len(p.Dependents) > 0 {
|
|
what += fmt.Sprintf(" (+%d dependent(s))", len(p.Dependents))
|
|
}
|
|
if len(p.New) > 0 {
|
|
if what != "" {
|
|
what += ", "
|
|
}
|
|
what += "new: " + strings.Join(p.New, ", ")
|
|
}
|
|
var to []string
|
|
for _, m := range p.Machines {
|
|
if len(m.Receives) > 0 {
|
|
to = append(to, m.Machine)
|
|
}
|
|
}
|
|
if len(to) > 0 {
|
|
parts = append(parts, "builds "+what+" → "+strings.Join(to, ", "))
|
|
} else {
|
|
parts = append(parts, "builds "+what+", sent nowhere")
|
|
}
|
|
bus := "no bus step"
|
|
for _, s := range p.Steps {
|
|
switch {
|
|
case strings.HasPrefix(s, busUpgradeNeeded):
|
|
bus = busUpgradeNeeded
|
|
case strings.HasPrefix(s, "a planned bus step") && bus == "no bus step":
|
|
bus = "a bus step"
|
|
}
|
|
}
|
|
parts = append(parts, bus)
|
|
waiting := 0
|
|
for _, m := range p.Machines {
|
|
waiting += len(m.Waits)
|
|
}
|
|
if waiting > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d wait(s) for a person", waiting))
|
|
}
|
|
return strings.Join(parts, "; ")
|
|
}
|
|
|
|
// planText is a change plan as a person reads it, for the pull request's comment.
|
|
func planText(p link.ChangePlan) string {
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "change plan of %s at %.8s into %s: %s\n", p.Repository, p.Head, p.Base, p.Summary)
|
|
for i, tier := range p.Tiers {
|
|
fmt.Fprintf(&b, " tier %d: %s\n", i, strings.Join(tier, ", "))
|
|
}
|
|
for _, m := range p.Machines {
|
|
line := " " + m.Machine + ": "
|
|
if len(m.Receives) > 0 {
|
|
line += "receives " + strings.Join(m.Receives, ", ")
|
|
}
|
|
if len(m.Waits) > 0 {
|
|
if len(m.Receives) > 0 {
|
|
line += "; "
|
|
}
|
|
line += "waits for a person: " + strings.Join(m.Waits, ", ")
|
|
}
|
|
b.WriteString(line + "\n")
|
|
}
|
|
for _, s := range p.Steps {
|
|
b.WriteString(" - " + s + "\n")
|
|
}
|
|
if len(p.Unread) > 0 {
|
|
fmt.Fprintf(&b, " read by no module's build: %s\n", strings.Join(p.Unread, ", "))
|
|
}
|
|
return b.String()
|
|
}
|