Feature 57 (config and provisioning): modules declare config and provisions, their files rendered from templates; synced with main (issues of feature 57, 874)

This commit is contained in:
2026-10-16 07:37:43 +00:00
parent 1a2c917abf
commit 9652b2d5ce
771 changed files with 52063 additions and 1774 deletions
+60 -5
View File
@@ -28,6 +28,11 @@ type manifest struct {
Environment struct {
Variables map[string]string `json:"variables"`
} `json:"environment"`
Items []struct {
Collection string `json:"collection"`
Attributes map[string]string `json:"attributes"`
Template string `json:"template"`
} `json:"items"`
Shell []struct {
For, Slot, Code string
} `json:"shell"`
@@ -46,6 +51,23 @@ func readManifest(t *testing.T) manifest {
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
// The module's code and variables are the templates of its items (design 57 L3, issues 795 and 807).
for _, it := range m.Items {
text, err := os.ReadFile(filepath.Join("..", "..", it.Template))
if err != nil {
t.Fatal(err)
}
parts := strings.Split(it.Collection, ":")
switch {
case len(parts) == 3 && parts[0] == "shell":
m.Shell = append(m.Shell, struct{ For, Slot, Code string }{parts[1], parts[2], string(text)})
case it.Collection == "environment" && it.Attributes["kind"] == "variable":
if m.Environment.Variables == nil {
m.Environment.Variables = map[string]string{}
}
m.Environment.Variables[it.Attributes["name"]] = strings.TrimSuffix(string(text), "\n")
}
}
return m
}
@@ -108,9 +130,10 @@ func TestItContributesTheSessionsExecLastAndTheDesktopsIdentity(t *testing.T) {
func TestTheConfigurationIsTheModulesFileImprovedAndEndsWithTheDropIns(t *testing.T) {
m := readManifest(t)
r := resource(t, m, "config")
raw, _ := os.ReadFile(filepath.Join("..", "..", "config", "config"))
if r["content"] != string(raw) || r["path"] != "${machine:account-home}/.config/i3/config" || r["into"] != nil {
t.Fatal("the manifest carries config/config whole, as an owned file")
raw, _ := os.ReadFile(filepath.Join("..", "..", "files", "config"))
if _, inline := r["content"]; inline || r["template"] != "files/config" || len(raw) == 0 ||
r["path"] != "${machine:account-home}/.config/i3/config" || r["into"] != nil {
t.Fatal("the manifest takes files/config whole as its template, as an owned file")
}
c := string(raw)
lines := strings.Split(strings.TrimSpace(c), "\n")
@@ -159,7 +182,11 @@ func TestTheLoginManagersEntryRunsTheSessionsStart(t *testing.T) {
if r["path"] != "/etc/lemurs/wms/i3" || r["mode"] != "0755" {
t.Fatalf("%v", r)
}
if !strings.Contains(r["content"].(string), `exec systemd-cat -t x-session /bin/sh "$HOME/.xinitrc"`) {
if r["template"] != "files/wms/i3" {
t.Fatalf("the entry's text is not its template files/wms/i3: %v", r["template"])
}
entry, _ := os.ReadFile(filepath.Join("..", "..", "files", "wms", "i3"))
if !strings.Contains(string(entry), `exec systemd-cat -t x-session /bin/sh "$HOME/.xinitrc"`) {
t.Fatal("the entry runs the session's start, never i3 bare")
}
pkgs := map[string]bool{}
@@ -219,7 +246,7 @@ func TestTheMainFileAndEveryModulesContributionLoadTogether(t *testing.T) {
if len(found) < 5 {
t.Fatalf("expected the launcher, clipboard, wallpaper, bars and the laptop to contribute; found %v", found)
}
main, _ := os.ReadFile(filepath.Join("..", "..", "config", "config"))
main, _ := os.ReadFile(filepath.Join("..", "..", "files", "config"))
text := strings.Replace(string(main), "${contribution:node-display-session:config}", placed.String(), 1)
// The operator's own files are not the catalogue's: the include line itself goes (a contribution may
// name it in a comment, so only the line that is exactly it).
@@ -230,3 +257,31 @@ func TestTheMainFileAndEveryModulesContributionLoadTogether(t *testing.T) {
t.Fatalf("with the contributions of %v: %v\n%s", found, err, out)
}
}
// No file resource carries its text inline: each names its template in files/, and the build packs files/
// into the one archive the controller reads the templates back from (novox/hq ADR 0349 §2).
func TestEveryFileTakesItsTextFromATemplate(t *testing.T) {
m := readManifest(t)
for _, r := range m.Resources {
if _, inline := r["content"]; inline {
t.Errorf("resource %v keeps its text inline: keep it in files/ and name it as the template", r["id"])
}
if name, ok := r["template"].(string); ok {
if _, err := os.Stat(filepath.Join("..", "..", name)); err != nil {
t.Errorf("resource %v names %s: %v", r["id"], name, err)
}
}
}
packs := 0
for _, a := range m.Build.Artifacts {
if a["kind"] == "archive" && a["from"] == "files" {
packs++
if a["name"] != "files" {
t.Errorf("the templates archive is named %v, not files", a["name"])
}
}
}
if packs != 1 {
t.Errorf("the build packs files/ into %d archives, not one", packs)
}
}