//go:build linux package instanceexec import ( "encoding/json" "os" "path/filepath" "testing" "time" "github.com/carlosframework/platform/internal/instancectl" ) func envOf(m map[string]string) func(string) string { return func(k string) string { return m[k] } } func TestWriteExitRecordWritesTheFourVariables(t *testing.T) { run := t.TempDir() iid := "h.example.com" if err := os.MkdirAll(filepath.Join(run, "instances", iid), 0o700); err != nil { t.Fatal(err) } now := time.Date(2026, 9, 11, 12, 0, 0, 0, time.UTC) env := envOf(map[string]string{"INVOCATION_ID": "deadbeefdeadbeefdeadbeefdeadbeef", "SERVICE_RESULT": "exit-code", "EXIT_CODE": "exited", "EXIT_STATUS": "3"}) if err := writeExitRecord(run, instancectl.RoleRun, iid, env, now); err != nil { t.Fatalf("writeExitRecord: %v", err) } b, err := os.ReadFile(filepath.Join(run, "instances", iid, "exit.run.deadbeefdeadbeefdeadbeefdeadbeef.json")) if err != nil { t.Fatal(err) } var rec instancectl.ExitRecord if err := json.Unmarshal(b, &rec); err != nil { t.Fatal(err) } if rec.Invocation != "deadbeefdeadbeefdeadbeefdeadbeef" || rec.Result != "exit-code" || rec.Code != "exited" || rec.Status != "3" || !rec.At.Equal(now) { t.Errorf("record = %+v", rec) } // 0600: the record is diagnostic detail about this instance's own exit, // not something every reader of the instance directory needs (final // review, plan 2, item 6). if fi, err := os.Stat(filepath.Join(run, "instances", iid, "exit.run.deadbeefdeadbeefdeadbeefdeadbeef.json")); err != nil || fi.Mode().Perm() != 0o600 { t.Errorf("mode = %v, %v, want 0600", fi.Mode(), err) } // O_EXCL: a second write for the same invocation is refused and the // first record stands. env2 := envOf(map[string]string{"INVOCATION_ID": "deadbeefdeadbeefdeadbeefdeadbeef", "SERVICE_RESULT": "success", "EXIT_CODE": "exited", "EXIT_STATUS": "0"}) if err := writeExitRecord(run, instancectl.RoleRun, iid, env2, now); err == nil { t.Error("second write for the same invocation succeeded") } b2, _ := os.ReadFile(filepath.Join(run, "instances", iid, "exit.run.deadbeefdeadbeefdeadbeefdeadbeef.json")) if string(b2) != string(b) { t.Error("first record was overwritten") } } func TestWriteExitRecordWithoutInvocationIsANoop(t *testing.T) { run := t.TempDir() iid := "h.example.com" if err := os.MkdirAll(filepath.Join(run, "instances", iid), 0o700); err != nil { t.Fatal(err) } if err := writeExitRecord(run, instancectl.RoleSeed, iid, envOf(nil), time.Now()); err != nil { t.Fatalf("no INVOCATION_ID: %v (must not fail the unit's stop)", err) } entries, _ := os.ReadDir(filepath.Join(run, "instances", iid)) if len(entries) != 0 { t.Errorf("wrote %v without an invocation id", entries) } } func TestWriteExitRecordRefusesAnInvocationThatIsNotAName(t *testing.T) { run := t.TempDir() iid := "h.example.com" if err := os.MkdirAll(filepath.Join(run, "instances", iid), 0o700); err != nil { t.Fatal(err) } env := envOf(map[string]string{"INVOCATION_ID": "../../etc/x", "SERVICE_RESULT": "success"}) if err := writeExitRecord(run, instancectl.RoleRun, iid, env, time.Now()); err == nil { t.Error("an invocation id with a path separator was accepted") } } func TestWriteExitRecordKeysAGenerationRecordByGeneration(t *testing.T) { run := t.TempDir() iid := "h.example.com" if err := os.MkdirAll(filepath.Join(run, "instances", iid), 0o700); err != nil { t.Fatal(err) } now := time.Date(2026, 10, 7, 12, 0, 0, 0, time.UTC) env := envOf(map[string]string{"INVOCATION_ID": "deadbeefdeadbeefdeadbeefdeadbeef", "SERVICE_RESULT": "success", "EXIT_CODE": "exited", "EXIT_STATUS": "0"}) if err := writeExitRecord(run, instancectl.RoleRun, instancectl.GenInstance(iid, 7), env, now); err != nil { t.Fatalf("writeExitRecord: %v", err) } if _, err := os.Stat(filepath.Join(run, "instances", iid, "exit.gen.7.deadbeefdeadbeefdeadbeefdeadbeef.json")); err != nil { t.Errorf("generation record missing: %v", err) } if _, err := os.Stat(filepath.Join(run, "instances", iid, "exit.run.deadbeefdeadbeefdeadbeefdeadbeef.json")); !os.IsNotExist(err) { t.Errorf("a generation wrote a legacy record: %v", err) } if err := writeExitRecord(run, instancectl.RoleSidecar, iid+":7", env, now); err == nil { t.Error("a sidecar generation was accepted") } }