package catalogue import ( "fmt" "strings" ) // What the mesh does when a module's build moves (novox/hq ADR 0236, extending ADR 0162 §3 and ADR // 0218 §2). // // **Rolled out by default, one machine first and gated.** With the gate on the first machine and the // rollback after it (to-be 45 §8), a build that moves is sent to one machine, judged there by its own // health, and only then to the rest — or put back there, said, and sent nowhere else. Recording an // upgrade and waiting for a person to push it is kept where a module says why, and where the mesh knows // a rollback cannot undo what a new build does. // The policies a module may declare. const ( // PolicyRoll sends one machine first, judges it at the gate, then the rest. PolicyRoll = "roll" // PolicyTogether sends every machine running the module at once — for a module that must change // everywhere in the same minute. Still judged, on every machine, after. PolicyTogether = "together" // PolicyRecord builds and sends nothing: the machines running it are behind until a person pushes. PolicyRecord = "record" ) // UpgradePolicy is what a module says about how its new builds reach its machines: `upgrade` in its // manifest. type UpgradePolicy struct { Policy string `json:"policy"` // Why is required for anything but roll: a person reading the catalogue sees why this module waits // for them, or why it changes everywhere at once. Why string `json:"why,omitempty"` } func (m Manifest) upgradeProblems() []string { if m.Upgrade == nil { return nil } switch m.Upgrade.Policy { case PolicyRoll: case PolicyTogether, PolicyRecord: if strings.TrimSpace(m.Upgrade.Why) == "" { return []string{fmt.Sprintf("upgrade %q says why: a module that does not roll out one machine first "+ "names the reason a person reads", m.Upgrade.Policy)} } default: return []string{fmt.Sprintf("upgrade is %q, %q or %q, not %q", PolicyRoll, PolicyTogether, PolicyRecord, m.Upgrade.Policy)} } return nil } // Where a policy came from, as `upgrade` says it. const ( FromPerson = "person" FromModule = "module" FromBus = "the bus" FromData = "irreplaceable data" FromDefault = "default" ) // DerivedUpgrade is the policy a module's manifest gives it when no person has chosen one, with where // it came from and why (ADR 0236): // // - **the bus is never rolled**: a module that provides the mesh's bus records, whatever it says — its // upgrade is a planned step a person starts (to-be 45 §8); // - a module that says its policy has it; // - a module that keeps irreplaceable data records — sending the previous build cannot undo what a new // one did to data that cannot be had again, so a person takes it, after a backup; // - everything else rolls out, one machine first. func DerivedUpgrade(m Manifest) (policy, from, why string) { if ProvidesBus(m) { return PolicyRecord, FromBus, "the bus is replaced only as a planned step a person starts (`bus upgrade`): " + "its streams are snapshotted first and checked after" } if m.Upgrade != nil && m.Upgrade.Policy != "" { return m.Upgrade.Policy, FromModule, m.Upgrade.Why } if item := m.irreplaceable(); item != "" { return PolicyRecord, FromData, "it keeps irreplaceable data (" + item + "): a rollback cannot undo what a new " + "build does to it, so a person takes each build, after a backup" } return PolicyRoll, FromDefault, "" } // ProvidesBus is whether a manifest provides the mesh's bus. func ProvidesBus(m Manifest) bool { for _, o := range m.Provides { if o.Name == "mesh-bus" { return true } } return false } // irreplaceable names the first irreplaceable data the module keeps, its own or its consumers', or // nothing. func (m Manifest) irreplaceable() string { if m.Data == nil { return "" } for _, it := range m.Data.Own { if it.Class == ClassIrreplaceable { return it.ID } } for provision, c := range m.Data.Consumers { if c.Class == ClassIrreplaceable { return "its consumers' " + provision } } for provision, k := range m.Data.KeptBy { if k.Class == ClassIrreplaceable { return "what it keeps with " + provision } } return "" }