fix(backup): fallback to file backup in dump-only mode when no DB dump is possible

- Change DB backup helpers to return whether a dump was actually produced
- Detect DB containers without successful dumps in --dump-only mode
- Fallback to file backups with a warning instead of skipping silently
- Refactor DB dump logic to return boolean status
- Add E2E test covering dump-only fallback when databases.csv entry is missing

https://chatgpt.com/share/6951a659-2b0c-800f-aafa-3e89ae1eb697
This commit is contained in:
2025-12-28 22:51:12 +01:00
parent d563dce20f
commit df32671cec
3 changed files with 225 additions and 30 deletions

View File

@@ -3,9 +3,8 @@ from __future__ import annotations
import os
import pathlib
import re
import pandas
import logging
import pandas
from .shell import BackupException, execute_shell_command
@@ -18,9 +17,7 @@ def get_instance(container: str, database_containers: list[str]) -> str:
return re.split(r"(_|-)(database|db|postgres)", container)[0]
def fallback_pg_dumpall(
container: str, username: str, password: str, out_file: str
) -> None:
def fallback_pg_dumpall(container: str, username: str, password: str, out_file: str) -> None:
cmd = (
f"PGPASSWORD={password} docker exec -i {container} "
f"pg_dumpall -U {username} -h localhost > {out_file}"
@@ -35,20 +32,25 @@ def backup_database(
db_type: str,
databases_df: "pandas.DataFrame",
database_containers: list[str],
) -> None:
) -> bool:
"""
Returns True if at least one dump file was produced, else False.
"""
instance_name = get_instance(container, database_containers)
entries = databases_df.loc[databases_df["instance"] == instance_name]
if entries.empty:
log.warning("No entry found for instance '%s'", instance_name)
return
log.warning("No entry found for instance '%s' (skipping DB dump)", instance_name)
return False
out_dir = os.path.join(volume_dir, "sql")
pathlib.Path(out_dir).mkdir(parents=True, exist_ok=True)
for row in entries.iloc:
db_name = row["database"]
user = row["username"]
password = row["password"]
produced = False
for row in entries.itertuples(index=False):
db_name = row.database
user = row.username
password = row.password
dump_file = os.path.join(out_dir, f"{db_name}.backup.sql")
@@ -58,13 +60,15 @@ def backup_database(
f"-u {user} -p{password} {db_name} > {dump_file}"
)
execute_shell_command(cmd)
produced = True
continue
if db_type == "postgres":
cluster_file = os.path.join(out_dir, f"{instance_name}.cluster.backup.sql")
if not db_name:
fallback_pg_dumpall(container, user, password, cluster_file)
return
return True
try:
cmd = (
@@ -72,6 +76,7 @@ def backup_database(
f"pg_dump -U {user} -d {db_name} -h localhost > {dump_file}"
)
execute_shell_command(cmd)
produced = True
except BackupException as e:
print(f"pg_dump failed: {e}", flush=True)
print(
@@ -79,4 +84,7 @@ def backup_database(
flush=True,
)
fallback_pg_dumpall(container, user, password, cluster_file)
produced = True
continue
return produced