mirror of
https://github.com/kevinveenbirkenbach/docker-volume-backup.git
synced 2026-08-24 23:04:34 +00:00
The package carried no ruff configuration at all, so it ran on the defaults (E4+E7+E9+F) while infinito-nexus-core, its only consumer, holds itself to a far wider selection. Measured against that selection this tree had 224 findings. It now has none. The selector list is core's verbatim so both repositories answer to one bar. target-version stays py39 rather than core's py311, because requires-python still declares >=3.9 and pyupgrade would otherwise propose syntax the declared minimum cannot run. Every ignore carries its reason: S603/S607 in particular, since running docker and dump binaries from PATH in list form is this tool's whole job and is already injection-safe. Two conversions are judgement rather than mechanics. os.path.join(dir, '') was the rsync idiom for a trailing separator, which Path drops, so it becomes an explicit os.sep. os.path.abspath stays where Path.resolve() would follow symlinks and let a symlinked volume test as inside the snapshot subject. BREAKING CHANGE: VersionMismatch is renamed VersionMismatchError. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
148 lines
4.9 KiB
Python
148 lines
4.9 KiB
Python
"""Refuse a dump the target engine is too old to read.
|
|
|
|
A restore with ``--empty`` destroys before it replays: the pre-clean drops the
|
|
schema in one session and the dump goes in the next, with no rollback across
|
|
the two. A dump the engine cannot parse therefore does not fail harmlessly -
|
|
it leaves an emptied database behind. Comparing the two versions first turns
|
|
that into a refusal.
|
|
|
|
Both engines state their origin in the dump's own header, and they do not
|
|
state it the same way. Postgres writes ``-- Dumped from database version``
|
|
around line seven. MariaDB opens line two with ``-- MariaDB dump 10.19-11.8.8``,
|
|
where the first number is mariadb-dump's own version, and names the server only
|
|
further down on the tab-separated ``-- Server version`` line. Matching the first
|
|
number in the header would read the tool on one engine and the server on the
|
|
other, so each engine gets its own pattern.
|
|
|
|
A ``pg_dumpall`` cluster dump has no version line of its own: its header opens
|
|
with the cluster banner and the roles section, and the first
|
|
``-- Dumped from database version`` belongs to the first database's embedded
|
|
``pg_dump`` output, arbitrarily far down. Hence the scan runs to
|
|
``SCAN_LINES`` rather than to a header-sized handful.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
from baudolo.restore.run import docker_exec, stdout_of
|
|
|
|
SCAN_LINES = 2000
|
|
DUMP_VERSION = {
|
|
"postgres": re.compile(r"^-- Dumped from database version (\S+)"),
|
|
"mariadb": re.compile(r"^-- Server version\s+(\S+)"),
|
|
}
|
|
|
|
|
|
class VersionMismatchError(Exception):
|
|
"""The dump cannot be replayed into this engine."""
|
|
|
|
|
|
def major_of(version: str) -> int:
|
|
"""The major number of an engine version string.
|
|
|
|
Args:
|
|
version: as the engine spells it, e.g. ``17.11`` or
|
|
``11.8.8-MariaDB-ubu2404``.
|
|
|
|
Raises:
|
|
VersionMismatchError: the string does not start with a number.
|
|
"""
|
|
leading = re.match(r"(\d+)", version)
|
|
if not leading:
|
|
raise VersionMismatchError(f"cannot read a major version from '{version}'")
|
|
return int(leading.group(1))
|
|
|
|
|
|
def dump_version(sql_path: str, engine: str) -> str:
|
|
"""Read the engine version a dump was taken from, out of its own header.
|
|
|
|
Args:
|
|
sql_path: the dump to read.
|
|
engine: ``postgres`` or ``mariadb``.
|
|
|
|
Returns:
|
|
The version string as the dump spells it.
|
|
|
|
Raises:
|
|
VersionMismatchError: no version line within the first ``SCAN_LINES``.
|
|
"""
|
|
pattern = DUMP_VERSION[engine]
|
|
with Path(sql_path).open(encoding="utf-8", errors="replace") as handle:
|
|
for _ in range(SCAN_LINES):
|
|
line = handle.readline()
|
|
if not line:
|
|
break
|
|
found = pattern.search(line)
|
|
if found:
|
|
return found.group(1)
|
|
raise VersionMismatchError(
|
|
f"{sql_path} carries no {engine} version header in its first {SCAN_LINES} lines"
|
|
)
|
|
|
|
|
|
def server_version(
|
|
container: str, engine: str, user: str, password: str, client: str = ""
|
|
) -> str:
|
|
"""Ask the running engine which version it is."""
|
|
if engine == "postgres":
|
|
return stdout_of(
|
|
docker_exec(
|
|
container,
|
|
["psql", "-U", user, "-tAc", "SHOW server_version"],
|
|
capture=True,
|
|
docker_env={"PGPASSWORD": password},
|
|
)
|
|
)
|
|
return stdout_of(
|
|
docker_exec(
|
|
container,
|
|
[
|
|
client or "mariadb",
|
|
"-u",
|
|
user,
|
|
f"--password={password}",
|
|
"-N",
|
|
"-B",
|
|
"-e",
|
|
"SELECT VERSION()",
|
|
],
|
|
capture=True,
|
|
)
|
|
)
|
|
|
|
|
|
def assert_replayable(sql_path: str, engine: str, dumped: str, serving: str) -> None:
|
|
"""Refuse a dump from a newer major version than the target engine.
|
|
|
|
Restoring forward across a major version is the upgrade path and stays
|
|
allowed; backward is refused, because a newer dump uses syntax an older
|
|
server rejects and the pre-clean would already have dropped the schema.
|
|
|
|
Raises:
|
|
VersionMismatchError: the dump is newer than the engine.
|
|
"""
|
|
if major_of(dumped) > major_of(serving):
|
|
raise VersionMismatchError(
|
|
f"{sql_path} came from {engine} {dumped} but {serving} is running; "
|
|
"a newer dump does not replay into an older engine, and --empty "
|
|
"would drop the schema before finding out"
|
|
)
|
|
|
|
|
|
def guard(
|
|
*,
|
|
sql_path: str,
|
|
engine: str,
|
|
container: str,
|
|
user: str,
|
|
password: str,
|
|
client: str = "",
|
|
) -> None:
|
|
"""Compare the dump's origin against the running engine before replaying."""
|
|
dumped = dump_version(sql_path, engine)
|
|
serving = server_version(container, engine, user, password, client)
|
|
assert_replayable(sql_path, engine, dumped, serving)
|
|
print(f"OK: dump is from {engine} {dumped}, {serving} is serving.")
|