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") } }