package catalogue import ( "strings" "testing" ) // novox/hq issue 231 — a misspelled placeholder is written out as text. // // The four placeholders of the issue, in one file: two misspelled namespaces, a setting key no // definition could declare, and the shell's own syntax. The first three are refused by name, at the // catalogue check and at composition; the fourth reaches the file as written. const ( misspelledShell = "${shel:zsh:first}" undeclaredSetting = "${setting:Undeclared}" misspelledMachine = "${machnie:address}" shellsOwn = "${XDG_CACHE_HOME:-x}" // The shell's operators after a word the mesh also uses, in any case: the shell's, and passed. shellsOperators = "${PORT:-8080} ${SHELL:-/bin/sh} ${SECRET:?unset} ${dir:-/tmp}" ) // The catalogue check — the strict parse registration runs too — refuses each by name, with the // module and the field it stands in, and says nothing about the shell's own syntax. func TestAMisspelledPlaceholderIsRefusedAtTheCheck(t *testing.T) { raw := `{"module":"speller","version":"1","resources":[{"id":"rc","type":"file","path":"/etc/speller.rc",` + `"content":"a=` + misspelledShell + `\nb=` + undeclaredSetting + `\nc=` + misspelledMachine + `\nd=` + shellsOwn + `\n"}]}` _, err := ParseManifest([]byte(raw)) if err == nil { t.Fatal("a file holding three placeholders no pass consumes was accepted") } for _, token := range []string{misspelledShell, undeclaredSetting, misspelledMachine} { if !strings.Contains(err.Error(), "speller's resource rc holds "+token+" in its content") { t.Errorf("the refusal does not name %s with its module and field: %v", token, err) } } if strings.Contains(err.Error(), "XDG_CACHE_HOME") { t.Errorf("the shell's own syntax was refused: %v", err) } // A namespace the mesh knows, misspelt in any way its own pattern does not take, and the same // namespace in a field its pass does not read: refused at the check as at composition. for _, c := range []struct{ resource, token, field string }{ {`{"id":"rc","type":"file","path":"/etc/rc","content":"${Machine:address}"}`, "${Machine:address}", "content"}, {`{"id":"rc","type":"file","path":"/etc/rc","content":"${machine:.address}"}`, "${machine:.address}", "content"}, {`{"id":"rc","type":"file","path":"/etc/rc","content":"${machine: address}"}`, "${machine: address}", "content"}, {`{"id":"rc","type":"file","path":"/etc/rc","content":"${Dir:x}"}`, "${Dir:x}", "content"}, {`{"id":"rc","type":"file","path":"/etc/rc","content":"a=${machine:address\nb=1"}`, "${machine:address", "content"}, {`{"id":"rc","type":"process","name":"speller","env":{"NAME":"${machine:name}"}}`, "${machine:name}", "env.NAME"}, } { raw := `{"module":"speller","version":"1","resources":[` + c.resource + `]}` _, err := ParseManifest([]byte(raw)) if err == nil || !strings.Contains(err.Error(), "speller's resource rc holds "+c.token+" in its "+c.field) { t.Errorf("%s in its %s was accepted at the check, or not named: %v", c.token, c.field, err) } } // And the shell's syntax alone, beside placeholders every pass knows, is accepted — as is the // shape a lower-case shell variable takes with an operator after its colon. raw = `{"module":"speller","version":"1","resources":[{"id":"rc","type":"file","path":"${machine:account-home}/.rc",` + `"content":"` + shellsOwn + ` ${(%):-%n} ${1:-.} ${count:-} ${trial:+ on trial} ${machine:address} ` + shellsOperators + `\n"},` + `{"id":"server","type":"container","name":"server","env":{"PORT":"${PORT:-80}"}}]}` if _, err := ParseManifest([]byte(raw)); err != nil { t.Fatalf("the shell's own syntax was refused: %v", err) } } // Composition refuses the same placeholders in the same words — a manifest the store already holds // was never parsed by this binary — and a namespace the mesh knows, written in a field its pass does // not read, is refused there too, because it would reach the machine as the same literal text. func TestAMisspelledPlaceholderIsRefusedAtComposition(t *testing.T) { for _, c := range []struct { field string r map[string]any token string }{ {"content", map[string]any{"id": "rc", "type": "file", "path": "/etc/speller.rc", "content": "a=" + misspelledShell + "\n"}, misspelledShell}, {"content", map[string]any{"id": "rc", "type": "file", "path": "/etc/speller.rc", "content": "b=" + undeclaredSetting + "\n"}, undeclaredSetting}, {"content", map[string]any{"id": "rc", "type": "file", "path": "/etc/speller.rc", "content": "c=" + misspelledMachine + "\n"}, misspelledMachine}, {"env.NAME", map[string]any{"id": "rc", "type": "process", "name": "speller", "env": map[string]any{"NAME": "${machine:name}"}}, "${machine:name}"}, {"content", map[string]any{"id": "rc", "type": "file", "path": "/etc/speller.rc", "content": "${Machine:address}"}, "${Machine:address}"}, {"content", map[string]any{"id": "rc", "type": "file", "path": "/etc/speller.rc", "content": "${machine:.address}"}, "${machine:.address}"}, {"content", map[string]any{"id": "rc", "type": "file", "path": "/etc/speller.rc", "content": "${machine: address}"}, "${machine: address}"}, {"content", map[string]any{"id": "rc", "type": "file", "path": "/etc/speller.rc", "content": "${Dir:x}"}, "${Dir:x}"}, {"content", map[string]any{"id": "rc", "type": "file", "path": "/etc/speller.rc", "content": "a=${machine:address\nb=1"}, "${machine:address"}, } { m := Manifest{Module: "speller", Version: "1", Resources: []map[string]any{c.r}} r := Resolution{Node: "workstation", Account: "op", Modules: []Manifest{m}} _, err := r.Declaration(Rendering{}) if err == nil || !strings.Contains(err.Error(), "speller's resource rc holds "+c.token+" in its "+c.field) { t.Errorf("%s in its %s was composed rather than refused by name: %v", c.token, c.field, err) } } m := Manifest{Module: "speller", Version: "1", Resources: []map[string]any{ {"id": "rc", "type": "file", "path": "/etc/speller.rc", "content": "d=" + shellsOwn + " " + shellsOperators + "\n"}, {"id": "server", "type": "process", "name": "server", "env": map[string]any{"PORT": "${PORT:-80}"}}, }} out, err := Resolution{Node: "workstation", Account: "op", Modules: []Manifest{m}}.Declaration(Rendering{}) if err != nil { t.Fatalf("the shell's own syntax was refused: %v", err) } if got := contentOf(t, out, "speller.rc"); got != "d="+shellsOwn+" "+shellsOperators+"\n" { t.Fatalf("the shell's own syntax did not pass through as written: %q", got) } } // contentOf is the content of the composed resource with this id, failing when it was not composed. func contentOf(t *testing.T, out []map[string]any, id string) string { t.Helper() for _, res := range out { if res["id"] == id { return plainly(res["content"]) } } t.Fatalf("%s was not composed: %v", id, out) return "" } // What a value puts into a file is its software's text, not the definition's, and is never swept // (review of mesh-controller #211): an operator's setting holding `${labels:instance}` filled through // ${setting:…}, and a JSON setting `${level:upper}` merged into a mergeable file, reach the machine as // set — software that templates its own configuration (Log4j's `${env:…}`, Spring's `${timeout:30}`) // is configured exactly this way. func TestAValueHoldingAPlaceholderShapeComposesUnchanged(t *testing.T) { m := Manifest{Module: "templater", Version: "1", Resources: []map[string]any{ {"id": "conf", "type": "file", "path": "/etc/templater.conf", "content": "template=${setting:template}\n"}, {"id": "json", "type": "file", "path": "/etc/templater.json", "merge": MergeJSON, "content": `{"fmt":"plain"}`}, }} settings := SettingsBy{"templater": {{From: "the operator", Values: map[string]any{ "template": "${labels:instance}", "fmt": "${level:upper}", }}}} out, err := Resolution{Node: "workstation", Account: "op", Modules: []Manifest{m}}.Declaration(Rendering{Settings: settings}) if err != nil { t.Fatalf("a value holding a placeholder's shape was refused as the definition's: %v", err) } if got := contentOf(t, out, "templater.conf"); got != "template=${labels:instance}\n" { t.Errorf("the setting did not reach the file as set: %q", got) } if got := contentOf(t, out, "templater.json"); !strings.Contains(got, `${level:upper}`) { t.Errorf("the merged setting did not reach the file as set: %q", got) } } // Contributed shell code is the shell's, and no pass reads it (novox/hq ADR 0204): zsh's own // `${path:t}` is namespace-shaped, and reaches the holder's file untouched. func TestContributedShellCodeIsNotSwept(t *testing.T) { modules := []Manifest{zshHolder(), {Module: "modifier", Shell: []ShellCode{ {For: "zsh", Slot: "normal", Code: "echo ${path:t} ${shel:zsh:first}"}, }}} out, err := Resolution{Node: "workstation", Account: "op", Modules: modules}.Declaration(Rendering{}) if err != nil { t.Fatalf("contributed shell code was swept: %v", err) } if got := contentOf(t, out, "zsh.zshrc"); !strings.Contains(got, "echo ${path:t} ${shel:zsh:first}") { t.Fatalf("the contributed code did not reach the holder's file as written: %q", got) } }