package main import ( "context" "fmt" "slices" "time" "github.com/novox/mesh-controller/internal/chain" "github.com/novox/mesh-controller/internal/inventory" ) // The links a rollout makes (novox/hq ADR 0408 §1, design 47 acceptance 10, issue 1095): the send of a plan's tier puts a // build on a machine; the assignment of the module there names the build, its commit and the rollout (written by the record of // the send, inventory.RecordSent, from the context a send is made in), and the rollout lists the assignments it changed with the // build before and after. // rolloutModules is what a plan sends of each module: the commit and the build of it. func rolloutModules(p *inventory.Plan) map[string]inventory.RolloutModule { out := make(map[string]inventory.RolloutModule, len(p.Modules)) for m, s := range p.Modules { if s != nil && s.Commit != "" { out[m] = inventory.RolloutModule{Commit: s.Commit, Build: s.Build} } } return out } // sendingAs is the context a plan's send is made in: what is recorded of the send names this rollout. func sendingAs(ctx context.Context, p *inventory.Plan) context.Context { return inventory.WithRollout(ctx, p.ID, rolloutModules(p)) } // ranBefore is what each module on a machine ran before a send, as the chain names it: its assignment's build and commit, // else the commit the machine was last sent for it. Read before the send, which writes the new ones. func ranBefore(ctx context.Context, inv *inventory.Inventory, node string) map[string]chain.Ref { was := map[string]chain.Ref{} if sent, known, err := inv.SentBuilds(ctx, node); err == nil && known { for m, commit := range sent { was[m] = chain.Ref{Commit: commit} } } if ran, err := inv.AssignmentRuns(ctx, node); err == nil { for m, r := range ran { was[m] = r } } return was } // noteChanges writes into a plan that a send to a machine changed the assignments of the modules it carried: the build and // commit before and after, the builds the rollout carried, and the step taken. A change noted already for the machine and the // module is replaced, so a send made again says it once. func noteChanges(p *inventory.Plan, node string, modules []string, was map[string]chain.Ref, at time.Time) { for _, m := range modules { s := p.Modules[m] if s == nil { continue } change := chain.Change{Node: node, Module: m, Before: was[m], After: chain.Ref{Build: s.Build, Commit: s.Commit}} if at := slices.IndexFunc(p.Changed, func(c chain.Change) bool { return c.Node == node && c.Module == m }); at >= 0 { change.Before = p.Changed[at].Before p.Changed[at] = change } else { p.Changed = append(p.Changed, change) } if s.Build != "" && !slices.Contains(p.Builds, s.Build) { p.Builds = append(p.Builds, s.Build) } } if !slices.ContainsFunc(p.Steps, func(s chain.Step) bool { return s.Tier == p.Tier && s.Node == node }) { sent := at.UTC() p.Steps = append(p.Steps, chain.Step{Tier: p.Tier, Node: node, Sent: &sent}) } } // refSaid is a build and the commit it was made from as a person reads them; "nothing" for a module that ran none. func refSaid(r chain.Ref) string { switch { case r.Build == "" && r.Commit == "": return "nothing" case r.Build == "": return "commit " + short(r.Commit) } return r.Build + " (" + short(r.Commit) + ")" } // changedLines say the assignments a rollout changed: the machine, the module, and the build before and after. func changedLines(p inventory.Plan) []string { if len(p.Changed) == 0 { return nil } out := []string{" changed:"} for _, c := range p.Changed { out = append(out, fmt.Sprintf(" %s/%s %s → %s", c.Node, c.Module, refSaid(c.Before), refSaid(c.After))) } return out } // runLines say what each module assigned to a machine runs there, as its assignment names it: the build, the commit, the // rollout that put it there and since when. func runLines(assigned []chain.Assignment) []string { var out []string for _, a := range assigned { if a.Build == "" && a.Commit == "" { continue } line := fmt.Sprintf(" %-22s %s", a.Module, refSaid(chain.Ref{Build: a.Build, Commit: a.Commit})) if a.Rollout != "" { line += ", sent by " + a.Rollout } if !a.Since.IsZero() { line += " at " + a.Since.Local().Format("2006-01-02 15:04") } out = append(out, line) } if len(out) == 0 { return nil } return append([]string{" what they run, as the chain names it:"}, out...) }