feat(manifest)!: record per volume what the run established
A finished generation cannot show whether a volume held a database, nor whether a dump was produced for it: under --only-sql a failed dump falls back to a file copy, and the resulting files/ tree looks like any other copy. The run knows both and threw the knowledge away as a printed warning, leaving every reader to guess from file names. Each generation now carries a manifest.json stating its layout and, per volume, database / dumped / engine. baudolo.generation is the single place those names are spelled; restore/paths.py, backup/db.py and backup/volume.py stop repeating them. It is deliberately import-free so a consumer can read the manifest with nothing but json, on hosts where this package is not installed. BREAKING CHANGE: BackupException is renamed BackupError. The rename is atomic across the ten modules that define or import it, three of which also carry the manifest change, so it lands in this commit rather than a separate one that could not import. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
03da186a06
commit
94637c32aa
22 files changed
+422
-106
No files matched your search
+14
-12
@@ -1,16 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
|
||||
import pandas
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from baudolo.databases import CLUSTER_ROW, validate_database
|
||||
from baudolo.generation import CLUSTER_SUFFIX, DUMP_SUFFIX, SQL_DIR
|
||||
|
||||
from .docker import docker_exec_argv
|
||||
from .shell import BackupException, execute_to_file
|
||||
from .shell import BackupError, execute_to_file
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import pandas as pd
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -47,7 +49,7 @@ def backup_database(
|
||||
volume_dir: str,
|
||||
db_type: str,
|
||||
dump_tool: str,
|
||||
databases_df: pandas.DataFrame,
|
||||
databases_df: pd.DataFrame,
|
||||
database_containers: list[str],
|
||||
) -> bool:
|
||||
"""
|
||||
@@ -66,8 +68,8 @@ def backup_database(
|
||||
log.debug("No database entries for instance '%s'", instance_name)
|
||||
return False
|
||||
|
||||
out_dir = os.path.join(volume_dir, "sql")
|
||||
pathlib.Path(out_dir).mkdir(parents=True, exist_ok=True)
|
||||
out_dir = pathlib.Path(volume_dir) / SQL_DIR
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
produced = False
|
||||
|
||||
@@ -85,13 +87,13 @@ def backup_database(
|
||||
f"'{CLUSTER_ROW}' is currently only supported for Postgres."
|
||||
)
|
||||
|
||||
cluster_file = os.path.join(out_dir, f"{instance_name}.cluster.backup.sql")
|
||||
cluster_file = str(out_dir / f"{instance_name}{CLUSTER_SUFFIX}")
|
||||
fallback_pg_dumpall(container, user, password, cluster_file)
|
||||
produced = True
|
||||
continue
|
||||
|
||||
db_name = db_value
|
||||
dump_file = os.path.join(out_dir, f"{db_name}.backup.sql")
|
||||
dump_file = str(out_dir / f"{db_name}{DUMP_SUFFIX}")
|
||||
|
||||
if db_type == "mariadb":
|
||||
# Force TCP so auth matches '<user>'@'%' instead of socket -> 'localhost'.
|
||||
@@ -136,13 +138,13 @@ def backup_database(
|
||||
env={"PGPASSWORD": password},
|
||||
)
|
||||
produced = True
|
||||
except BackupException as e:
|
||||
raise BackupException(
|
||||
except BackupError as e:
|
||||
raise BackupError(
|
||||
f"Postgres dump failed for instance '{instance_name}', "
|
||||
f"database '{db_name}'. This database was explicitly configured "
|
||||
"and therefore must succeed.\n"
|
||||
f"{e}"
|
||||
)
|
||||
) from e
|
||||
continue
|
||||
|
||||
return produced
|
||||
Reference in new issue
Block a user