Run a run-once process as its oneshot unit (novox/hq design 38 WP4c)
A step was run directly: in the host's own working directory, without its env, env files or user. A module step moved out of its container (node bootstrap/index.js) could find neither its code nor its words. A oneshot unit carries all four as a daemon's does, and starting it waits.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
package apply
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-host/internal/store"
|
||||
)
|
||||
|
||||
// A run-once process is a step run where, how and as whom it was declared (novox/hq design 38
|
||||
// WP4c): its bundle's directory, its environment and environment files, its user. Run directly,
|
||||
// it started in the host's own directory with none of them.
|
||||
func TestARunOnceProcessRunsAsItsOneshotUnit(t *testing.T) {
|
||||
units, bundles := t.TempDir(), t.TempDir()
|
||||
wasUnits, wasBundles := unitDir, daemonRoot
|
||||
unitDir, daemonRoot = units, bundles
|
||||
t.Cleanup(func() { unitDir, daemonRoot = wasUnits, wasBundles })
|
||||
|
||||
me, err := user.Current()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body, digest := anArchive(t, map[string]string{"bootstrap/index.js": "console.log(1)\n"})
|
||||
var commands []string
|
||||
fail := false
|
||||
run := func(ctx context.Context, name string, args ...string) (string, error) {
|
||||
commands = append(commands, name+" "+strings.Join(args, " "))
|
||||
if fail && name == "systemctl" && len(args) > 0 && args[0] == "start" {
|
||||
return "", errors.New("exit status 1")
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
d := declare(t, `{"id":"mosquitto.bootstrap","type":"process","name":"mosquitto-bootstrap","source":"`+serving(t, body)+
|
||||
`","digest":"`+digest+`","run":["node","bootstrap/index.js"],"run-once":true,"user":"`+me.Username+`",`+
|
||||
`"env":{"MESH_ADMIN":"mesh-admin"},"env-file":["/var/lib/mesh/mosquitto/bootstrap.env"]}`)
|
||||
|
||||
if _, _, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginDeclared, run, nil, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
unit, err := os.ReadFile(filepath.Join(units, "mosquitto-bootstrap.service"))
|
||||
if err != nil {
|
||||
t.Fatalf("no unit was written for the step: %v", err)
|
||||
}
|
||||
for _, want := range []string{
|
||||
"WorkingDirectory=" + filepath.Join(bundles, "mosquitto-bootstrap"),
|
||||
"EnvironmentFile=/var/lib/mesh/mosquitto/bootstrap.env",
|
||||
`Environment="MESH_ADMIN=mesh-admin"`,
|
||||
"User=" + me.Username,
|
||||
"Type=oneshot",
|
||||
"ExecStart=node bootstrap/index.js",
|
||||
} {
|
||||
if !strings.Contains(string(unit), want) {
|
||||
t.Errorf("the step's unit lacks %q:\n%s", want, unit)
|
||||
}
|
||||
}
|
||||
for _, never := range []string{"Restart=always", "[Install]", "Type=simple"} {
|
||||
if strings.Contains(string(unit), never) {
|
||||
t.Errorf("a step's unit says %q:\n%s", never, unit)
|
||||
}
|
||||
}
|
||||
joined := strings.Join(commands, "; ")
|
||||
if !strings.Contains(joined, "systemctl start mosquitto-bootstrap.service") {
|
||||
t.Errorf("the step was not started as its unit: %s", joined)
|
||||
}
|
||||
if strings.Contains(joined, "node bootstrap/index.js") || strings.Contains(joined, "enable mosquitto-bootstrap") {
|
||||
t.Errorf("the step was run directly or enabled: %s", joined)
|
||||
}
|
||||
|
||||
// A step that fails fails the apply, and is not recorded as done.
|
||||
fail = true
|
||||
d2 := declare(t, `{"id":"mosquitto.bootstrap","type":"process","name":"mosquitto-bootstrap","source":"`+serving(t, body)+
|
||||
`","digest":"`+digest+`","run":["node","bootstrap/index.js"],"run-once":true,"env":{"MESH_ADMIN":"changed"}}`)
|
||||
if _, _, err := Apply(context.Background(), archHost(t), d2, store.State{}, store.OriginDeclared, run, nil, nil); err == nil {
|
||||
t.Error("a step that failed did not fail the apply")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user