mirror of
https://github.com/kevinveenbirkenbach/docker-volume-backup.git
synced 2026-08-24 23:04:34 +00:00
fix(backup): detect the engine by its dump tool, not by the image name
36b2336 matched postgres and mariadb against the image's repository path. That name is not a property of the software: a dedicated Postgres inside an app's own stack is tagged <app>-database or postgis/postgis and carries no engine token at all, so it was never recognised, never dumped, and its data directory was copied as files without a single warning - the fallback notice hangs on found_db, which stayed false.
The container is now asked what it can run. pg_dumpall, mariadb-dump and mysqldump are probed by executing them, not by asking a shell for them, because a distroless image has no shell and would deny every tool it ships. The verdict is cached per image ID rather than per container, so replicas of one image cost a single probe.
Because the probe names the tool it found, the dump uses it instead of the hardcoded /usr/bin/mariadb-dump, which makes an image that ships only mysqldump dumpable rather than silently file-copied.
image_name and has_image are gone with their registry-host and tag stripping; the trap they worked around cannot occur when nothing reads the name. get_image_info stays, since the --images-* lists match exact references.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,45 @@ import pandas
|
||||
from pandas.errors import EmptyDataError
|
||||
|
||||
from .db import backup_database
|
||||
from .docker import has_image
|
||||
from .docker import has_tool, image_id
|
||||
|
||||
DUMP_TOOLS: tuple[tuple[str, str], ...] = (
|
||||
("postgres", "pg_dumpall"),
|
||||
("mariadb", "mariadb-dump"),
|
||||
("mariadb", "mysqldump"),
|
||||
)
|
||||
|
||||
_ENGINE_BY_IMAGE: dict[str, tuple[str, str] | None] = {}
|
||||
|
||||
|
||||
def container_engine(container: str) -> tuple[str, str] | None:
|
||||
"""The (engine, dump tool) a container can serve, or None for neither.
|
||||
|
||||
Asks the container what it can run instead of reading its image name. A
|
||||
dedicated Postgres is tagged `<app>-database` or `postgis/postgis` and
|
||||
carries no engine token at all, while a swarm registry host such as
|
||||
`svc-db-mariadb-swarm-mgr-01:5000` carries the wrong one.
|
||||
|
||||
Args:
|
||||
container: must be running - `docker exec` is the probe, and a
|
||||
stopped container would be cached as "no engine" for its whole
|
||||
image. The only caller feeds it `docker ps` output.
|
||||
|
||||
Returns:
|
||||
The engine and the tool that dumps it, cached per image ID so that
|
||||
replicas of one image are probed once.
|
||||
"""
|
||||
image = image_id(container)
|
||||
if image not in _ENGINE_BY_IMAGE:
|
||||
_ENGINE_BY_IMAGE[image] = next(
|
||||
(
|
||||
(engine, tool)
|
||||
for engine, tool in DUMP_TOOLS
|
||||
if has_tool(container, tool)
|
||||
),
|
||||
None,
|
||||
)
|
||||
return _ENGINE_BY_IMAGE[image]
|
||||
|
||||
|
||||
def backup_mariadb_or_postgres(
|
||||
@@ -21,17 +59,19 @@ def backup_mariadb_or_postgres(
|
||||
"""
|
||||
Returns (is_db_container, dumped_any)
|
||||
"""
|
||||
for img in ["mariadb", "postgres"]:
|
||||
if has_image(container, img):
|
||||
dumped = backup_database(
|
||||
container=container,
|
||||
volume_dir=volume_dir,
|
||||
db_type=img,
|
||||
databases_df=databases_df,
|
||||
database_containers=database_containers,
|
||||
)
|
||||
return True, dumped
|
||||
return False, False
|
||||
engine = container_engine(container)
|
||||
if engine is None:
|
||||
return False, False
|
||||
db_type, dump_tool = engine
|
||||
dumped = backup_database(
|
||||
container=container,
|
||||
volume_dir=volume_dir,
|
||||
db_type=db_type,
|
||||
dump_tool=dump_tool,
|
||||
databases_df=databases_df,
|
||||
database_containers=database_containers,
|
||||
)
|
||||
return True, dumped
|
||||
|
||||
|
||||
def _empty_databases_df() -> pandas.DataFrame:
|
||||
|
||||
Reference in New Issue
Block a user