mirror of
https://github.com/kevinveenbirkenbach/docker-volume-backup.git
synced 2026-08-24 14:54:32 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 90d289d92f | |||
| 57fc7c96bc |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## [3.4.3] - 2026-08-16
|
||||
|
||||
- Backup: *create_version_directory* carried *exist_ok=True*, so a run starting
|
||||
in the same wall-clock second as its predecessor claimed that predecessor's
|
||||
generation. Generation names carry seconds, and a host with little to copy
|
||||
finishes inside one — rsync *--delete* then overwrote a finished generation,
|
||||
and only afterwards did *create_stamp_file* refuse the already-stamped
|
||||
directory and exit 2. The guard reported the damage instead of preventing it.
|
||||
- Backup: the generation directory is claimed exclusively. Claiming it is the
|
||||
first filesystem action of a run, so a collision aborts before the first
|
||||
write and names the second it collided on.
|
||||
- Tests: the idempotence test asserted the reuse and gave way to one that
|
||||
requires the refusal.
|
||||
|
||||
## [3.4.2] - 2026-08-15
|
||||
|
||||
- Backup: *has_image* matched the raw *.Config.Image*, so the registry host and
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "backup-docker-to-local"
|
||||
version = "3.4.2"
|
||||
version = "3.4.3"
|
||||
description = "Backup Docker volumes to local with rsync and optional DB dumps."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
|
||||
@@ -7,7 +7,7 @@ import pathlib
|
||||
|
||||
from dirval import create_stamp_file
|
||||
|
||||
from .shell import execute_shell_command
|
||||
from .shell import BackupException, execute_shell_command
|
||||
|
||||
|
||||
def get_machine_id() -> str:
|
||||
@@ -23,7 +23,14 @@ def stamp_directory(version_dir: str) -> None:
|
||||
|
||||
def create_version_directory(versions_dir: str, backup_time: str) -> str:
|
||||
version_dir = os.path.join(versions_dir, backup_time)
|
||||
pathlib.Path(version_dir).mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
pathlib.Path(version_dir).mkdir(parents=True)
|
||||
except FileExistsError:
|
||||
raise BackupException(
|
||||
f"generation {backup_time} already exists at {version_dir}; "
|
||||
"another run claimed this second - refusing to write into it, "
|
||||
"since rsync --delete would overwrite that generation"
|
||||
) from None
|
||||
return version_dir
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
from baudolo.backup import layout as mod
|
||||
from baudolo.backup.shell import BackupException
|
||||
|
||||
|
||||
class TestVersionDirectory(unittest.TestCase):
|
||||
@@ -17,11 +18,12 @@ class TestVersionDirectory(unittest.TestCase):
|
||||
self.assertTrue(Path(created).is_dir())
|
||||
self.assertEqual(Path(created).name, "20260731020304")
|
||||
|
||||
def test_it_is_idempotent(self) -> None:
|
||||
def test_it_refuses_a_generation_another_run_already_claimed(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
first = mod.create_version_directory(tmp, "20260731")
|
||||
second = mod.create_version_directory(tmp, "20260731")
|
||||
self.assertEqual(first, second)
|
||||
mod.create_version_directory(tmp, "20260731")
|
||||
with self.assertRaises(BackupException) as caught:
|
||||
mod.create_version_directory(tmp, "20260731")
|
||||
self.assertIn("20260731", str(caught.exception))
|
||||
|
||||
def test_it_creates_missing_parents(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
|
||||
Reference in New Issue
Block a user