Keep a left-out module's provisions and backups, and refuse more identity keys (hq ADR 0262)
A module left out for an unknown key inside an entry lost its whole manifest, so every consumer of what it provides was refused and its data stopped being copied. Read past only the unknown key, keep its backup lines, and say in the condition what stops.
This commit is contained in:
@@ -223,7 +223,8 @@ func TestANodeCalledDefaultIsANodesLayer(t *testing.T) {
|
||||
func TestAKeyIsTheOperatorsByWhatItIsAbout(t *testing.T) {
|
||||
for _, key := range []string{"max-tokens", "show-hostname", "ghost-opacity", "users-per-page", "mailbox-size",
|
||||
"font-size", "width", "keyboard-delay", "ipv6-preferred", "client-width", "user-agent", "url-timeout",
|
||||
"site-title", "cert-renewal-days", "name", "font-name"} {
|
||||
"site-title", "cert-renewal-days", "name", "font-name", "allow-resize", "use-gpu", "disable-sender-check",
|
||||
"max-recipients", "gateway-timeout", "sender-delay"} {
|
||||
if w := operatorsWord(key); w != "" {
|
||||
t.Errorf("%s read as the operator's (%s)", key, w)
|
||||
}
|
||||
@@ -235,7 +236,12 @@ func TestAKeyIsTheOperatorsByWhatItIsAbout(t *testing.T) {
|
||||
"allowed-hosts": "host", "admin-emails": "email", "tokens": "token", "hostname": "hostname",
|
||||
"apikey": "apikey", "servername": "servername", "tls-cert": "cert", "site": "site", "timezone": "timezone",
|
||||
"bearer": "bearer", "bind-ipv4": "ipv4", "listen-ipv6": "ipv6", "site-name": "site-name",
|
||||
"server-name": "server-name", "public-name": "public-name", "smtp-relay": "smtp-relay"} {
|
||||
"server-name": "server-name", "public-name": "public-name", "smtp-relay": "smtp-relay",
|
||||
"host-name": "host-name", "user-name": "user-name", "domain-name": "domain-name",
|
||||
"nameserver": "nameserver", "upstream-nameservers": "nameserver", "dns-server": "dns-server",
|
||||
"dns-servers": "dns-server", "default-gateway": "gateway", "lan-subnet": "subnet", "sender": "sender",
|
||||
"notify-recipients": "recipient", "contact": "contact", "tls-certificate": "certificate",
|
||||
"allow-from": "allow-from", "deny-from": "deny-from", "allow-hosts": "host", "use-host": "host"} {
|
||||
if w := operatorsWord(key); w != word {
|
||||
t.Errorf("%s: read %q, want %q", key, w, word)
|
||||
}
|
||||
@@ -332,3 +338,67 @@ func TestAStoredManifestWithAnUnknownKeyIsLeftOutAndRegistrationRefusesIt(t *tes
|
||||
t.Fatal("a malformed stored manifest was read")
|
||||
}
|
||||
}
|
||||
|
||||
// A module left out for a key this controller does not know, inside an entry, is read past that key
|
||||
// alone: it still provides what it provides, its other entries are whole, and a key of the same name
|
||||
// that another entry knows is kept (novox/hq ADR 0262).
|
||||
func TestALeftOutModuleStillProvidesWhatItProvides(t *testing.T) {
|
||||
var m Manifest
|
||||
raw := `{"module": "later", "version": "2",
|
||||
"provides": [{"name": "db", "scope": "mesh"}, {"name": "cache", "a-field-from-later": 1}],
|
||||
"state": [{"name": "s", "history": 3}],
|
||||
"data": {"own": [{"id": "d", "path": "${dir:d}", "class": "valuable", "backup": {"dump": "x", "into": "d", "class": "later"}}]},
|
||||
"resources": [{"id": "d", "type": "directory", "mode": "0700"}]}`
|
||||
if err := json.Unmarshal([]byte(raw), &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(m.Provides) != 2 || m.Provides[0].Name != "db" || m.Provides[1].Name != "cache" {
|
||||
t.Fatalf("provides: %+v", m.Provides)
|
||||
}
|
||||
if len(m.State) != 1 || m.State[0].History != 3 {
|
||||
t.Fatalf("state: %+v", m.State)
|
||||
}
|
||||
if m.Data == nil || len(m.Data.Own) != 1 || m.Data.Own[0].Class != "valuable" {
|
||||
t.Fatalf("data: the item's own class was taken for the backup's unknown one: %+v", m.Data)
|
||||
}
|
||||
for _, want := range []string{"provides[1].a-field-from-later", "data.own[0].backup.class"} {
|
||||
if !strings.Contains(m.UnknownField(), want) {
|
||||
t.Errorf("unknown %q does not say it read without %s", m.UnknownField(), want)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(UnknownFieldReason(m), "left out") {
|
||||
t.Fatal(UnknownFieldReason(m))
|
||||
}
|
||||
}
|
||||
|
||||
// A left-out module's data is still copied: the backup holder on its machine keeps its lines while every
|
||||
// other thing of it is left out.
|
||||
func TestALeftOutModulesDataIsStillBackedUp(t *testing.T) {
|
||||
var later Manifest
|
||||
if err := json.Unmarshal([]byte(`{"module": "later", "version": "2", "a-field-from-later": 1,
|
||||
"resources": [{"id": "d", "type": "directory", "mode": "0700"}, {"id": "rc", "type": "file", "path": "/etc/later.conf", "content": "x\n"}],
|
||||
"data": {"own": [{"id": "d", "path": "${dir:d}", "class": "valuable"}]}}`), &later); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
holder := Manifest{Module: "backups", Version: "1",
|
||||
Claims: []Claim{{Name: BackupSeat, Scope: ScopeNode}},
|
||||
Resources: []map[string]any{{"id": "list", "type": "file", "path": "/etc/backups.list", "mode": "0644",
|
||||
"content": "${contribution:" + BackupSeat + ":backup}"}}}
|
||||
r := anAdoptedAnchor()
|
||||
r.Modules = append(r.Modules, holder, later)
|
||||
composed, err := r.Compose(anchorRendering(false))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, left := composed.LeftOut["later"]; !left {
|
||||
t.Fatalf("not left out: %v", composed.LeftOut)
|
||||
}
|
||||
got := byID(composed.Resources)
|
||||
if _, declared := got["later.rc"]; declared {
|
||||
t.Fatal("the left-out module's file is still declared")
|
||||
}
|
||||
list, _ := got["backups.list"]["content"].(string)
|
||||
if !strings.Contains(list, "# later") || !strings.Contains(list, "path /var/lib/later/d") {
|
||||
t.Fatalf("the left-out module's data is no longer backed up:\n%s", list)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user