controller v0.267.0: tests off DooPlex's Docker, cut-off copies refused, two pages true
gates / gates (push) Successful in 26s
gates / gates (push) Successful in 26s
R-650: internal/dockerexec — every docker exec routed through it; under go test a real docker is refused (opt-in FELHOM_TEST_REAL_DOCKER=1; a stub under the temp dir is allowed). api/stacks/web tests run under a silent stub (TestMain). TestR650_NoBareDockerExec pins it repo-wide. R-640: a dump without its engine's completion marker is refused before the first mutation (unit + off-site restore) and again before any load. R-499: the Tier-2 page's system-disk sentence has four true branches. R-518: the backup button states the measured ~8 min stop. R-626: measured on 9202, not reproduced. 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:
@@ -5,7 +5,11 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/appbackup"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
|
||||
)
|
||||
|
||||
// reimportDBDumps replays the captured per-app .sql dumps back into the app's now-running database
|
||||
@@ -74,6 +78,12 @@ func (m *Manager) reimportDBDumpsFrom(ctx context.Context, stackName, dumpDir st
|
||||
if _, statErr := os.Stat(dumpPath); statErr != nil {
|
||||
continue // no dump for this particular DB engine
|
||||
}
|
||||
// R-640: a cut-off copy loads as SUCCESS into an empty PostgreSQL database. Checked here, on
|
||||
// the one path every replay takes, whatever `imp` is — the seam must not be able to skip it.
|
||||
if err := appbackup.CheckDumpComplete(dumpPath, db.DBType); err != nil {
|
||||
m.logger.Printf("[ERROR] [backup] Restore %s: NOT replaying %s — %v", stackName, filepath.Base(dumpPath), err)
|
||||
return imported, util.MsgError("err.backup.adatbazis_masolat_csonka_nem_toltve", stackName)
|
||||
}
|
||||
m.logger.Printf("[INFO] [backup] Restore %s: replaying DB dump into %s (%s)", stackName, db.ContainerName, db.DBType)
|
||||
if err := imp(ctx, db, dumpPath); err != nil {
|
||||
return imported, fmt.Errorf("importing %s dump for %s: %w", db.DBType, stackName, err)
|
||||
@@ -111,3 +121,34 @@ func (m *Manager) reimportDBDumpsAtCtx(stackName, dumpDir string) (int, error) {
|
||||
defer cancel()
|
||||
return m.reimportDBDumpsFrom(ctx, stackName, dumpDir)
|
||||
}
|
||||
|
||||
// incompleteDumps names the replayable dumps in dumpDir that do not end with their engine's
|
||||
// completion marker (R-640). Only the files a replay would load are checked: `<stack>-<engine>.sql`,
|
||||
// never the pre-restore safety copies. A directory that cannot be read yields nothing here — the
|
||||
// replay's own ReadDir reports that.
|
||||
func incompleteDumps(dumpDir string) []string {
|
||||
entries, err := os.ReadDir(dumpDir)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var bad []string
|
||||
for _, e := range entries {
|
||||
name := e.Name()
|
||||
if e.IsDir() || filepath.Ext(name) != ".sql" || strings.HasPrefix(name, preRestoreDumpPrefix) {
|
||||
continue
|
||||
}
|
||||
var t DBType
|
||||
switch {
|
||||
case strings.HasSuffix(name, "-"+string(appbackup.DBTypePostgres)+".sql"):
|
||||
t = appbackup.DBTypePostgres
|
||||
case strings.HasSuffix(name, "-"+string(appbackup.DBTypeMariaDB)+".sql"):
|
||||
t = appbackup.DBTypeMariaDB
|
||||
default:
|
||||
continue // not a file the replay loads
|
||||
}
|
||||
if err := appbackup.CheckDumpComplete(filepath.Join(dumpDir, name), t); err != nil {
|
||||
bad = append(bad, name)
|
||||
}
|
||||
}
|
||||
return bad
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user