Skip to content

Commit 4e05e86

Browse files
committed
Add option to backup and prune for tags
This adds the options for both backup and prune to allow specifying tags. For backup it adds the specified tags to the backup snapshot. For prune it will ensure only snapshots with the specified tags are forgotten. Tested by adding unit tests and then: ``` poetry run pytest -k test_runner ```
1 parent 730cfe0 commit 4e05e86

File tree

3 files changed

+72
-1
lines changed

3 files changed

+72
-1
lines changed

runrestic/restic/runner.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ def backup(self) -> None:
154154
extra_args += ["--exclude-file", exclude_file]
155155
for exclude_if_present in cfg.get("exclude_if_present", []):
156156
extra_args += ["--exclude-if-present", exclude_if_present]
157+
for tag in cfg.get("tags", []):
158+
extra_args += ["--tag", tag]
157159

158160
commands = [
159161
[
@@ -233,6 +235,9 @@ def forget(self) -> None:
233235
extra_args += [f"--{key}", str(value)]
234236
if key == "group-by":
235237
extra_args += ["--group-by", value]
238+
if key == "tags":
239+
for tag in value:
240+
extra_args += ["--tag", tag]
236241

237242
direct_abort_reasons = [
238243
"Fatal: unable to open config file",

runrestic/runrestic/schema.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"exclude_patterns": {"type": "array", "items": {"type": "string"}},
6767
"exclude_files": {"type": "array", "items": {"type": "string"}},
6868
"exclude_if_present": {"type": "array", "items": {"type": "string"}},
69+
"tags": {"type": "array", "items": {"type": "string"}},
6970
"pre_hooks": {"type": "array", "items": {"type": "string"}},
7071
"post_hooks": {"type": "array", "items": {"type": "string"}},
7172
"continue_on_pre_hooks_error": {"type": "boolean", "default": false}
@@ -93,7 +94,8 @@
9394
"keep-yearly": {"type": "integer"},
9495
"keep-within": {"type": "string"},
9596
"keep-tag": {"type": "string"},
96-
"group-by": {"type": "string"}
97+
"group-by": {"type": "string"},
98+
"tags": {"type": "array", "items": {"type": "string"}}
9799
}
98100
},
99101

tests/restic/test_runner.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ def test_backup_metrics(self, mock_redact, mock_parse_backup, mock_mc):
244244
"exclude_patterns": ["*.exclude"],
245245
"exclude_files": ["dummy_file"],
246246
"exclude_if_present": ["*.present"],
247+
"tags": ["tagone", "tagtwo"],
247248
},
248249
"metrics": {},
249250
}
@@ -273,6 +274,10 @@ def test_backup_metrics(self, mock_redact, mock_parse_backup, mock_mc):
273274
"dummy_file",
274275
"--exclude-if-present",
275276
"*.present",
277+
"--tag",
278+
"tagone",
279+
"--tag",
280+
"tagtwo",
276281
"/data",
277282
],
278283
[
@@ -289,6 +294,10 @@ def test_backup_metrics(self, mock_redact, mock_parse_backup, mock_mc):
289294
"dummy_file",
290295
"--exclude-if-present",
291296
"*.present",
297+
"--tag",
298+
"tagone",
299+
"--tag",
300+
"tagtwo",
292301
"/data",
293302
],
294303
]
@@ -463,6 +472,61 @@ def test_forget_metrics(self, mock_redact, mock_parse_forget, mock_mc):
463472
self.assertEqual(metrics["repo"], {"forgotten": True})
464473
self.assertEqual(runner_instance.metrics["errors"], 0)
465474

475+
@patch("runrestic.restic.runner.MultiCommand")
476+
@patch("runrestic.restic.runner.parse_forget")
477+
@patch(
478+
"runrestic.restic.runner.redact_password", side_effect=lambda repo, repl: repo
479+
)
480+
def test_forget_with_tags(self, mock_redact, mock_parse_forget, mock_mc):
481+
"""
482+
Test forget() handles tags correctly.
483+
"""
484+
config = {
485+
"repositories": ["repo"],
486+
"environment": {},
487+
"execution": {},
488+
"prune": {
489+
"keep-last": 3,
490+
"tags": ["tagone", "tagtwo"],
491+
},
492+
}
493+
args = Namespace(dry_run=True)
494+
restic_args: list[str] = []
495+
runner_instance = runner.ResticRunner(config, args, restic_args)
496+
process_info = {"output": [(0, "")], "time": 0.1}
497+
mock_mc.return_value.run.return_value = [process_info]
498+
mock_parse_forget.return_value = {"forgotten": True}
499+
500+
runner_instance.forget()
501+
metrics = runner_instance.metrics["forget"]
502+
self.assertEqual(metrics["repo"], {"forgotten": True})
503+
self.assertEqual(runner_instance.metrics["errors"], 0)
504+
505+
# Ensure "--tag tagone --tag tagtwo" appears in the command
506+
expected_cmds = [
507+
[
508+
"restic",
509+
"-r",
510+
"repo",
511+
"forget",
512+
"--dry-run",
513+
"--keep-last",
514+
"3",
515+
"--tag",
516+
"tagone",
517+
"--tag",
518+
"tagtwo",
519+
]
520+
]
521+
mock_mc.assert_called_once_with(
522+
expected_cmds,
523+
config=config["execution"],
524+
abort_reasons=[
525+
"Fatal: unable to open config file",
526+
"Fatal: wrong password",
527+
],
528+
)
529+
466530
@patch("runrestic.restic.runner.MultiCommand")
467531
@patch("runrestic.restic.runner.parse_forget")
468532
@patch(

0 commit comments

Comments
 (0)