With --init command-line flag, if a repository already exists, proceed without erroring (#117).
This commit is contained in:
parent
14aeddc11f
commit
30b52e5523
6 changed files with 60 additions and 27 deletions
2
NEWS
2
NEWS
|
@ -1,5 +1,7 @@
|
||||||
1.2.13.dev0
|
1.2.13.dev0
|
||||||
* #100: Support for --stats command-line flag independent of --verbosity.
|
* #100: Support for --stats command-line flag independent of --verbosity.
|
||||||
|
* #117: With borgmatic --init command-line flag, if a repository already exists, proceed without
|
||||||
|
erroring.
|
||||||
|
|
||||||
1.2.12
|
1.2.12
|
||||||
* #110: Support for Borg repository initialization via borgmatic --init command-line flag.
|
* #110: Support for Borg repository initialization via borgmatic --init command-line flag.
|
||||||
|
|
|
@ -146,6 +146,10 @@ Also, optionally check out the [Borg Quick
|
||||||
Start](https://borgbackup.readthedocs.org/en/latest/quickstart.html) for more
|
Start](https://borgbackup.readthedocs.org/en/latest/quickstart.html) for more
|
||||||
background about repository initialization.
|
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
|
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.
|
key-based SSH access to the desired user account on the remote host.
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,17 @@ def initialize_repository(
|
||||||
):
|
):
|
||||||
'''
|
'''
|
||||||
Given a local or remote repository path, a Borg encryption mode, whether the repository should
|
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)
|
(local_path, 'init', repository)
|
||||||
+ (('--encryption', encryption_mode) if encryption_mode else ())
|
+ (('--encryption', encryption_mode) if encryption_mode else ())
|
||||||
+ (('--append-only',) if append_only else ())
|
+ (('--append-only',) if append_only else ())
|
||||||
|
@ -27,5 +35,5 @@ def initialize_repository(
|
||||||
+ (('--remote-path', remote_path) if remote_path else ())
|
+ (('--remote-path', remote_path) if remote_path else ())
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(' '.join(full_command))
|
logger.debug(' '.join(init_command))
|
||||||
subprocess.check_call(full_command)
|
subprocess.check_call(init_command)
|
||||||
|
|
|
@ -149,10 +149,8 @@ def parse_arguments(*arguments):
|
||||||
'The --encryption, --append-only, and --storage-quota options can only be used with the --init option'
|
'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):
|
if args.init and args.dry_run:
|
||||||
raise ValueError(
|
raise ValueError('The --init option cannot be used with the --dry-run option')
|
||||||
'The --init option cannot be used with the --prune, --create, or --dry-run options'
|
|
||||||
)
|
|
||||||
if args.init and not args.encryption_mode:
|
if args.init and not args.encryption_mode:
|
||||||
raise ValueError('The --encryption option is required with the --init option')
|
raise ValueError('The --encryption option is required with the --init option')
|
||||||
|
|
||||||
|
|
|
@ -113,25 +113,25 @@ def test_parse_arguments_disallows_storage_quota_without_init():
|
||||||
module.parse_arguments('--config', 'myconfig', '--storage-quota', '5G')
|
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'])
|
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
module.parse_arguments('--config', 'myconfig', '--init', '--encryption', 'repokey', '--prune')
|
||||||
module.parse_arguments('--config', 'myconfig', '--init', '--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'])
|
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
module.parse_arguments('--config', 'myconfig', '--init', '--encryption', 'repokey', '--create')
|
||||||
module.parse_arguments('--config', 'myconfig', '--init', '--create')
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_arguments_disallows_init_and_dry_run():
|
def test_parse_arguments_disallows_init_and_dry_run():
|
||||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
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():
|
def test_parse_arguments_allows_progress_and_create():
|
||||||
|
|
|
@ -6,53 +6,74 @@ from borgmatic.borg import init as module
|
||||||
from ..test_verbosity import insert_logging_mock
|
from ..test_verbosity import insert_logging_mock
|
||||||
|
|
||||||
|
|
||||||
def insert_subprocess_mock(check_call_command, **kwargs):
|
INFO_REPOSITORY_EXISTS_RESPONSE_CODE = 0
|
||||||
subprocess = flexmock(module.subprocess)
|
INFO_REPOSITORY_NOT_FOUND_RESPONSE_CODE = 2
|
||||||
subprocess.should_receive('check_call').with_args(check_call_command, **kwargs).once()
|
|
||||||
|
|
||||||
|
|
||||||
INIT_COMMAND = ('borg', 'init', 'repo', '--encryption', 'repokey')
|
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():
|
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')
|
module.initialize_repository(repository='repo', encryption_mode='repokey')
|
||||||
|
|
||||||
|
|
||||||
def test_initialize_repository_with_append_only_calls_borg_with_append_only_parameter():
|
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)
|
module.initialize_repository(repository='repo', encryption_mode='repokey', append_only=True)
|
||||||
|
|
||||||
|
|
||||||
def test_initialize_repository_with_storage_quota_calls_borg_with_storage_quota_parameter():
|
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')
|
module.initialize_repository(repository='repo', encryption_mode='repokey', storage_quota='5G')
|
||||||
|
|
||||||
|
|
||||||
def test_initialize_repository_with_log_info_calls_borg_with_info_parameter():
|
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)
|
insert_logging_mock(logging.INFO)
|
||||||
|
|
||||||
module.initialize_repository(repository='repo', encryption_mode='repokey')
|
module.initialize_repository(repository='repo', encryption_mode='repokey')
|
||||||
|
|
||||||
|
|
||||||
def test_initialize_repository_with_log_debug_calls_borg_with_debug_parameter():
|
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)
|
insert_logging_mock(logging.DEBUG)
|
||||||
|
|
||||||
module.initialize_repository(repository='repo', encryption_mode='repokey')
|
module.initialize_repository(repository='repo', encryption_mode='repokey')
|
||||||
|
|
||||||
|
|
||||||
def test_initialize_repository_with_local_path_calls_borg_via_local_path():
|
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')
|
module.initialize_repository(repository='repo', encryption_mode='repokey', local_path='borg1')
|
||||||
|
|
||||||
|
|
||||||
def test_initialize_repository_with_remote_path_calls_borg_with_remote_path_parameter():
|
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')
|
module.initialize_repository(repository='repo', encryption_mode='repokey', remote_path='borg1')
|
||||||
|
|
Loading…
Reference in a new issue