Merge pull request '(bugfix) Enable rsync progress output for live real runs' (#71) from issue-64-rsync-progress-live-runs into master
Reviewed-on: #71
This commit was merged in pull request #71.
This commit is contained in:
@@ -27,7 +27,7 @@ def queue_backup_run(
|
||||
host: HostConfig,
|
||||
run_type: str = BackupRun.RunType.MANUAL,
|
||||
dry_run: bool = False,
|
||||
verbose_output: bool = False,
|
||||
verbose_output: bool = True,
|
||||
prune: bool = False,
|
||||
prune_max_delete: int = 10,
|
||||
prune_protect_bases: bool = False,
|
||||
|
||||
@@ -19,6 +19,7 @@ class Command(BaseCommand):
|
||||
parser.add_argument("--prefix", default=settings.POBSYNC_HOME, help="Runtime state root")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Run rsync --dry-run")
|
||||
parser.add_argument("--verbose-rsync", action="store_true", help="Write itemized rsync output to the run log")
|
||||
parser.add_argument("--quiet-rsync", action="store_true", help="Skip default rsync progress output for real runs")
|
||||
parser.add_argument("--prune", action="store_true", help="Apply retention after a successful run")
|
||||
parser.add_argument("--prune-max-delete", type=int, default=10)
|
||||
parser.add_argument("--prune-protect-bases", action="store_true")
|
||||
@@ -32,6 +33,7 @@ class Command(BaseCommand):
|
||||
except HostConfig.DoesNotExist as exc:
|
||||
raise CommandError(f"Missing enabled host {host_name!r}") from exc
|
||||
|
||||
verbose_output = bool(options["dry_run"] or options["verbose_rsync"] or not options["quiet_rsync"])
|
||||
run = BackupRun.objects.create(
|
||||
host=host,
|
||||
run_type=BackupRun.RunType.MANUAL if options["manual"] else BackupRun.RunType.SCHEDULED,
|
||||
@@ -39,7 +41,7 @@ class Command(BaseCommand):
|
||||
result={
|
||||
"requested": {
|
||||
"dry_run": bool(options["dry_run"]),
|
||||
"verbose_output": bool(options["dry_run"] or options["verbose_rsync"]),
|
||||
"verbose_output": verbose_output,
|
||||
"prune": bool(options["prune"]),
|
||||
"prune_max_delete": int(options["prune_max_delete"]),
|
||||
"prune_protect_bases": bool(options["prune_protect_bases"]),
|
||||
@@ -50,7 +52,7 @@ class Command(BaseCommand):
|
||||
run=run,
|
||||
prefix=paths.home,
|
||||
dry_run=bool(options["dry_run"]),
|
||||
verbose_output=bool(options["dry_run"] or options["verbose_rsync"]),
|
||||
verbose_output=verbose_output,
|
||||
prune=bool(options["prune"]),
|
||||
prune_max_delete=int(options["prune_max_delete"]),
|
||||
prune_protect_bases=bool(options["prune_protect_bases"]),
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
</form>
|
||||
<form class="inline-form" method="post" action="{% url 'queue_manual_backup' host.host %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="verbose_output" value="on">
|
||||
<input type="hidden" name="prune_max_delete" value="10">
|
||||
<button type="submit" {% if not can_queue_real_backup %}disabled{% endif %}>Queue backup</button>
|
||||
</form>
|
||||
|
||||
@@ -39,13 +39,20 @@ class BackupWorkerTests(TestCase):
|
||||
},
|
||||
)
|
||||
|
||||
def test_queue_backup_run_can_request_verbose_output(self) -> None:
|
||||
def test_queue_backup_run_enables_verbose_output_by_default(self) -> None:
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
|
||||
run = queue_backup_run(host=host, verbose_output=True)
|
||||
run = queue_backup_run(host=host)
|
||||
|
||||
self.assertTrue(run.result["requested"]["verbose_output"])
|
||||
|
||||
def test_queue_backup_run_can_disable_verbose_output(self) -> None:
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
|
||||
run = queue_backup_run(host=host, verbose_output=False)
|
||||
|
||||
self.assertFalse(run.result["requested"]["verbose_output"])
|
||||
|
||||
def test_worker_executes_next_queued_run(self) -> None:
|
||||
with TemporaryDirectory() as tmp:
|
||||
backup_root = Path(tmp) / "backups"
|
||||
|
||||
@@ -39,12 +39,16 @@ class RunBackupRecordsSnapshotTests(TestCase):
|
||||
"host": host.host,
|
||||
"snapshot": str(snapshot_dir),
|
||||
"base": None,
|
||||
"verbose_output": True,
|
||||
"rsync": {"exit_code": 0},
|
||||
}
|
||||
call_command("run_pobsync_backup", host.host, prefix=str(Path(tmp) / "home"), stdout=StringIO())
|
||||
|
||||
run_scheduled.assert_called_once()
|
||||
self.assertTrue(run_scheduled.call_args.kwargs["verbose_output"])
|
||||
self.assertEqual(BackupRun.objects.count(), 1)
|
||||
run = BackupRun.objects.get()
|
||||
self.assertTrue(run.result["verbose_output"])
|
||||
self.assertEqual(SnapshotRecord.objects.count(), 1)
|
||||
record = SnapshotRecord.objects.get()
|
||||
self.assertEqual(run.snapshot, record)
|
||||
@@ -52,6 +56,45 @@ class RunBackupRecordsSnapshotTests(TestCase):
|
||||
self.assertEqual(record.kind, "scheduled")
|
||||
self.assertEqual(record.status, "success")
|
||||
|
||||
def test_backup_command_can_skip_default_verbose_rsync_output(self) -> None:
|
||||
with TemporaryDirectory() as tmp:
|
||||
backup_root = Path(tmp) / "backups"
|
||||
GlobalConfig.objects.create(name="default", backup_root=str(backup_root))
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
snapshot_dir = backup_root / host.host / "scheduled" / "20260519-021500Z__ABCDEFGH"
|
||||
meta_dir = snapshot_dir / "meta"
|
||||
meta_dir.mkdir(parents=True)
|
||||
write_yaml_atomic(
|
||||
meta_dir / "meta.yaml",
|
||||
{
|
||||
"status": "success",
|
||||
"started_at": "2026-05-19T02:15:00Z",
|
||||
"ended_at": "2026-05-19T02:16:00Z",
|
||||
},
|
||||
)
|
||||
|
||||
with patch("pobsync_backend.backup_runner.run_scheduled") as run_scheduled:
|
||||
run_scheduled.return_value = {
|
||||
"ok": True,
|
||||
"dry_run": False,
|
||||
"host": host.host,
|
||||
"snapshot": str(snapshot_dir),
|
||||
"base": None,
|
||||
"verbose_output": False,
|
||||
"rsync": {"exit_code": 0},
|
||||
}
|
||||
call_command(
|
||||
"run_pobsync_backup",
|
||||
host.host,
|
||||
prefix=str(Path(tmp) / "home"),
|
||||
quiet_rsync=True,
|
||||
stdout=StringIO(),
|
||||
)
|
||||
|
||||
run_scheduled.assert_called_once()
|
||||
self.assertFalse(run_scheduled.call_args.kwargs["verbose_output"])
|
||||
self.assertFalse(BackupRun.objects.get().result["verbose_output"])
|
||||
|
||||
def test_prune_uses_sql_retention_after_snapshot_record_is_created(self) -> None:
|
||||
with TemporaryDirectory() as tmp:
|
||||
backup_root = Path(tmp) / "backups"
|
||||
|
||||
@@ -1372,7 +1372,7 @@ class ViewTests(TestCase):
|
||||
|
||||
response = self.client.post(
|
||||
reverse("queue_manual_backup", args=[host.host]),
|
||||
{"prune_max_delete": "10"},
|
||||
{"verbose_output": "on", "prune_max_delete": "10"},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
@@ -1610,7 +1610,7 @@ class ViewTests(TestCase):
|
||||
|
||||
response = self.client.post(
|
||||
reverse("queue_manual_backup", args=[host.host]),
|
||||
{"prune_max_delete": "10"},
|
||||
{"verbose_output": "on", "prune_max_delete": "10"},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
@@ -1620,7 +1620,7 @@ class ViewTests(TestCase):
|
||||
run.result["requested"],
|
||||
{
|
||||
"dry_run": False,
|
||||
"verbose_output": False,
|
||||
"verbose_output": True,
|
||||
"prune": False,
|
||||
"prune_max_delete": 10,
|
||||
"prune_protect_bases": False,
|
||||
|
||||
@@ -1301,6 +1301,7 @@ def _default_manual_backup_initial(host_config: HostConfig) -> dict[str, object]
|
||||
schedule = _schedule_for_host(host_config)
|
||||
return {
|
||||
"dry_run": True,
|
||||
"verbose_output": True,
|
||||
"prune": bool(schedule.prune) if schedule else False,
|
||||
"prune_max_delete": schedule.prune_max_delete if schedule else 10,
|
||||
"prune_protect_bases": bool(schedule.prune_protect_bases) if schedule else False,
|
||||
|
||||
Reference in New Issue
Block a user