Third review of mesh-catalog #147. A unit's name can now come from a setting (a template's instance), so every systemctl call that names a unit passes `--` before it, and a name can never be read as an option. On removal, a unit is started or enabled again only when it is exactly the stopped and disabled the mesh held it to. A masked unit, or one somebody started or enabled since, is left as it is, and the report says why. A restore that fails (the start refused, or the unit not running after it) puts back what this attempt changed and is reported as waiting, so no apply is made fatal by it and the next one tries again. The tests' fake service managers read systemctl's arguments in the shape they were written for (oldShape); internal/system's own test holds the `--`.
83 lines
3.2 KiB
Go
83 lines
3.2 KiB
Go
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) {
|
|
args = oldShape(name, args)
|
|
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")
|
|
}
|
|
}
|