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:
1 parent
e333477b11
commit
7e15a374b2
3 files changed
+73
-2
No files matched your search
@@ -9,6 +9,11 @@ hub answers 400 to both and nothing else changes.
|
|||||||
as `restore_failed` — `since`, `why`), `app_hold_cleared` when a hold is gone; every hold is sent again once a day and
|
as `restore_failed` — `since`, `why`), `app_hold_cleared` when a hold is gone; every hold is sent again once a day and
|
||||||
at each start (`repeat: true`). The last-reported set is kept in `settings.reported_holds`, so a hold cleared while the
|
at each start (`repeat: true`). The last-reported set is kept in `settings.reported_holds`, so a hold cleared while the
|
||||||
controller was down is still reported cleared. Both severity info (the hub mails nothing on receipt).
|
controller was down is still reported cleared. Both severity info (the hub mails nothing on receipt).
|
||||||
|
- **R-1220 (P2, delete path):** an app whose `app.yaml` EXISTS but cannot be read counts as INSTALLED (fail closed):
|
||||||
|
`stacks.liveDriveBinds(drives)` adds every folder its compose binds on every given drive, so „Megőrzött adatok" never
|
||||||
|
lists — and never offers to delete — that app's live folder; one WARN names the app. Before: a parse error gave a nil
|
||||||
|
`AppConfig`, the app read as not installed, its folder was a „leftover". Test `TestR1220_AnUnreadableAppYamlKeepsTheFolderLive`
|
||||||
|
(with a real leftover as the control), red-proved against the old code.
|
||||||
- `settings.RestoreHold.Why()` / `WireReason()`: the hold sentence moved out of `cmd/controller` (`holdWhy` calls it), so
|
- `settings.RestoreHold.Why()` / `WireReason()`: the hold sentence moved out of `cmd/controller` (`holdWhy` calls it), so
|
||||||
`--restore-holds` and the hub's list say the same thing.
|
`--restore-holds` and the hub's list say the same thing.
|
||||||
- Tests `internal/holdreport/holdreport_test.go` (8): the report field, the daily repeat, cleared once, cleared while
|
- Tests `internal/holdreport/holdreport_test.go` (8): the report field, the daily repeat, cleared once, cleared while
|
||||||
|
|||||||
@@ -217,10 +217,31 @@ func readKeptMarker(dir string) *KeptMarker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// liveDriveBinds is every drive folder an INSTALLED app binds — never kept data.
|
// 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
|
var out []string
|
||||||
for _, st := range m.GetStacks() {
|
for _, st := range m.GetStacks() {
|
||||||
|
if st.ComposePath == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !st.Deployed || st.AppConfig == nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
hdd := strings.TrimSpace(st.AppConfig.Env["HDD_PATH"])
|
hdd := strings.TrimSpace(st.AppConfig.Env["HDD_PATH"])
|
||||||
@@ -234,6 +255,15 @@ func (m *Manager) liveDriveBinds() []string {
|
|||||||
return out
|
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 {
|
func overlaps(a, b string) bool {
|
||||||
sep := string(filepath.Separator)
|
sep := string(filepath.Separator)
|
||||||
return a == b || strings.HasPrefix(a, b+sep) || strings.HasPrefix(b, a+sep)
|
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>/,
|
// 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.
|
// and each <drive>/appdata/<x> that holds something and that no installed app binds. Sorted newest first.
|
||||||
func (m *Manager) ListKept(drives []string) []KeptItem {
|
func (m *Manager) ListKept(drives []string) []KeptItem {
|
||||||
live := m.liveDriveBinds()
|
live := m.liveDriveBinds(drives)
|
||||||
var out []KeptItem
|
var out []KeptItem
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
for _, d := range drives {
|
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