H5 engine-integration: the node-engine whole applies if-config, config-lines and a module's stop at a failure

The existing node-engine integration test's module gains a file under if-config,
a template with config-lines from a list, and two files that fail and are left
under a value; the test asserts the file appears and goes with its value, the
lines follow the list, and the failing resource fails the module with the later
resource left. No integration test is added.

Builds: design 42 zfs-owns-zfs §5 (and §1 to §3), A-22
This commit is contained in:
2026-11-13 00:24:20 +01:00
parent 61806a7a92
commit 225f2abb1f
+39 -1
View File
@@ -425,8 +425,16 @@ func h8(t *testing.T, b *itBed, checkBuild bool) (func(), h8state) {
candidates := kvOf(buckets.ProposalCandidates) candidates := kvOf(buckets.ProposalCandidates)
packages := kvOf(link.PackagesBucket) packages := kvOf(link.PackagesBucket)
path := filepath.Join(b.dir, "web.conf") path := filepath.Join(b.dir, "web.conf")
man := `{"module":"web","version":"1","config":{"greeting":{"type":"string","description":"what the file says","default":"hello"}},` + // Beside the greeting: a file under `if-config`, a template with `config-lines` from a list, and two files under a
// value that makes the first of them fail (a file below a file), the second being left (design 42 zfs-owns-zfs §5).
more := func(name string) string { return filepath.Join(b.dir, "web-"+name+".conf") }
man := `{"module":"web","version":"1","config":{"greeting":{"type":"string","description":"what the file says","default":"hello"},` +
`"mods":{"type":"list","description":"lines","default":["a","b"]}},` +
`"resources":[{"id":"conf","type":"file","path":"` + path + `","mode":"0644","content":"greeting=${config:greeting}\n"},` + `"resources":[{"id":"conf","type":"file","path":"` + path + `","mode":"0644","content":"greeting=${config:greeting}\n"},` +
`{"id":"mods","type":"file","path":"` + more("mods") + `","mode":"0644","content":"${config-lines:mods:m=}\n"},` +
`{"id":"salut","type":"file","path":"` + more("salut") + `","mode":"0644","content":"salut\n","if-config":["greeting=salut"]},` +
`{"id":"bad","type":"file","path":"` + path + `/x","mode":"0644","content":"x\n","if-config":["greeting=boom"]},` +
`{"id":"after","type":"file","path":"` + more("after") + `","mode":"0644","content":"after\n","if-config":["greeting=boom"]},` +
`{"id":"tools","type":"package","package":"web-tools"}],` + `{"id":"tools","type":"package","package":"web-tools"}],` +
`"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]}}` `"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]}}`
digest := meshcatalogclient.ManifestDigest([]byte(man)) digest := meshcatalogclient.ManifestDigest([]byte(man))
@@ -538,6 +546,36 @@ func h8rest(t *testing.T, b *itBed, resolved, candidates nats.KeyValue, hkey str
t.Fatal(err) t.Fatal(err)
} }
within(t, 3*time.Second, "a changed config value was not composed and applied again", func() bool { return b.file("web") == "greeting=salut\n" }) within(t, 3*time.Second, "a changed config value was not composed and applied again", func() bool { return b.file("web") == "greeting=salut\n" })
// Design 42 zfs-owns-zfs §5: the file under `if-config` appears with its value and goes with it, the template's lines
// follow the list, and a failing resource stops its module's apply with the later resource left.
other := func(name string) string {
got, _ := os.ReadFile(filepath.Join(b.dir, "web-"+name+".conf"))
return string(got)
}
setGreeting := func(value string) {
if _, err := resolved.Put(itNode+".web.greeting", []byte(`{"value":"`+value+`","source":"module"}`)); err != nil {
t.Fatal(err)
}
}
within(t, 3*time.Second, "the file under if-config did not appear with its value, or the lines do not follow the list", func() bool {
return other("salut") == "salut\n" && other("mods") == "m=a\nm=b\n"
})
setGreeting("bonjour")
within(t, 3*time.Second, "the file under if-config did not go with its value", func() bool {
_, err := os.Stat(filepath.Join(b.dir, "web-salut.conf"))
return b.file("web") == "greeting=bonjour\n" && errors.Is(err, os.ErrNotExist)
})
setGreeting("boom")
within(t, 5*time.Second, "the failing resource did not fail its module", func() bool {
return b.have("web").Phase == nec.PhaseFailed
})
if got := other("after"); got != "" {
t.Errorf("a resource after the failed one was applied: %q", got)
}
setGreeting("salut")
within(t, 5*time.Second, "the module did not run again once its value held no failing resource", func() bool {
return b.file("web") == "greeting=salut\n" && b.have("web").Phase == nec.PhaseRunning
})
within(t, 2*time.Second, "the have does not name the revision", func() bool { within(t, 2*time.Second, "the have does not name the revision", func() bool {
h := b.have("web") h := b.have("web")
return h.Fp == fp && h.Composed != first && h.Phase == nec.PhaseRunning return h.Fp == fp && h.Composed != first && h.Phase == nec.PhaseRunning