mirror of
https://github.com/borgmatic-collective/borgmatic.git
synced 2026-07-22 18:13:02 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6feca169c | ||
|
|
05e2900ab0 | ||
|
|
30b52e5523 | ||
|
|
14aeddc11f | ||
|
|
066399ecdb | ||
|
|
d4bbac4467 | ||
|
|
7516443a89 | ||
|
|
73d67e29b4 |
@@ -1,3 +1,8 @@
|
||||
1.2.13
|
||||
* #100: Support for --stats command-line flag independent of --verbosity.
|
||||
* #117: With borgmatic --init command-line flag, proceed without erroring if a repository already
|
||||
exists.
|
||||
|
||||
1.2.12
|
||||
* #110: Support for Borg repository initialization via borgmatic --init command-line flag.
|
||||
* #111: Update Borg create --filter values so a dry run lists files to back up.
|
||||
|
||||
@@ -146,6 +146,10 @@ Also, optionally check out the [Borg Quick
|
||||
Start](https://borgbackup.readthedocs.org/en/latest/quickstart.html) for more
|
||||
background about repository initialization.
|
||||
|
||||
Note that borgmatic skips repository initialization if the repository already
|
||||
exists. This supports use cases like ensuring a repository exists prior to
|
||||
performing a backup.
|
||||
|
||||
If the repository is on a remote host, make sure that your local user has
|
||||
key-based SSH access to the desired user account on the remote host.
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ def create_archive(
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
progress=False,
|
||||
stats=False,
|
||||
json=False,
|
||||
):
|
||||
'''
|
||||
@@ -135,7 +136,7 @@ def create_archive(
|
||||
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
|
||||
+ (('--list', '--filter', 'AME-') if logger.isEnabledFor(logging.INFO) else ())
|
||||
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
|
||||
+ (('--stats',) if not dry_run and logger.isEnabledFor(logging.INFO) else ())
|
||||
+ (('--stats',) if not dry_run and (logger.isEnabledFor(logging.INFO) or stats) else ())
|
||||
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
|
||||
+ (('--dry-run',) if dry_run else ())
|
||||
+ (('--progress',) if progress else ())
|
||||
|
||||
+12
-4
@@ -15,9 +15,17 @@ def initialize_repository(
|
||||
):
|
||||
'''
|
||||
Given a local or remote repository path, a Borg encryption mode, whether the repository should
|
||||
be append-only, and the storage quota to use, initialize the repository.
|
||||
be append-only, and the storage quota to use, initialize the repository. If the repository
|
||||
already exists, then log and skip initialization.
|
||||
'''
|
||||
full_command = (
|
||||
info_command = (local_path, 'info', repository)
|
||||
logger.debug(' '.join(info_command))
|
||||
|
||||
if subprocess.call(info_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0:
|
||||
logger.info('Repository already exists. Skipping initialization.')
|
||||
return
|
||||
|
||||
init_command = (
|
||||
(local_path, 'init', repository)
|
||||
+ (('--encryption', encryption_mode) if encryption_mode else ())
|
||||
+ (('--append-only',) if append_only else ())
|
||||
@@ -27,5 +35,5 @@ def initialize_repository(
|
||||
+ (('--remote-path', remote_path) if remote_path else ())
|
||||
)
|
||||
|
||||
logger.debug(' '.join(full_command))
|
||||
subprocess.check_call(full_command)
|
||||
logger.debug(' '.join(init_command))
|
||||
subprocess.check_call(init_command)
|
||||
|
||||
@@ -31,7 +31,13 @@ def _make_prune_flags(retention_config):
|
||||
|
||||
|
||||
def prune_archives(
|
||||
dry_run, repository, storage_config, retention_config, local_path='borg', remote_path=None
|
||||
dry_run,
|
||||
repository,
|
||||
storage_config,
|
||||
retention_config,
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
stats=False,
|
||||
):
|
||||
'''
|
||||
Given dry-run flag, a local or remote repository path, a storage config dict, and a
|
||||
@@ -51,6 +57,7 @@ def prune_archives(
|
||||
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
|
||||
+ (('--debug', '--list', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
|
||||
+ (('--dry-run',) if dry_run else ())
|
||||
+ (('--stats',) if stats else ())
|
||||
)
|
||||
|
||||
logger.debug(' '.join(full_command))
|
||||
|
||||
@@ -107,6 +107,13 @@ def parse_arguments(*arguments):
|
||||
action='store_true',
|
||||
help='Display progress with --create option for each file as it is backed up',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--stats',
|
||||
dest='stats',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Display statistics of archive with --create or --prune option',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--json',
|
||||
dest='json',
|
||||
@@ -142,16 +149,17 @@ def parse_arguments(*arguments):
|
||||
'The --encryption, --append-only, and --storage-quota options can only be used with the --init option'
|
||||
)
|
||||
|
||||
if args.init and (args.prune or args.create or args.dry_run):
|
||||
raise ValueError(
|
||||
'The --init option cannot be used with the --prune, --create, or --dry-run options'
|
||||
)
|
||||
if args.init and args.dry_run:
|
||||
raise ValueError('The --init option cannot be used with the --dry-run option')
|
||||
if args.init and not args.encryption_mode:
|
||||
raise ValueError('The --encryption option is required with the --init option')
|
||||
|
||||
if args.progress and not args.create:
|
||||
raise ValueError('The --progress option can only be used with the --create option')
|
||||
|
||||
if args.stats and not (args.create or args.prune):
|
||||
raise ValueError('The --stats option can only be used with the --create or --prune options')
|
||||
|
||||
if args.json and not (args.create or args.list or args.info):
|
||||
raise ValueError(
|
||||
'The --json option can only be used with the --create, --list, or --info options'
|
||||
@@ -261,6 +269,7 @@ def _run_commands_on_repository(
|
||||
retention,
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
stats=args.stats,
|
||||
)
|
||||
if args.create:
|
||||
logger.info('{}: Creating archive{}'.format(repository, dry_run_label))
|
||||
@@ -272,6 +281,7 @@ def _run_commands_on_repository(
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
progress=args.progress,
|
||||
stats=args.stats,
|
||||
)
|
||||
if args.check and checks.repository_enabled_for_checks(repository, consistency):
|
||||
logger.info('{}: Running consistency checks'.format(repository))
|
||||
|
||||
@@ -41,6 +41,7 @@ for sub_command in prune create check list info; do
|
||||
| grep -v '^--nobsdflags$' \
|
||||
| grep -v '^--pattern$' \
|
||||
| grep -v '^--progress$' \
|
||||
| grep -v '^--stats$' \
|
||||
| grep -v '^--read-special$' \
|
||||
| grep -v '^--repository-only$' \
|
||||
| grep -v '^--show-rc$' \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
VERSION = '1.2.12'
|
||||
VERSION = '1.2.13'
|
||||
|
||||
|
||||
setup(
|
||||
|
||||
@@ -113,25 +113,25 @@ def test_parse_arguments_disallows_storage_quota_without_init():
|
||||
module.parse_arguments('--config', 'myconfig', '--storage-quota', '5G')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_init_and_prune():
|
||||
def test_parse_arguments_allows_init_and_prune():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--config', 'myconfig', '--init', '--prune')
|
||||
module.parse_arguments('--config', 'myconfig', '--init', '--encryption', 'repokey', '--prune')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_init_and_create():
|
||||
def test_parse_arguments_allows_init_and_create():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--config', 'myconfig', '--init', '--create')
|
||||
module.parse_arguments('--config', 'myconfig', '--init', '--encryption', 'repokey', '--create')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_init_and_dry_run():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--config', 'myconfig', '--init', '--dry-run')
|
||||
module.parse_arguments(
|
||||
'--config', 'myconfig', '--init', '--encryption', 'repokey', '--dry-run'
|
||||
)
|
||||
|
||||
|
||||
def test_parse_arguments_allows_progress_and_create():
|
||||
@@ -143,6 +143,19 @@ def test_parse_arguments_disallows_progress_without_create():
|
||||
module.parse_arguments('--progress', '--list')
|
||||
|
||||
|
||||
def test_parse_arguments_with_stats_and_create_flags_does_not_raise():
|
||||
module.parse_arguments('--stats', '--create', '--list')
|
||||
|
||||
|
||||
def test_parse_arguments_with_stats_and_prune_flags_does_not_raise():
|
||||
module.parse_arguments('--stats', '--prune', '--list')
|
||||
|
||||
|
||||
def test_parse_arguments_with_stats_flag_but_no_create_or_prune_flag_raises_value_error():
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--stats', '--list')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_json_with_list_or_info():
|
||||
module.parse_arguments('--list', '--json')
|
||||
module.parse_arguments('--info', '--json')
|
||||
|
||||
@@ -6,53 +6,74 @@ from borgmatic.borg import init as module
|
||||
from ..test_verbosity import insert_logging_mock
|
||||
|
||||
|
||||
def insert_subprocess_mock(check_call_command, **kwargs):
|
||||
subprocess = flexmock(module.subprocess)
|
||||
subprocess.should_receive('check_call').with_args(check_call_command, **kwargs).once()
|
||||
|
||||
|
||||
INFO_REPOSITORY_EXISTS_RESPONSE_CODE = 0
|
||||
INFO_REPOSITORY_NOT_FOUND_RESPONSE_CODE = 2
|
||||
INIT_COMMAND = ('borg', 'init', 'repo', '--encryption', 'repokey')
|
||||
|
||||
|
||||
def insert_info_command_mock(info_response):
|
||||
subprocess = flexmock(module.subprocess)
|
||||
subprocess.should_receive('call').and_return(info_response)
|
||||
|
||||
|
||||
def insert_init_command_mock(init_command, **kwargs):
|
||||
subprocess = flexmock(module.subprocess)
|
||||
subprocess.should_receive('check_call').with_args(init_command, **kwargs).once()
|
||||
|
||||
|
||||
def test_initialize_repository_calls_borg_with_parameters():
|
||||
insert_subprocess_mock(INIT_COMMAND)
|
||||
insert_info_command_mock(INFO_REPOSITORY_NOT_FOUND_RESPONSE_CODE)
|
||||
insert_init_command_mock(INIT_COMMAND)
|
||||
|
||||
module.initialize_repository(repository='repo', encryption_mode='repokey')
|
||||
|
||||
|
||||
def test_initialize_repository_skips_initialization_when_repository_already_exists():
|
||||
insert_info_command_mock(INFO_REPOSITORY_EXISTS_RESPONSE_CODE)
|
||||
flexmock(module.subprocess).should_receive('check_call').never()
|
||||
|
||||
module.initialize_repository(repository='repo', encryption_mode='repokey')
|
||||
|
||||
|
||||
def test_initialize_repository_with_append_only_calls_borg_with_append_only_parameter():
|
||||
insert_subprocess_mock(INIT_COMMAND + ('--append-only',))
|
||||
insert_info_command_mock(INFO_REPOSITORY_NOT_FOUND_RESPONSE_CODE)
|
||||
insert_init_command_mock(INIT_COMMAND + ('--append-only',))
|
||||
|
||||
module.initialize_repository(repository='repo', encryption_mode='repokey', append_only=True)
|
||||
|
||||
|
||||
def test_initialize_repository_with_storage_quota_calls_borg_with_storage_quota_parameter():
|
||||
insert_subprocess_mock(INIT_COMMAND + ('--storage-quota', '5G'))
|
||||
insert_info_command_mock(INFO_REPOSITORY_NOT_FOUND_RESPONSE_CODE)
|
||||
insert_init_command_mock(INIT_COMMAND + ('--storage-quota', '5G'))
|
||||
|
||||
module.initialize_repository(repository='repo', encryption_mode='repokey', storage_quota='5G')
|
||||
|
||||
|
||||
def test_initialize_repository_with_log_info_calls_borg_with_info_parameter():
|
||||
insert_subprocess_mock(INIT_COMMAND + ('--info',))
|
||||
insert_info_command_mock(INFO_REPOSITORY_NOT_FOUND_RESPONSE_CODE)
|
||||
insert_init_command_mock(INIT_COMMAND + ('--info',))
|
||||
insert_logging_mock(logging.INFO)
|
||||
|
||||
module.initialize_repository(repository='repo', encryption_mode='repokey')
|
||||
|
||||
|
||||
def test_initialize_repository_with_log_debug_calls_borg_with_debug_parameter():
|
||||
insert_subprocess_mock(INIT_COMMAND + ('--debug',))
|
||||
insert_info_command_mock(INFO_REPOSITORY_NOT_FOUND_RESPONSE_CODE)
|
||||
insert_init_command_mock(INIT_COMMAND + ('--debug',))
|
||||
insert_logging_mock(logging.DEBUG)
|
||||
|
||||
module.initialize_repository(repository='repo', encryption_mode='repokey')
|
||||
|
||||
|
||||
def test_initialize_repository_with_local_path_calls_borg_via_local_path():
|
||||
insert_subprocess_mock(('borg1',) + INIT_COMMAND[1:])
|
||||
insert_info_command_mock(INFO_REPOSITORY_NOT_FOUND_RESPONSE_CODE)
|
||||
insert_init_command_mock(('borg1',) + INIT_COMMAND[1:])
|
||||
|
||||
module.initialize_repository(repository='repo', encryption_mode='repokey', local_path='borg1')
|
||||
|
||||
|
||||
def test_initialize_repository_with_remote_path_calls_borg_with_remote_path_parameter():
|
||||
insert_subprocess_mock(INIT_COMMAND + ('--remote-path', 'borg1'))
|
||||
insert_info_command_mock(INFO_REPOSITORY_NOT_FOUND_RESPONSE_CODE)
|
||||
insert_init_command_mock(INIT_COMMAND + ('--remote-path', 'borg1'))
|
||||
|
||||
module.initialize_repository(repository='repo', encryption_mode='repokey', remote_path='borg1')
|
||||
|
||||
Reference in New Issue
Block a user