Files
mesh-catalog/modules/mounts/cmd/mounts/manifest_test.go
T
jschoubben 35ff67bda0
mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
Design 47's last milestones: the keeper, self-check, healers, hand-acts, provisioner, vault, artifact sweep, disk readings, credential forms (issues 774, 1952, 1953, 1954, 1955)
One commit with exactly the tree of the reviewed head 35b4509c0a, on main 12c2574c9d, so two compiled programs added and removed on the branch leave no trace in history (issue 1577).
2026-10-30 02:21:51 +01:00

164 lines
6.6 KiB
Go

package main
import (
"encoding/json"
"os"
"regexp"
"sort"
"strings"
"testing"
)
type manifest struct {
Tools []string `json:"tools"`
Requires []string `json:"requires"`
Reads []string `json:"reads"`
Invokes []string `json:"invokes"`
Claims []map[string]any `json:"claims"`
State []map[string]any `json:"state"`
Config map[string]map[string]any `json:"config"`
OwnSecrets map[string]map[string]string `json:"own-secrets"`
Resources []map[string]any `json:"resources"`
}
func readManifest(t *testing.T) (manifest, string) {
t.Helper()
raw, err := os.ReadFile("../../module.json")
if err != nil {
t.Fatal(err)
}
var m manifest
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
// And the text of each file it places, kept in files/ as its template (novox/hq ADR 0349 §2): what the
// machine is given is judged as a whole.
text := string(raw)
for _, r := range m.Resources {
if _, has := r["template"]; has {
text += templateText(t, r)
}
}
return m, text
}
// The tools the bundle serves are the manifest's tools and the seat's verbs, each saying whether it reads
// or acts; the module requires the share, asks the server's seat and holds node-mounts.
func TestTheManifestNamesWhatTheBundleServesAndWhatItNeeds(t *testing.T) {
m, raw := readManifest(t)
var tools, verbs []string
for _, tl := range Tools(newSystem(t).machine()) {
if !strings.HasSuffix(tl.Description, "(r)") && !strings.HasSuffix(tl.Description, "(a)") {
t.Errorf("%s does not say whether it reads (r) or acts (a)", tl.Name)
}
if v, ok := strings.CutPrefix(tl.Name, Seat+"."); ok {
verbs = append(verbs, v)
} else {
tools = append(tools, tl.Name)
}
}
sort.Strings(tools)
sort.Strings(m.Tools)
if strings.Join(tools, ",") != strings.Join(m.Tools, ",") {
t.Errorf("served %v, listed %v", tools, m.Tools)
}
if len(m.Claims) != 1 || m.Claims[0]["name"] != Seat || m.Claims[0]["scope"] != "node" {
t.Fatalf("claims %v", m.Claims)
}
var serves []string
for _, v := range m.Claims[0]["serves"].([]any) {
serves = append(serves, v.(string))
}
sort.Strings(serves)
sort.Strings(verbs)
if strings.Join(serves, ",") != strings.Join(verbs, ",") || strings.Join(verbs, ",") != "adopt,list,mount,test,unmount" {
t.Errorf("the seat serves %v, the bundle %v", serves, verbs)
}
if strings.Join(m.Requires, ",") != "nfs-share" {
t.Errorf("requires %v", m.Requires)
}
// It reads nfs-server's exports, and disk-load's readings for where the mesh keeps data on this machine (review
// finding 4d, ADR 0571 clause 3); it calls no verb.
if strings.Join(m.Reads, ",") != "disk-load.readings,"+ExportsState || len(m.Invokes) != 0 {
t.Errorf("reads %v, invokes %v", m.Reads, m.Invokes)
}
if !strings.Contains(raw, `node=${machine:name}`) {
t.Error("the config gives the process this machine's name, so it never mounts a share onto the machine that serves it")
}
if len(m.State) != 1 || m.State[0]["name"] != SharesState || m.State[0]["per-machine"] != true {
t.Errorf("state %v", m.State)
}
// Each SMB source's password is its own one-line secret, a member of a family the mesh never makes a value
// for (novox/hq ADR 0283), placed where the process looks for it.
sec := m.OwnSecrets["smb-password-*"]
if len(m.OwnSecrets) != 1 || sec["issued-by"] != "outside" || sec["path"] != "${dir:state}/"+PasswordSecret("*")+".secret" {
t.Errorf("the SMB passwords are an outside party's, one secret per source: %v", m.OwnSecrets)
}
if got := PasswordFile("${dir:state}", "games"); got != strings.Replace(sec["path"], "*", "games", 1) {
t.Errorf("the process reads %s, the mesh places %s", got, sec["path"])
}
if _, has := m.Config["smb-users"]; has || len(m.Config) != 2 {
t.Errorf("an SMB source's username is in its entry of sources, not a config value of its own: %v", m.Config)
}
for _, key := range []string{"shares", "sources"} {
s := m.Config[key]
if s["type"] != "string" || s["default"] != None || s["description"] == "" || s["trusted"] != true ||
!strings.Contains(raw, "${config:"+key+"}") {
t.Errorf("config value %s: %v", key, s)
}
}
// A machine's or a person's name, as a word: "novox/hq" names the record, not the machine.
if hit := regexp.MustCompile(`\b(g14|novox|ace|shanks|jochen|recalbox)\b|/home/|/storage`).FindString(strings.ReplaceAll(raw, "novox/hq", "")); hit != "" {
t.Errorf("the manifest says %q", hit)
}
if strings.Contains(raw, "/etc/fstab") {
t.Error("the manifest places nothing in /etc/fstab: the operator's lines are taken over only by adopt")
}
}
// Every long-running resource says how it is healthy (ADR 0240): apply by its unit, watch by mounts_health,
// both as root; the config file the process reads is the one the manifest places.
func TestTheProcessesAreRootAndJudgedByTheirUnitAndTheHealthTool(t *testing.T) {
m, raw := readManifest(t)
byID := map[string]map[string]any{}
for _, r := range m.Resources {
byID[r["id"].(string)] = r
if r["type"] == "process" || r["type"] == "service" || r["type"] == "container" {
if r["health"] == nil {
t.Errorf("%s runs and does not say how it is healthy", r["id"])
}
}
}
apply, watch := byID["apply"], byID["watch"]
if h, _ := apply["health"].(map[string]any); apply["user"] != nil || h["kind"] != "unit" {
t.Errorf("apply runs as root and is judged by its unit: %v", apply)
}
if h, _ := watch["health"].(map[string]any); watch["user"] != nil || h["kind"] != "tool" || h["tool"] != "mounts_health" {
t.Errorf("watch runs as root and is judged by mounts_health: %v", watch)
}
for id, mode := range map[string]string{"apply": "apply", "watch": "watch"} {
if run, _ := byID[id]["run"].([]any); len(run) != 2 || run[1] != mode {
t.Errorf("%s runs the bundle's %s: %v", id, mode, byID[id]["run"])
}
}
if byID["config"]["path"] != ConfigFile {
t.Errorf("the config file is %v, the code reads %s", byID["config"]["path"], ConfigFile)
}
content := templateText(t, byID["config"])
cfg := ParseConfig(content)
if len(cfg.Problems) != 0 && !strings.Contains(strings.Join(cfg.Problems, " "), "${") {
t.Errorf("the config's template reads: %v", cfg.Problems)
}
for _, want := range []string{"server=${bound:nfs-share:from}", "at=${bound:nfs-share:at}", "passwords=${dir:state}"} {
if !strings.Contains(content, want) {
t.Errorf("the config lacks %s", want)
}
}
if byID["bus-dir"]["path"] != BusDir || byID["bus-dir"]["owner"] != "${machine:account}" {
t.Errorf("the bundle, as the operator account, writes the bus's answer: %v", byID["bus-dir"])
}
if strings.Contains(raw, "password=") {
t.Error("the manifest holds no password")
}
}