154 lines
5.9 KiB
Go
154 lines
5.9 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// The three seats that apply resources (novox/hq ADR 0207 §1). Named once, because the derivation,
|
|
// the seed and the messages all turn on these strings.
|
|
const (
|
|
ServiceManagerSeat = "node-service-manager"
|
|
PackageManagerSeat = "node-package-manager"
|
|
ContainerRuntimeSeat = "node-container-runtime"
|
|
)
|
|
|
|
// appliedThrough is which seat applies a resource of each type. **Only these three**: ADR 0207
|
|
// names them and no more. A process is supervised by the host itself, a file, a directory, an
|
|
// archive, a user or an action is the host's own act, and a module's other kinds reach the machine
|
|
// without a role in between — adding one here is a decision, not a refinement.
|
|
var appliedThrough = map[string]string{
|
|
"service": ServiceManagerSeat,
|
|
"package": PackageManagerSeat,
|
|
"container": ContainerRuntimeSeat,
|
|
}
|
|
|
|
// isFoundation is whether a module's declarations are the foundation's rather than its own: a module whose resources are
|
|
// computed (`computed`, the private network's peer list written per node) is the mesh's by construction. Read from the
|
|
// manifest alone, never from its name (issue 774, the operator 2026-11-03): the node-engine's own manifest declares no
|
|
// resource a seat applies, so it depends on none by the rules below.
|
|
func isFoundation(m Manifest) bool {
|
|
return m.Computed != ""
|
|
}
|
|
|
|
// DependsOn is every seat a module needs held on its node: derived from the resource types it
|
|
// declares itself (novox/hq ADR 0207 §2), from what it contributes to (ADR 0210), and from what a
|
|
// node seat it claims needs beside it — read from whichever manifest in `catalogue` declares that
|
|
// seat (novox/hq ADR 0220, ADR 0378 §8). Sorted.
|
|
//
|
|
// **The module's own `resources` only**, for the first source. What the controller composes around
|
|
// a module — its filter, jails, certificates, kept files, bundles, the guard — is the mesh's, put
|
|
// there because the module is assigned, and depending on it would make the module answer for the
|
|
// mesh's choices.
|
|
func DependsOn(catalogue map[string]Manifest, m Manifest) []string {
|
|
if isFoundation(m) {
|
|
return nil
|
|
}
|
|
seen := map[string]bool{}
|
|
for _, r := range m.Resources {
|
|
if seat, applied := appliedThrough[fmt.Sprint(r["type"])]; applied {
|
|
seen[seat] = true
|
|
}
|
|
}
|
|
for _, seat := range contributedTo(m) {
|
|
seen[seat] = true
|
|
}
|
|
for _, seat := range neededBesideClaims(catalogue, m) {
|
|
seen[seat] = true
|
|
}
|
|
out := make([]string, 0, len(seen))
|
|
for s := range seen {
|
|
out = append(out, s)
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
// contributedTo is every seat a module contributes to (novox/hq ADR 0210 §3): a contribution is
|
|
// configuration only the seat's holder applies, so it is a dependency on that seat exactly as a
|
|
// resource is on the seat that applies it. The environment goes to node-environment's holder
|
|
// (ADR 0203); shell code to the holder that places it for its target — the login shell's for a
|
|
// shell, the display server's for the session's start and resources (ADR 0204, ADR 0208 §4).
|
|
func contributedTo(m Manifest) []string {
|
|
var out []string
|
|
if e := m.Environment; e != nil && (len(e.Variables) > 0 || len(e.Path) > 0) {
|
|
out = append(out, EnvironmentSeat)
|
|
}
|
|
for _, c := range m.Shell {
|
|
out = append(out, placerOf(c.For))
|
|
}
|
|
// Any other seat's contribution (novox/hq ADR 0212 §4), by the seat's canonical name; one the
|
|
// mesh does not define is refused at registration and depends on nothing here.
|
|
// A kind the seat only offers to place makes no dependency (novox/hq ADR 0255).
|
|
for _, c := range m.Contributions {
|
|
if s, r, ok := receivable(c.Seat, c.Kind); ok && r.Offered {
|
|
continue
|
|
} else if s.Name != "" {
|
|
out = append(out, s.Name)
|
|
}
|
|
}
|
|
// And the backup holder for what is irreplaceable (novox/hq ADR 0233): it backs it up or watches the
|
|
// array it is on. What is valuable is backed up where a holder is — the standard plan — and makes
|
|
// no machine refuse it for want of one.
|
|
if m.NeedsBackupHolder() {
|
|
out = append(out, BackupSeat)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// neededBesideClaims is every seat a node seat the module claims needs held on the same node
|
|
// (novox/hq ADR 0220, ADR 0378 §8): a module-declared seat's needs are read from whichever manifest
|
|
// in the catalogue declares that seat, never written again in the claimant's own manifest. Derived
|
|
// from the claim, as a resource's seat is derived from its type, so a module that claims the seat
|
|
// cannot leave the dependency out. A compiled seat carries no needs (novox/hq ADR 0378 §8).
|
|
func neededBesideClaims(catalogue map[string]Manifest, m Manifest) []string {
|
|
var out []string
|
|
for _, seat := range nodeSeatsClaimed(m) {
|
|
for _, candidate := range catalogue {
|
|
for _, s := range candidate.DefinesSeats {
|
|
if s.Name == seat {
|
|
out = append(out, s.Needs...)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Unheld is one dependency of one module on a node that nothing on that node holds.
|
|
type Unheld struct {
|
|
Node string `json:"node"`
|
|
Module string `json:"module"`
|
|
Seat string `json:"seat"`
|
|
// Holders are the modules in the catalogue that could hold the seat: assigning one meets it.
|
|
Holders []string `json:"holders"`
|
|
}
|
|
|
|
// String is the line a refusal and a report both say, so the two never drift.
|
|
func (u Unheld) String() string {
|
|
remedy := "and no module in the catalogue claims it yet"
|
|
if len(u.Holders) > 0 {
|
|
remedy = "— assign one that holds it: " + strings.Join(u.Holders, ", ")
|
|
}
|
|
return fmt.Sprintf("%s on %s depends on %s, which nothing on %s holds (novox/hq ADR 0207) %s",
|
|
u.Module, u.Node, u.Seat, u.Node, remedy)
|
|
}
|
|
|
|
// nodeSeatsClaimed is every node seat a module claims, each by its current name (ADR 0122), so
|
|
// a dependency on any of them — a resource's or a contribution's — is met by the claim.
|
|
func nodeSeatsClaimed(m Manifest) []string {
|
|
var out []string
|
|
for _, c := range m.Claims {
|
|
if c.At() != ScopeNode {
|
|
continue
|
|
}
|
|
name := c.Name
|
|
if s, known := SeatNamed(name); known {
|
|
name = s.Name
|
|
}
|
|
out = append(out, name)
|
|
}
|
|
return out
|
|
}
|