fix(R-1220): an unreadable app.yaml counts as installed — its folders are never kept data
liveDriveBinds(drives): an app whose app.yaml exists but does not parse binds every folder its compose names on every drive (fail closed); WARN per app. Red-proved (the live folder was listed). Commit only.
This commit is contained in:
@@ -217,10 +217,31 @@ func readKeptMarker(dir string) *KeptMarker {
|
||||
}
|
||||
|
||||
// liveDriveBinds is every drive folder an INSTALLED app binds — never kept data.
|
||||
func (m *Manager) liveDriveBinds() []string {
|
||||
//
|
||||
// R-1220 — FAIL CLOSED ON AN UNREADABLE app.yaml. A parse error gives a nil AppConfig, so ScanStacks reads the app as
|
||||
// „not installed"; before this, its live folder was then listed as kept data and offered for deletion. An app whose
|
||||
// app.yaml EXISTS but cannot be read counts as installed: its HDD_PATH is unknown, so every folder its compose would bind
|
||||
// on ANY of the given drives is live. One WARN per app per listing names it. Pinned by
|
||||
// TestR1220_AnUnreadableAppYamlKeepsTheFolderLive.
|
||||
func (m *Manager) liveDriveBinds(drives []string) []string {
|
||||
var out []string
|
||||
for _, st := range m.GetStacks() {
|
||||
if st.ComposePath == "" {
|
||||
continue
|
||||
}
|
||||
if !st.Deployed || st.AppConfig == nil {
|
||||
if !st.Deployed && !appYAMLUnreadable(filepath.Dir(st.ComposePath)) {
|
||||
continue // not installed: no app.yaml, or one that reads as not deployed
|
||||
}
|
||||
m.logger.Printf("[WARN] [stacks] kept: %s has an app.yaml that cannot be read — it counts as INSTALLED, so its drive folders are never listed as kept data (R-1220)", st.Name)
|
||||
for _, d := range drives {
|
||||
if d = strings.TrimSpace(d); d == "" {
|
||||
continue
|
||||
}
|
||||
for _, p := range ParseComposeHDDMounts(st.ComposePath, filepath.Clean(d)) {
|
||||
out = append(out, filepath.Clean(p))
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
hdd := strings.TrimSpace(st.AppConfig.Env["HDD_PATH"])
|
||||
@@ -234,6 +255,15 @@ func (m *Manager) liveDriveBinds() []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// appYAMLUnreadable is true when the stack folder holds an app.yaml that LoadAppConfig cannot turn into a config — the
|
||||
// R-1220 case. A missing app.yaml is false (a template that was never installed, or a removed app).
|
||||
func appYAMLUnreadable(stackDir string) bool {
|
||||
if _, err := os.Stat(filepath.Join(stackDir, "app.yaml")); err != nil {
|
||||
return false
|
||||
}
|
||||
return LoadAppConfig(stackDir) == nil
|
||||
}
|
||||
|
||||
func overlaps(a, b string) bool {
|
||||
sep := string(filepath.Separator)
|
||||
return a == b || strings.HasPrefix(a, b+sep) || strings.HasPrefix(b, a+sep)
|
||||
@@ -284,7 +314,7 @@ var sizeFn = getDirSizeBytes
|
||||
// ListKept lists every kept item on the given drive roots: the dated folders under <drive>/kept/<app>/,
|
||||
// and each <drive>/appdata/<x> that holds something and that no installed app binds. Sorted newest first.
|
||||
func (m *Manager) ListKept(drives []string) []KeptItem {
|
||||
live := m.liveDriveBinds()
|
||||
live := m.liveDriveBinds(drives)
|
||||
var out []KeptItem
|
||||
seen := map[string]bool{}
|
||||
for _, d := range drives {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package stacks
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// R-1220: an installed app whose app.yaml cannot be read must count as INSTALLED (fail closed). Before the fix a parse
|
||||
// error gave a nil AppConfig, so the app looked „not installed", its live drive folder was listed as kept data — and
|
||||
// the household's Delete was offered on it. Red-proved against the code before the fix (the folder was listed).
|
||||
func TestR1220_AnUnreadableAppYamlKeepsTheFolderLive(t *testing.T) {
|
||||
m, drive := keptManager(t)
|
||||
app := filepath.Join(filepath.Dir(m.stacks["cloudapp"].ComposePath))
|
||||
// An app.yaml that exists but does not parse (a half-written file, a disk error).
|
||||
must(t, os.WriteFile(filepath.Join(app, "app.yaml"), []byte("deployed: true\nenv: [this is: not: yaml\n"), 0o600))
|
||||
must(t, m.ScanStacks())
|
||||
items := m.ListKept([]string{drive})
|
||||
for _, it := range items {
|
||||
if it.App == "cloudapp" || it.Path == filepath.Join(drive, "appdata", "cloudapp") {
|
||||
t.Fatalf("an app with an unreadable app.yaml had its live folder listed as kept: %+v", it)
|
||||
}
|
||||
}
|
||||
if _, err := m.DeleteKept([]string{drive}, filepath.Join(drive, "appdata", "cloudapp")); err == nil {
|
||||
t.Fatal("Delete was allowed on the folder of an app whose app.yaml cannot be read")
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(drive, "appdata/cloudapp/user1/file.txt")); err != nil {
|
||||
t.Fatalf("the live folder's file is gone: %v", err)
|
||||
}
|
||||
// The control: with NO app.yaml at all (a real leftover), the folder IS listed.
|
||||
must(t, os.Remove(filepath.Join(app, "app.yaml")))
|
||||
must(t, m.ScanStacks())
|
||||
if items := m.ListKept([]string{drive}); len(items) != 1 || items[0].App != "cloudapp" {
|
||||
t.Fatalf("a real leftover (no app.yaml) is not listed: %+v", items)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user