277 lines
12 KiB
Go
277 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"github.com/novox/mesh-controller/internal/conditions"
|
|
"io"
|
|
"os"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// asking a build machine for a module, and what came back.
|
|
//
|
|
// Split out of main.go, which had reached 2,769 lines because appending was always the
|
|
// cheapest next step. That is how novox/hq ADR 0001 records `hal/sdk` reaching 34,636:
|
|
// nothing in it was wrong, and no one edit was the one that should have been a new file.
|
|
|
|
// buildsCommand says what has been built lately.
|
|
func buildsCommand(ctx context.Context, args []string) error {
|
|
set := flag.NewFlagSet("builds", flag.ContinueOnError)
|
|
limit := set.Int("n", 20, "how many to show")
|
|
positionals, err := parseAround(set, args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
module := ""
|
|
if len(positionals) == 1 {
|
|
module = positionals[0]
|
|
} else if len(positionals) > 1 {
|
|
return errors.New("builds [<module>] [-n N]")
|
|
}
|
|
|
|
open, err := openStores(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer open.Close()
|
|
inv := open.inventory
|
|
|
|
builds, err := inv.Builds(ctx, module, *limit)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(builds) == 0 {
|
|
// Said rather than printed as nothing: an empty list and a failed read must never look
|
|
// the same, and getting here means the store answered.
|
|
if module != "" {
|
|
fmt.Printf("nothing has been built for %s\n", module)
|
|
return nil
|
|
}
|
|
fmt.Println("nothing has been built yet")
|
|
return nil
|
|
}
|
|
|
|
for _, b := range builds {
|
|
printBuild(os.Stdout, b)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// printBuild says one build: what, its outcome, where and when, what it made and published, its reuse key, and its
|
|
// source fingerprint (novox/hq issue 1204: the one tool that reads what the builder recorded it was made from).
|
|
func printBuild(w io.Writer, b inventory.Build) {
|
|
what := b.Module
|
|
if what == "" {
|
|
// It failed before knowing what it was building, which is most of the interesting
|
|
// failures. The repository is what a person has to go and look at.
|
|
what = "?"
|
|
}
|
|
outcome := "built " + short(b.Commit)
|
|
if !b.Worked() {
|
|
outcome = "failed"
|
|
}
|
|
fmt.Fprintf(w, "%-18s %-14s %-10s %s %s\n",
|
|
what, outcome, b.On, b.At.Local().Format("2006-01-02 15:04"), b.ID)
|
|
fmt.Fprintf(w, " %s", b.Repository)
|
|
if b.Ref != "" {
|
|
fmt.Fprintf(w, " at %s", b.Ref)
|
|
}
|
|
fmt.Fprintln(w)
|
|
for _, made := range b.Made {
|
|
fmt.Fprintf(w, " %-10s %s\n", made.Kind, made.Reference)
|
|
}
|
|
// What it published and under what reuse key (novox/hq ADR 0408 §1).
|
|
if p := b.Published; p != nil {
|
|
if p.Artifact != "" {
|
|
fmt.Fprintf(w, " published %s\n", p.Artifact)
|
|
}
|
|
if p.Package != nil {
|
|
fmt.Fprintf(w, " published %s@%s\n", p.Package.Name, p.Package.Version)
|
|
}
|
|
}
|
|
if b.ReuseKey != "" {
|
|
fmt.Fprintf(w, " reuse key %s\n", b.ReuseKey)
|
|
}
|
|
if b.SourceFingerprint != "" {
|
|
fmt.Fprintf(w, " source %s\n", b.SourceFingerprint)
|
|
}
|
|
if !b.Worked() {
|
|
// The builder's own first line. The whole failure is often a build log, and printing
|
|
// it here would bury every other row.
|
|
fmt.Fprintf(w, " %s\n", firstLine(b.Failed))
|
|
}
|
|
}
|
|
|
|
// takenBuild is a build the controller judges and registers, whatever told it of the build: build-agent's outcome (the
|
|
// builder's own module's, until R4g, ADR 0453 §5) or the builder's done key (ADR 0501 §1). **One judging for both**:
|
|
// the rules of who may register, the gate a build failed, the predefined keys and the rule that an unchanged build
|
|
// source moves nothing are the same whichever part built it (ADR 0501 §1, §3).
|
|
type takenBuild struct {
|
|
kept inventory.Build
|
|
manifest catalogue.Manifest
|
|
recorded inventory.Source
|
|
ask registrationAsk
|
|
path string
|
|
trunk string
|
|
// by and what name the build in what is said: who built it, and what was built ("ace", "novox/mesh-catalog").
|
|
by, what, commit string
|
|
// published refuses a build that may not become a module's version (off the trunk, in the scratch namespace);
|
|
// nil for a key, which is a trunk fingerprint's by construction (ADR 0501 §2).
|
|
published func() error
|
|
// carried publishes a list of predefined keys the build carried; nil when it carried none.
|
|
carried func() error
|
|
}
|
|
|
|
// registerJudged judges a recorded build and registers its module, or says why not.
|
|
func registerJudged(ctx context.Context, inv *inventory.Inventory, j takenBuild) (catalogue.Manifest, error) {
|
|
manifest, kept := j.manifest, j.kept
|
|
notRegistered := func(err error) (catalogue.Manifest, error) {
|
|
return manifest, fmt.Errorf("%s built %s (%s), recorded and not registered: %w", j.by, manifest.Module,
|
|
short(j.commit), err)
|
|
}
|
|
if err := namesNoInstallation(manifest); err != nil {
|
|
return manifest, fmt.Errorf("%s built %s (%s), and the mesh does not register it: %w",
|
|
j.by, j.what, short(j.commit), err)
|
|
}
|
|
// **Only from the repository the catalogue builds the module from** (novox/hq ADR 0266): else the trunk
|
|
// below is the trunk of whatever repository was built, which may be one an agent made — and a module named
|
|
// `sudo` from it would be what the next push sends. Another repository is the operator's, at the terminal.
|
|
repoID, err := mayRegisterFrom(ctx, inv, manifest.Module, j.recorded, j.ask, j.path, j.trunk)
|
|
if err != nil {
|
|
return notRegistered(err)
|
|
}
|
|
if j.published != nil {
|
|
if err := j.published(); err != nil {
|
|
return notRegistered(err)
|
|
}
|
|
}
|
|
// Then the module is judged against the list the mesh publishes, as every manifest read again is: one naming a
|
|
// key that list does not hold, or of another type, is refused here and never enters the catalogue.
|
|
if j.carried != nil {
|
|
if err := j.carried(); err != nil {
|
|
return manifest, err
|
|
}
|
|
}
|
|
if manifest.NamesPredefined() {
|
|
if _, err := catalogue.ParseManifest(kept.Manifest); err != nil {
|
|
return notRegistered(fmt.Errorf("judged against the list of predefined keys the mesh publishes, %w", err))
|
|
}
|
|
}
|
|
// **A build whose source is unchanged is never a move** (novox/hq issue 280): a rebuild made from
|
|
// what the build the mesh stands on was made from registers that build's artifacts at the new
|
|
// commit, so no machine is sent a new digest for a source nobody changed — an image is not
|
|
// byte-reproducible, and the bus rebuilt for another module's merge demanded a planned upgrade.
|
|
if kept.SourceFingerprint != "" {
|
|
stands, raw, err := inv.StandingBuild(ctx, manifest.Module, kept.ID)
|
|
if err != nil {
|
|
return manifest, err
|
|
}
|
|
if stands != "" && stands != kept.ID && len(raw) > 0 {
|
|
if was, err := catalogue.ParseManifest(raw); err == nil && was.Module == manifest.Module {
|
|
fmt.Printf("%s at %s was made from the source %s was: registered with its artifacts, no move\n",
|
|
manifest.Module, short(j.commit), stands)
|
|
manifest = was
|
|
}
|
|
}
|
|
}
|
|
if err := inv.RegisterModule(ctx, manifest, j.recorded); err != nil {
|
|
if errors.Is(err, inventory.ErrSuperseded) {
|
|
return notRegistered(err)
|
|
}
|
|
return manifest, err
|
|
}
|
|
// A bus process the new definition gains gets its account now, on each machine assigned the module, so the
|
|
// next push composes a real account (issue 1226).
|
|
issueGainedProcessAccounts(ctx, inv, manifest)
|
|
// Which repository it is registered from, by the forge's own id (novox/hq ADR 0266).
|
|
if err := inv.SetSourceIdentity(ctx, manifest.Module, repoID); err != nil {
|
|
return manifest, err
|
|
}
|
|
// The keep set just moved, and new bytes just landed (novox/hq ADR 0189). Asked here rather
|
|
// than on a timer of its own: this is the only moment either is true. Never fatal — the build
|
|
// worked and the module is registered.
|
|
collect(ctx, inv)
|
|
return manifest, nil
|
|
}
|
|
|
|
// answers is what the three questions came back with, read once.
|
|
type answers struct {
|
|
// notAnnounced are the owners some module reads the state of that have announced nothing yet (novox/hq ADR 0495,
|
|
// issue 1790): read from `reads` as before until they do; notAnnouncedWhy why that could not be read.
|
|
notAnnounced []string
|
|
notAnnouncedWhy string
|
|
wrong []inventory.Doing
|
|
nodes []inventory.Node
|
|
quiet []inventory.Node
|
|
behind map[string][]string
|
|
sources map[string]inventory.Source
|
|
// waiting is every machine not running what the mesh would send it.
|
|
waiting []inventory.Machine
|
|
// recordedWaits is, per machine, every move of a recorded module that waits there for a person (novox/hq ADR
|
|
// 0377 §6): the machine is behind for these, and `push <node>` sends them.
|
|
recordedWaits map[string][]string
|
|
// reported is every machine's last word beside when it was last sent a declaration — the
|
|
// pair that answers "has it caught up", which waiting alone cannot (the sent digest is
|
|
// recorded at send, not at apply).
|
|
reported []inventory.Reported
|
|
// refused is why a machine cannot be worked out at all, by name. A different thing from every
|
|
// other answer here: those are about a machine that was told something, and this is about one
|
|
// that cannot be told anything — it never reaches waiting, because nothing was computed for it
|
|
// to compare against, so without this a wholly blocked mesh reads as a well one.
|
|
refused map[string]string
|
|
// network is why the private network could not be computed, when it could not. Almost always
|
|
// a consequence of the refusals above: a node that does not resolve is not on the network, and
|
|
// a mesh whose hub is that node has no hub.
|
|
network string
|
|
// filtered is every converged machine that is not filtered by the mesh alone (novox/hq ADR
|
|
// 0168): what filters it beyond the mesh's own, the runtime's plumbing and bans, by name — a
|
|
// predecessor's chain, a found firewall in force again. Such a machine is not "all well".
|
|
filtered map[string]inventory.Filtering
|
|
// untaken is, per machine, each assigned module whose resources the machine is holding as it
|
|
// found them, and how many — a module that was assigned, sent, and is running none of what it
|
|
// declares because nothing has taken it (novox/hq ADR 0100, 04-ISSUES/125).
|
|
//
|
|
// **Its absence cost an outage.** The module was assigned, the push reported success, this
|
|
// command said the machine was doing everything it was told, and the module's three containers
|
|
// did not exist. On the strength of those reports the predecessor's proxy was stopped and every
|
|
// public name on the machine went dark. The holds were correct; they were recorded only in the
|
|
// machine's own state file, and the one visible symptom was a count that did not add up.
|
|
untaken map[string]map[string]int
|
|
// leftOut is, per machine, every module of its set its composition leaves out, and why (novox/hq issue
|
|
// 380): assigned and not applied, which every push said only in passing. Not well while there is any.
|
|
leftOut map[string][]leftOutModule
|
|
// unheld is every module on a machine whose resources are applied through a seat nothing on
|
|
// that machine holds (novox/hq ADR 0207), with the modules that could hold it. Reported, not
|
|
// refused, until the switch — and while there is any, the mesh is not all well: the order the
|
|
// machines' modules are built in is the mesh's to keep, and this is where it says it is not kept.
|
|
unheld []catalogue.Unheld
|
|
// conditions is every open condition (novox/hq to-be 45 §2), urgent first and then oldest first:
|
|
// what leads status, and what its all-well sentence needs to be none of, silenced ones included.
|
|
// A provider failing a consumer is one of them (ADR 0224). conditionsUnread says why they could
|
|
// not be read when they could not — never read as none.
|
|
conditions []conditions.Condition
|
|
conditionsUnread string
|
|
// overflowing is every module whose identity overflows the bound of a provision it requires
|
|
// (novox/hq ADR 0225): its provider leaves it out of the grants and composes everything else, so
|
|
// this is the one place it is said across the mesh. Not well while there is any.
|
|
overflowing []catalogue.Overflow
|
|
// handActs is how many acts were done by hand in the last seven days (novox/hq to-be 45 §7), nil
|
|
// where the log is not on hand; handActsUnread why it could not be read when it could not.
|
|
handActs *int
|
|
handActsUnread string
|
|
// heals is what the healers did in the last seven days (novox/hq to-be 45 §7): acts, and conditions
|
|
// handed to the operator; healsUnread why it could not be read.
|
|
heals *healsCount
|
|
healsUnread string
|
|
// pending is every assignment waiting for its module's build, and every one ended in the last day
|
|
// (novox/hq issue 325); pendingUnread why they could not be read. One waiting, or one ended other than
|
|
// applied, is not well: an assignment somebody made is not made yet, or will not be.
|
|
pending []inventory.PendingAssignment
|
|
pendingUnread string
|
|
}
|