13 changed files with 436 additions and 130 deletions
@ -0,0 +1,45 @@
|
||||
import logging |
||||
|
||||
from borgmatic.borg.flags import make_flags |
||||
from borgmatic.execute import execute_command |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
|
||||
REPOSITORYLESS_BORG_COMMANDS = {'serve', None} |
||||
|
||||
|
||||
def run_arbitrary_borg( |
||||
repository, storage_config, options, archive=None, local_path='borg', remote_path=None |
||||
): |
||||
''' |
||||
Given a local or remote repository path, a storage config dict, a sequence of arbitrary |
||||
command-line Borg options, and an optional archive name, run an arbitrary Borg command on the |
||||
given repository/archive. |
||||
''' |
||||
lock_wait = storage_config.get('lock_wait', None) |
||||
|
||||
try: |
||||
options = options[1:] if options[0] == '--' else options |
||||
borg_command = options[0] |
||||
command_options = tuple(options[1:]) |
||||
except IndexError: |
||||
borg_command = None |
||||
command_options = () |
||||
|
||||
repository_archive = '::'.join((repository, archive)) if repository and archive else repository |
||||
|
||||
full_command = ( |
||||
(local_path,) |
||||
+ ((borg_command,) if borg_command else ()) |
||||
+ ((repository_archive,) if borg_command and repository_archive else ()) |
||||
+ command_options |
||||
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) |
||||
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ()) |
||||
+ make_flags('remote-path', remote_path) |
||||
+ make_flags('lock-wait', lock_wait) |
||||
) |
||||
|
||||
return execute_command( |
||||
full_command, output_log_level=logging.WARNING, borg_local_path=local_path, |
||||
) |
@ -0,0 +1,94 @@
|
||||
--- |
||||
title: How to run arbitrary Borg commands |
||||
eleventyNavigation: |
||||
key: Run arbitrary Borg commands |
||||
parent: How-to guides |
||||
order: 10 |
||||
--- |
||||
## Running Borg with borgmatic |
||||
|
||||
Borg has several commands (and options) that borgmatic does not currently |
||||
support. Sometimes though, as a borgmatic user, you may find yourself wanting |
||||
to take advantage of these off-the-beaten-path Borg features. You could of |
||||
course drop down to running Borg directly. But then you'd give up all the |
||||
niceties of your borgmatic configuration. You could file a [borgmatic |
||||
ticket](https://torsion.org/borgmatic/#issues) or even a [pull |
||||
request](https://torsion.org/borgmatic/#contributing) to add the feature. But |
||||
what if you need it *now*? |
||||
|
||||
That's where borgmatic's support for running "arbitrary" Borg commands comes |
||||
in. Running Borg commands with borgmatic takes advantage of the following, all |
||||
based on your borgmatic configuration files or command-line arguments: |
||||
|
||||
* configured repositories (automatically runs your Borg command once for each |
||||
one) |
||||
* local and remote Borg binary paths |
||||
* SSH settings and Borg environment variables |
||||
* lock wait settings |
||||
* verbosity |
||||
|
||||
|
||||
### borg action |
||||
|
||||
The way you run Borg with borgmatic is via the `borg` action. Here's a simple |
||||
example: |
||||
|
||||
```bash |
||||
borgmatic borg break-lock |
||||
``` |
||||
|
||||
(No `borg` action in borgmatic? Time to upgrade!) |
||||
|
||||
This runs Borg's `break-lock` command once on each configured borgmatic |
||||
repository. Notice how the repository isn't present in the specified Borg |
||||
options, as that part is provided by borgmatic. |
||||
|
||||
You can also specify Borg options for relevant commands: |
||||
|
||||
```bash |
||||
borgmatic borg list --progress |
||||
``` |
||||
|
||||
This runs Borg's `list` command once on each configured borgmatic |
||||
repository. However, the native `borgmatic list` action should be preferred |
||||
for most use. |
||||
|
||||
What if you only want to run Borg on a single configured borgmatic repository |
||||
when you've got several configured? Not a problem. |
||||
|
||||
```bash |
||||
borgmatic borg --repository repo.borg break-lock |
||||
``` |
||||
|
||||
And what about a single archive? |
||||
|
||||
```bash |
||||
borgmatic borg --archive your-archive-name list |
||||
``` |
||||
|
||||
### Limitations |
||||
|
||||
borgmatic's `borg` action is not without limitations: |
||||
|
||||
* The Borg command you want to run (`create`, `list`, etc.) *must* come first |
||||
after the `borg` action. If you have any other Borg options to specify, |
||||
provide them after. For instance, `borgmatic borg list --progress` will work, |
||||
but `borgmatic borg --progress list` will not. |
||||
* borgmatic supplies the repository/archive name to Borg for you (based on |
||||
your borgmatic configuration or the `borgmatic borg --repository`/`--archive` |
||||
arguments), so do not specify the repository/archive otherwise. |
||||
* The `borg` action will not currently work for any Borg commands like `borg |
||||
serve` that do not accept a repository/archive name. |
||||
* Do not specify any global borgmatic arguments to the right of the `borg` |
||||
action. (They will be passed to Borg instead of borgmatic.) If you have |
||||
global borgmatic arguments, specify them *before* the `borg` action. |
||||
* Unlike other borgmatic actions, you cannot combine the `borg` action with |
||||
other borgmatic actions. This is to prevent ambiguity in commands like |
||||
`borgmatic borg list`, in which `list` is both a valid Borg command and a |
||||
borgmatic action. In this case, only the Borg command is run. |
||||
* Unlike normal borgmatic actions that support JSON, the `borg` action will |
||||
not disable certain borgmatic logs to avoid interfering with JSON output. |
||||
|
||||
In general, this `borgmatic borg` feature should be considered an escape |
||||
valve—a feature of second resort. In the long run, it's preferable to wrap |
||||
Borg commands with borgmatic actions that can support them fully. |
@ -0,0 +1,123 @@
|
||||
import logging |
||||
|
||||
from flexmock import flexmock |
||||
|
||||
from borgmatic.borg import borg as module |
||||
|
||||
from ..test_verbosity import insert_logging_mock |
||||
|
||||
|
||||
def test_run_arbitrary_borg_calls_borg_with_parameters(): |
||||
flexmock(module).should_receive('execute_command').with_args( |
||||
('borg', 'break-lock', 'repo'), output_log_level=logging.WARNING, borg_local_path='borg' |
||||
) |
||||
|
||||
module.run_arbitrary_borg( |
||||
repository='repo', storage_config={}, options=['break-lock'], |
||||
) |
||||
|
||||
|
||||
def test_run_arbitrary_borg_with_log_info_calls_borg_with_info_parameter(): |
||||
flexmock(module).should_receive('execute_command').with_args( |
||||
('borg', 'break-lock', 'repo', '--info'), |
||||
output_log_level=logging.WARNING, |
||||
borg_local_path='borg', |
||||
) |
||||
insert_logging_mock(logging.INFO) |
||||
|
||||
module.run_arbitrary_borg( |
||||
repository='repo', storage_config={}, options=['break-lock'], |
||||
) |
||||
|
||||
|
||||
def test_run_arbitrary_borg_with_log_debug_calls_borg_with_debug_parameter(): |
||||
flexmock(module).should_receive('execute_command').with_args( |
||||
('borg', 'break-lock', 'repo', '--debug', '--show-rc'), |
||||
output_log_level=logging.WARNING, |
||||
borg_local_path='borg', |
||||
) |
||||
insert_logging_mock(logging.DEBUG) |
||||
|
||||
module.run_arbitrary_borg( |
||||
repository='repo', storage_config={}, options=['break-lock'], |
||||
) |
||||
|
||||
|
||||
def test_run_arbitrary_borg_with_lock_wait_calls_borg_with_lock_wait_parameters(): |
||||
storage_config = {'lock_wait': 5} |
||||
flexmock(module).should_receive('execute_command').with_args( |
||||
('borg', 'break-lock', 'repo', '--lock-wait', '5'), |
||||
output_log_level=logging.WARNING, |
||||
borg_local_path='borg', |
||||
) |
||||
|
||||
module.run_arbitrary_borg( |
||||
repository='repo', storage_config=storage_config, options=['break-lock'], |
||||
) |
||||
|
||||
|
||||
def test_run_arbitrary_borg_with_archive_calls_borg_with_archive_parameter(): |
||||
storage_config = {} |
||||
flexmock(module).should_receive('execute_command').with_args( |
||||
('borg', 'break-lock', 'repo::archive'), |
||||
output_log_level=logging.WARNING, |
||||
borg_local_path='borg', |
||||
) |
||||
|
||||
module.run_arbitrary_borg( |
||||
repository='repo', storage_config=storage_config, options=['break-lock'], archive='archive', |
||||
) |
||||
|
||||
|
||||
def test_run_arbitrary_borg_with_local_path_calls_borg_via_local_path(): |
||||
flexmock(module).should_receive('execute_command').with_args( |
||||
('borg1', 'break-lock', 'repo'), output_log_level=logging.WARNING, borg_local_path='borg1' |
||||
) |
||||
|
||||
module.run_arbitrary_borg( |
||||
repository='repo', storage_config={}, options=['break-lock'], local_path='borg1', |
||||
) |
||||
|
||||
|
||||
def test_run_arbitrary_borg_with_remote_path_calls_borg_with_remote_path_parameters(): |
||||
flexmock(module).should_receive('execute_command').with_args( |
||||
('borg', 'break-lock', 'repo', '--remote-path', 'borg1'), |
||||
output_log_level=logging.WARNING, |
||||
borg_local_path='borg', |
||||
) |
||||
|
||||
module.run_arbitrary_borg( |
||||
repository='repo', storage_config={}, options=['break-lock'], remote_path='borg1', |
||||
) |
||||
|
||||
|
||||
def test_run_arbitrary_borg_passes_borg_specific_parameters_to_borg(): |
||||
flexmock(module).should_receive('execute_command').with_args( |
||||
('borg', 'list', 'repo', '--progress'), |
||||
output_log_level=logging.WARNING, |
||||
borg_local_path='borg', |
||||
) |
||||
|
||||
module.run_arbitrary_borg( |
||||
repository='repo', storage_config={}, options=['list', '--progress'], |
||||
) |
||||
|
||||
|
||||
def test_run_arbitrary_borg_omits_dash_dash_in_parameters_passed_to_borg(): |
||||
flexmock(module).should_receive('execute_command').with_args( |
||||
('borg', 'break-lock', 'repo'), output_log_level=logging.WARNING, borg_local_path='borg', |
||||
) |
||||
|
||||
module.run_arbitrary_borg( |
||||
repository='repo', storage_config={}, options=['--', 'break-lock'], |
||||
) |
||||
|
||||
|
||||
def test_run_arbitrary_borg_without_borg_specific_parameters_does_not_raise(): |
||||
flexmock(module).should_receive('execute_command').with_args( |
||||
('borg',), output_log_level=logging.WARNING, borg_local_path='borg', |
||||
) |
||||
|
||||
module.run_arbitrary_borg( |
||||
repository='repo', storage_config={}, options=[], |
||||
) |
Loading…
Reference in new issue