A module's containers that declare identity run as an identity the controller allocates once, mesh-wide, from 50000-59999; each access names its users and whether its folder is taken; an access wait is excused for a folder the mesh may not keep yet, and the operator is asked once per folder and machine, the folder taken only on the warrant. All of it is sent only to an engine at contract 5. A placement for an access a newer build brings is judged against that build (issue 575). mesh-host pinned to the ADR 0325 branch head.
288 lines
11 KiB
Go
288 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// moduleCheck judges manifests where they are written, with no mesh (novox/hq ADR 0037, issue 148).
|
|
//
|
|
// **The same functions registration runs, and nothing the command line adds** (ADR 0035): the strict
|
|
// parse with every per-manifest problem, then the rules no single manifest can be judged against,
|
|
// over exactly the manifests given. Somebody describing their own application in their own
|
|
// repository runs this before pushing and finds out there, rather than when a running mesh refuses
|
|
// the registration or, later, when a machine applies something that resolved and should not have.
|
|
//
|
|
// **What it cannot know without a store, it says.** The mesh's own seat set is the store's (ADR
|
|
// 0122); this binary carries a compiled copy that the store overrides when loaded, so a claim on a
|
|
// mesh seat is judged fully only at registration. A seat another module declares is unknown unless
|
|
// that module's manifest is passed too. Both are printed as a note, not as a problem — a check that
|
|
// refused what it could not see would teach people to ignore it.
|
|
//
|
|
// **And every identity against every bound it meets** (novox/hq ADR 0225, issue 263): each module's
|
|
// identity, on a machine whose name is `longestMachine` characters, against the bound of every
|
|
// provision it wants that a manifest given here offers. An overflow is refused in the pull request
|
|
// that introduces it — a new requirement, a lowered bound, a longer slug — instead of on the
|
|
// provider's machine when a real machine's name first meets the module's.
|
|
// SomeManifestsVar, set by the merge gate, says the manifests given are only some of their repository's.
|
|
const SomeManifestsVar = "MESH_MODULE_CHECK_SOME"
|
|
|
|
func moduleCheck(paths []string, out io.Writer) error {
|
|
return moduleCheckFor(paths, catalogue.DefaultLongestMachine, out)
|
|
}
|
|
|
|
func moduleCheckFor(paths []string, longestMachine int, out io.Writer) error {
|
|
if len(paths) == 0 {
|
|
return errors.New("module check <manifest.json>... — one file per module; pass every " +
|
|
"manifest of a repository together so the rules between them are checked too")
|
|
}
|
|
shelf := catalogue.Shelf{}
|
|
faulted := map[string]bool{}
|
|
failed := 0
|
|
for _, path := range paths {
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
fmt.Fprintf(out, "%s: %v\n", path, err)
|
|
failed++
|
|
continue
|
|
}
|
|
m, err := catalogue.ParseManifest(raw)
|
|
if err != nil {
|
|
fmt.Fprintf(out, "%s: %v\n", path, err)
|
|
failed++
|
|
continue
|
|
}
|
|
if first, twice := shelf[m.Module]; twice {
|
|
_ = first
|
|
fmt.Fprintf(out, "%s: %s was already given; two manifests name one module\n", path, m.Module)
|
|
failed++
|
|
continue
|
|
}
|
|
// A definition names no installation (novox/hq ADR 0112, ADR 0155): judged here, in the
|
|
// catalogue-wide test, and at registration, which refuses in the same words.
|
|
if wrong := catalogue.TrustProblems(m); len(wrong) > 0 {
|
|
for _, p := range wrong {
|
|
fmt.Fprintf(out, "%s: %s\n", path, p)
|
|
}
|
|
failed += len(wrong)
|
|
faulted[m.Module] = true
|
|
}
|
|
if named := catalogue.InstallationProblems(m); len(named) > 0 {
|
|
for _, p := range named {
|
|
fmt.Fprintf(out, "%s: %s\n", path, p)
|
|
}
|
|
failed += len(named)
|
|
faulted[m.Module] = true
|
|
}
|
|
shelf[m.Module] = m
|
|
}
|
|
|
|
// Between the manifests: a seat declared twice, a use of a seat nothing declares, a claim on
|
|
// a seat that does not exist. Run only over what parsed, because a problem inside one manifest
|
|
// has already been said and would be said again here in a worse form.
|
|
//
|
|
// **Over some of a repository's manifests, a seat none of them declares is a note** (novox/hq issue 364), as
|
|
// this command's own word says above: the merge gate passes only the manifests a change touches, and says so
|
|
// with SomeManifestsVar, so a module that uses or claims a seat another module declares (the operator
|
|
// channel's, a channel bench) was refused there for a manifest it was not given. Given every manifest — the
|
|
// catalogue's own check, and registration — it stays a refusal.
|
|
some := os.Getenv(SomeManifestsVar) != ""
|
|
problems := catalogue.CatalogueProblems(shelf)
|
|
sort.Strings(problems)
|
|
for _, p := range problems {
|
|
if some && catalogue.IsUndeclaredSeat(p) {
|
|
fmt.Fprintf(out, "note: %s among the manifests given; registration judges it against the whole catalogue, "+
|
|
"and passing the declaring module's manifest too judges it here\n", p)
|
|
continue
|
|
}
|
|
fmt.Fprintln(out, p)
|
|
failed++
|
|
}
|
|
|
|
// Between the manifests too: an identity against the bounds of the provisions it wants, which
|
|
// only the provider's manifest states.
|
|
identities := catalogue.IdentityProblems(shelf, longestMachine)
|
|
sort.Strings(identities)
|
|
for _, p := range identities {
|
|
fmt.Fprintln(out, p)
|
|
}
|
|
failed += len(identities)
|
|
|
|
// And the data each module keeps (novox/hq ADR 0233): a provider that grants says what it keeps for
|
|
// its consumers, a directory a container writes is declared, and no backup line is written by hand.
|
|
data := catalogue.DataProblems(shelf)
|
|
sort.Strings(data)
|
|
for _, p := range data {
|
|
fmt.Fprintln(out, p)
|
|
}
|
|
failed += len(data)
|
|
|
|
var names []string
|
|
for name := range shelf {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
|
|
// **Every long-running resource says how it is ready** (novox/hq ADR 0240 rule 8): warned until the
|
|
// date, refused from it. The count is the catalogue's: its merge check keeps the number and lets a
|
|
// change lower it, never raise it.
|
|
undeclared := 0
|
|
required := !checkNow().Before(catalogue.HealthRequiredFrom)
|
|
for _, name := range names {
|
|
missing := catalogue.Undeclared(shelf[name])
|
|
undeclared += len(missing)
|
|
if len(missing) == 0 {
|
|
continue
|
|
}
|
|
if required {
|
|
for _, id := range missing {
|
|
fmt.Fprintf(out, "%s: %s stays up and does not say how it is ready: a long-running resource declares "+
|
|
"health since %s (novox/hq ADR 0240 rule 8)\n", name, id, catalogue.HealthRequiredFrom.Format("2006-01-02"))
|
|
}
|
|
failed += len(missing)
|
|
faulted[name] = true
|
|
}
|
|
}
|
|
|
|
// **What ADR 0325 refuses once a module runs as its own identity is warned of until then** (novox/hq ADR 0325 §1–§3):
|
|
// a directory naming its owner, a container declaring no identity that mounts the module's own directory or a
|
|
// read-write access. Printed, never counted: every registered container module does one of them today, and the
|
|
// catalogues migrate against these lines. A module that declares identity is refused them instead (ParseManifest).
|
|
for _, name := range names {
|
|
for _, w := range catalogue.IdentityWarnings(shelf[name]) {
|
|
fmt.Fprintf(out, "warning: %s\n", w)
|
|
}
|
|
}
|
|
|
|
// **A file that asks for a setting and does not say whether it is trusted counts as trusted** (novox/hq issue
|
|
// 339): listed and counted, never refused, so an author can opt out a file nothing trusts.
|
|
unsaid := 0
|
|
for _, name := range names {
|
|
unsaid += len(catalogue.UnsaidTrust(shelf[name]))
|
|
}
|
|
|
|
for _, name := range names {
|
|
m := shelf[name]
|
|
if faulted[name] {
|
|
continue
|
|
}
|
|
fmt.Fprintf(out, "%s: ok", name)
|
|
if n := len(m.Tools); n > 0 {
|
|
fmt.Fprintf(out, ", %d tool(s)", n)
|
|
}
|
|
if len(m.Invokes) > 0 {
|
|
fmt.Fprintf(out, ", invokes %s", joinInvokes(m.Invokes))
|
|
}
|
|
// The state it keeps and reads (novox/hq ADR 0201), so a reviewer sees what lands on the bus.
|
|
if len(m.State) > 0 {
|
|
kept := make([]string, 0, len(m.State))
|
|
for _, s := range m.State {
|
|
kept = append(kept, s.Name)
|
|
}
|
|
fmt.Fprintf(out, ", keeps state %s", strings.Join(kept, ", "))
|
|
}
|
|
// And the logs it keeps, with their caps (novox/hq ADR 0297).
|
|
if len(m.Logs) > 0 {
|
|
kept := make([]string, 0, len(m.Logs))
|
|
for _, l := range m.Logs {
|
|
kept = append(kept, fmt.Sprintf("%s (%d MiB)", l.Name, l.Cap()))
|
|
}
|
|
fmt.Fprintf(out, ", keeps log %s", strings.Join(kept, ", "))
|
|
}
|
|
if len(m.Reads) > 0 {
|
|
fmt.Fprintf(out, ", reads %s", strings.Join(m.Reads, ", "))
|
|
}
|
|
// The data it keeps, by class, so a reviewer sees what the mesh will protect and how.
|
|
if items := m.DataItems(); len(items) > 0 {
|
|
kept := make([]string, 0, len(items))
|
|
for _, it := range items {
|
|
kept = append(kept, it.ID+" ("+it.Class+")")
|
|
}
|
|
fmt.Fprintf(out, ", keeps %s", strings.Join(kept, ", "))
|
|
}
|
|
// How what it runs is ready (ADR 0240): each declared check, and what is judged by liveness alone.
|
|
var checks []string
|
|
for _, r := range m.Resources {
|
|
if h, has, _ := catalogue.ReadHealth(r); has {
|
|
checks = append(checks, fmt.Sprintf("%v by %s", r["id"], catalogue.HealthWords(h)))
|
|
}
|
|
}
|
|
if len(checks) > 0 {
|
|
fmt.Fprintf(out, ", ready: %s", strings.Join(checks, "; "))
|
|
}
|
|
if missing := catalogue.UnsaidTrust(m); len(missing) > 0 {
|
|
fmt.Fprintf(out, "; WARNING: %s ask(s) for a setting and do(es) not say whether it is trusted, so it counts as "+
|
|
"trusted: set at the terminal alone; say %q false where nothing trusts it (novox/hq issue 339)",
|
|
strings.Join(missing, ", "), catalogue.TrustedField)
|
|
}
|
|
if missing := catalogue.Undeclared(m); len(missing) > 0 {
|
|
fmt.Fprintf(out, "; WARNING: %s stay(s) up and say(s) not how it is ready — judged by liveness alone, "+
|
|
"refused from %s (ADR 0240 rule 8)", strings.Join(missing, ", "), catalogue.HealthRequiredFrom.Format("2006-01-02"))
|
|
}
|
|
fmt.Fprintln(out)
|
|
}
|
|
// The count the catalogue keeps (ADR 0240 rule 8), in a line its merge check reads.
|
|
fmt.Fprintf(out, "%s %d\n", UndeclaredHealthLine, undeclared)
|
|
fmt.Fprintf(out, "%s %d\n", UnsaidTrustLine, unsaid)
|
|
if failed > 0 {
|
|
return fmt.Errorf("%d problem(s) in %d manifest(s)", failed, len(paths))
|
|
}
|
|
fmt.Fprintf(out, "%d manifest(s) checked. Judged against the seats this binary carries; a claim on "+
|
|
"one of the mesh's own seats is judged fully at registration, and a seat declared by a "+
|
|
"module not given here reads as unknown. Identities judged on a %d-character machine name, "+
|
|
"against the bounds of the providers given here\n", len(paths), longestMachine)
|
|
return nil
|
|
}
|
|
|
|
// UndeclaredHealthLine starts the line `module check` says the count of long-running resources without
|
|
// `health` in, over the manifests given: the catalogue's merge check compares it with the number it keeps.
|
|
const UndeclaredHealthLine = "long-running resources without health:"
|
|
|
|
// UnsaidTrustLine starts the line `module check` says the count of files that ask for a setting and do not say
|
|
// whether it is trusted, and so count as trusted (novox/hq issue 339).
|
|
const UnsaidTrustLine = "files asking for a setting without saying whether it is trusted:"
|
|
|
|
// checkNow is the clock `module check` judges the date by; a test sets it.
|
|
var checkNow = time.Now
|
|
|
|
func joinInvokes(invokes []string) string {
|
|
if len(invokes) == 1 && invokes[0] == "*" {
|
|
return "every tool"
|
|
}
|
|
s := ""
|
|
for i, t := range invokes {
|
|
if i > 0 {
|
|
s += ", "
|
|
}
|
|
s += t
|
|
}
|
|
return s
|
|
}
|
|
|
|
// manifestsUnder lists every module.json below a directory, for `module check <dir>`.
|
|
func manifestsUnder(dir string) ([]string, error) {
|
|
var found []string
|
|
err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if d.IsDir() && (d.Name() == "node_modules" || d.Name() == ".git" || d.Name() == "dist") {
|
|
return filepath.SkipDir
|
|
}
|
|
if !d.IsDir() && d.Name() == "module.json" {
|
|
found = append(found, path)
|
|
}
|
|
return nil
|
|
})
|
|
sort.Strings(found)
|
|
return found, err
|
|
}
|