From f17fde399e78d4100950b2b55f55955400c20578 Mon Sep 17 00:00:00 2001 From: C-Duv <1466273+C-Duv@users.noreply.github.com> Date: Thu, 11 May 2023 01:42:55 +0200 Subject: [PATCH] Add "actions" runtime variable (#657) This commit adds the "actions" variable to the list of variables borgmatic can interpolate at runtime. This variable will contains the list (as a string of comma-separated elements) of all the actions borgmatic will run in this instance. This will allow running "intelligent" before_actions and after_actions hooks capable of behavior dependent of the actual action borgmatic will perform/has performed. Original need for this variable was to automatically mount and unmount an NFS remote folder. Issue: #657 --- borgmatic/commands/borgmatic.py | 1 + ...reparation-and-cleanup-steps-to-backups.md | 3 + docs/how-to/monitor-your-backups.md | 2 + tests/unit/commands/test_borgmatic.py | 88 ++++++++++++++++++- 4 files changed, 93 insertions(+), 1 deletion(-) diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 44396cd4..d2202996 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -272,6 +272,7 @@ def run_actions( global_arguments = arguments['global'] dry_run_label = ' (dry run; not making any changes)' if global_arguments.dry_run else '' hook_context = { + 'actions': ','.join(sorted(action for action in arguments.keys() if action != 'global')), 'repository': repository_path, # Deprecated: For backwards compatibility with borgmatic < 1.6.0. 'repositories': ','.join([repo['path'] for repo in location['repositories']]), diff --git a/docs/how-to/add-preparation-and-cleanup-steps-to-backups.md b/docs/how-to/add-preparation-and-cleanup-steps-to-backups.md index 426e1a85..8be3a2ed 100644 --- a/docs/how-to/add-preparation-and-cleanup-steps-to-backups.md +++ b/docs/how-to/add-preparation-and-cleanup-steps-to-backups.md @@ -64,6 +64,9 @@ values into the hook command: the borgmatic configuration filename and the paths of the current Borg repository. Here's the full set of supported variables you can use here: + * `actions` + New in version 1.7.13: + comma-separated list of all the action(s) borgmatic is running * `configuration_filename`: borgmatic configuration filename in which the hook was defined * `log_file` diff --git a/docs/how-to/monitor-your-backups.md b/docs/how-to/monitor-your-backups.md index 517f9c79..7ac97abd 100644 --- a/docs/how-to/monitor-your-backups.md +++ b/docs/how-to/monitor-your-backups.md @@ -108,6 +108,8 @@ In this example, when the error occurs, borgmatic interpolates runtime values into the hook command: the borgmatic configuration filename, and the path of the repository. Here's the full set of supported variables you can use here: + * `actions`: + comma-separated list of all the action(s) borgmatic will run * `configuration_filename`: borgmatic configuration filename in which the error occurred * `repository`: path of the repository in which the error occurred (may be diff --git a/tests/unit/commands/test_borgmatic.py b/tests/unit/commands/test_borgmatic.py index bd98c01f..518ad662 100644 --- a/tests/unit/commands/test_borgmatic.py +++ b/tests/unit/commands/test_borgmatic.py @@ -434,7 +434,7 @@ def test_run_actions_adds_log_file_to_hook_context(): location={'repositories': []}, storage=object, hooks={}, - hook_context={'repository': 'repo', 'repositories': '', 'log_file': 'foo'}, + hook_context={'repository': 'repo', 'repositories': '', 'log_file': 'foo', 'actions': 'create'}, local_borg_version=object, create_arguments=object, global_arguments=object, @@ -461,6 +461,92 @@ def test_run_actions_adds_log_file_to_hook_context(): assert result == (expected,) +def test_run_actions_adds_action_name_to_hook_context(): + flexmock(module).should_receive('add_custom_log_levels') + flexmock(module.command).should_receive('execute_hook') + expected = flexmock() + flexmock(borgmatic.actions.create).should_receive('run_create').with_args( + config_filename=object, + repository={'path': 'repo'}, + location={'repositories': []}, + storage=object, + hooks={}, + hook_context={'repository': 'repo', 'repositories': '', 'actions': 'create'}, + local_borg_version=object, + create_arguments=object, + global_arguments=object, + dry_run_label='', + local_path=object, + remote_path=object, + ).once().and_return(expected) + + result = tuple( + module.run_actions( + arguments={'global': flexmock(dry_run=False), 'create': flexmock()}, + config_filename=flexmock(), + location={'repositories': []}, + storage=flexmock(), + retention=flexmock(), + consistency=flexmock(), + hooks={}, + local_path=flexmock(), + remote_path=flexmock(), + local_borg_version=flexmock(), + repository={'path': 'repo'}, + ) + ) + assert result == (expected,) + + +def test_run_actions_adds_all_sorted_action_names_to_hook_context(): + flexmock(module).should_receive('add_custom_log_levels') + flexmock(module.command).should_receive('execute_hook') + expected = flexmock() + flexmock(borgmatic.actions.check).should_receive('run_check').with_args( + config_filename=object, + repository={'path': 'repo'}, + location={'repositories': []}, + storage=object, + consistency=object, + hooks={}, + hook_context={'repository': 'repo', 'repositories': '', 'actions': 'check,prune'}, + local_borg_version=object, + check_arguments=object, + global_arguments=object, + local_path=object, + remote_path=object, + ).once() + + flexmock(borgmatic.actions.prune).should_receive('run_prune').with_args( + config_filename=object, + repository={'path': 'repo'}, + storage=object, + retention=object, + hooks={}, + hook_context={'repository': 'repo', 'repositories': '', 'actions': 'check,prune'}, + local_borg_version=object, + prune_arguments=object, + global_arguments=object, + dry_run_label='', + local_path=object, + remote_path=object, + ).once() + + module.run_actions( + arguments={'global': flexmock(dry_run=False), 'prune': flexmock(), 'check': flexmock()}, + config_filename=flexmock(), + location={'repositories': []}, + storage=flexmock(), + retention=flexmock(), + consistency=flexmock(), + hooks={}, + local_path=flexmock(), + remote_path=flexmock(), + local_borg_version=flexmock(), + repository={'path': 'repo'}, + ) + + def test_run_actions_runs_transfer(): flexmock(module).should_receive('add_custom_log_levels') flexmock(module.command).should_receive('execute_hook')