R-621: a held app's logs show the log the hold kept
GetLogs (logs page, /api/stacks/<n>/logs, the operator's remote diagnostics) returned "" for a held app — no containers — while captureHoldLogs had kept the app's own log under hold-logs/<ts>/compose-logs.txt. It now serves the newest non-empty kept log, under a header naming the file, when the live log is empty; a running app's own output always wins. GetLogs gains a test seam (logsComposeFn) so the test never reaches docker. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
package stacks
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// R-621 (the open half): the hold keeps the app's own log, and the logs a person can READ — the logs
|
||||
// page, /api/stacks/<n>/logs, the operator's remote diagnostics, all through GetLogs — now show it
|
||||
// when the held app has no live output. Before this, GetLogs returned "" for a held app (no
|
||||
// containers), so the kept file was reachable only by a shell on the box.
|
||||
func TestR621_GetLogsFallsBackToTheHoldLog(t *testing.T) {
|
||||
m, dir := newPinManager(t, pinTplOld, pinTplNew, "deployed: true\nenv: {}\n")
|
||||
live := ""
|
||||
m.logsComposeFn = func(string, ...string) (string, error) { return live, nil }
|
||||
|
||||
// No hold log yet: an empty live log stays empty (nothing invented).
|
||||
if got, err := m.GetLogs("nextcloud", 200); err != nil || got != "" {
|
||||
t.Fatalf("no hold log: got %q, %v", got, err)
|
||||
}
|
||||
|
||||
for ts, body := range map[string]string{
|
||||
"20260921T211700Z": "web-1 | OLDER hold\n",
|
||||
"20260922T080000Z": "web-1 | Applying migration 0009... OK\nweb-1 | never bound :8000\n",
|
||||
"20260923T000000Z": " \n", // a newer, EMPTY capture must not hide the useful one
|
||||
} {
|
||||
p := filepath.Join(dir, "hold-logs", ts)
|
||||
if err := os.MkdirAll(p, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(p, "compose-logs.txt"), []byte(body), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
got, err := m.GetLogs("nextcloud", 200)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(got, "never bound :8000") || !strings.HasPrefix(got, "--- hold-logs/20260922T080000Z/compose-logs.txt ---\n") {
|
||||
t.Fatalf("R-621: a held app's logs must show the newest non-empty kept log under its header, got %q", got)
|
||||
}
|
||||
if strings.Contains(got, "OLDER hold") {
|
||||
t.Errorf("only the newest kept log is served, got %q", got)
|
||||
}
|
||||
|
||||
// A running app's own output always wins over a kept log.
|
||||
live = "web-1 | serving\n"
|
||||
if got, _ := m.GetLogs("nextcloud", 200); got != live {
|
||||
t.Errorf("live output must win over the hold log, got %q", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user