소스 검색

With --init command-line flag, if a repository already exists, proceed without erroring (#117).

Dan Helfman 6 년 전
부모
커밋
30b52e5523
6개의 변경된 파일58개의 추가작업 그리고 25개의 파일을 삭제
  1. 2 0
      NEWS
  2. 4 0
      README.md
  3. 12 4
      borgmatic/borg/init.py
  4. 2 4
      borgmatic/commands/borgmatic.py
  5. 7 7
      tests/integration/commands/test_borgmatic.py
  6. 31 10
      tests/unit/borg/test_init.py

+ 2 - 0
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.

+ 4 - 0
README.md

@@ -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.
 
 

+ 12 - 4
borgmatic/borg/init.py

@@ -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))
-    subprocess.check_call(full_command)
+    logger.debug(' '.join(init_command))
+    subprocess.check_call(init_command)

+ 2 - 4
borgmatic/commands/borgmatic.py

@@ -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):
-        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:
     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')
 
 

+ 7 - 7
tests/integration/commands/test_borgmatic.py

@@ -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', '--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'])
     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():
 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():

+ 31 - 10
tests/unit/borg/test_init.py

@@ -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
+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 = flexmock(module.subprocess)
-    subprocess.should_receive('check_call').with_args(check_call_command, **kwargs).once()
+    subprocess.should_receive('call').and_return(info_response)
 
 
 
 
-INIT_COMMAND = ('borg', 'init', 'repo', '--encryption', 'repokey')
+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')