D49 = A follow-ups: (A) the undo copy is released only after a stable start — 3 min of running, healthy, no restart, one rule for the first verdict and the late-start watch; a watch ends only for a restore begun after it; (B, R-1279) the .fab import downloads first with the restores' D48 helper, before it removes anything. Red-proved. Commit only.
gates / gates (push) Successful in 1m26s
gates / gates (push) Successful in 1m26s
This commit is contained in:
@@ -2093,6 +2093,10 @@ func main() {
|
||||
// this the export path would be the uncovered sibling of two covered ones, which is how a reader
|
||||
// concludes the whole class is handled (§2.2).
|
||||
appExporter.SetStopGuard(exportStopGuard{g: appStopGuard})
|
||||
// R-1279 (D48 for the import): the import downloads first, with the restores' helper and sentences.
|
||||
if backupMgr != nil {
|
||||
appExporter.SetImagePreparer(backupMgr.PrepareRestoreImages)
|
||||
}
|
||||
apiRouter.SetDebug(cfg.Logging.Level == "debug")
|
||||
|
||||
// --- Initialize web server ---
|
||||
@@ -3439,6 +3443,38 @@ func (a *stackAdapter) StartStackServices(name string, services []string) error
|
||||
return a.mgr.StartStackServices(name, services)
|
||||
}
|
||||
|
||||
// ContainerSamples (R-1270 follow-up, fix A) reads each container of the stack: running, its RestartCount, its health
|
||||
// ("" without a healthcheck) — the stable-start rule's sample (backup.containerSampler). Read-only docker calls.
|
||||
func (a *stackAdapter) ContainerSamples(name string) ([]backup.ContainerSample, error) {
|
||||
out, err := dockerexec.Command("docker", "ps", "-a", "-q", "--no-trunc", "--filter", "label=com.docker.compose.project="+name).Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids := strings.Fields(string(out))
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
args := append([]string{"inspect", "-f", "{{.Id}} {{.State.Status}} {{.RestartCount}} {{if .State.Health}}{{.State.Health.Status}}{{end}}"}, ids...)
|
||||
out, err = dockerexec.Command("docker", args...).Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res []backup.ContainerSample
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
||||
f := strings.Fields(line)
|
||||
if len(f) < 3 {
|
||||
continue
|
||||
}
|
||||
n, _ := strconv.Atoi(f[2])
|
||||
c := backup.ContainerSample{ID: f[0], Running: f[1] == "running", RestartCount: n}
|
||||
if len(f) > 3 {
|
||||
c.Health = f[3]
|
||||
}
|
||||
res = append(res, c)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// RefreshAndIsRunning forces a docker ps scan before checking state.
|
||||
// Called during post-restore health check (~every 5s for up to 90s).
|
||||
// Full refresh is acceptable here since restores are rare operations.
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// R-1279 — the `.fab` import downloads first only if main.go passes the restores' helper in; an unwired exporter keeps
|
||||
// the old order (volumes removed before the pull). Seam-built-but-never-wired is a recorded class (REUSE.md).
|
||||
func TestR1279_MainWiresTheImportsDownloadFirst(t *testing.T) {
|
||||
b, err := os.ReadFile("main.go")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(b), "appExporter.SetImagePreparer(backupMgr.PrepareRestoreImages)") {
|
||||
t.Fatal("main.go must wire the import's download-first helper (SetImagePreparer)")
|
||||
}
|
||||
if !strings.Contains(string(b), "func (a *stackAdapter) ContainerSamples(") {
|
||||
t.Fatal("main.go's stack adapter must read container samples for the stable-start rule (fix A)")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user