소스 검색

Fixing up borg module to deal with new parsed config file structures.

Dan Helfman 8 년 전
부모
커밋
8b2b41eefc
3개의 변경된 파일95개의 추가작업 그리고 72개의 파일을 삭제
  1. 38 21
      borgmatic/borg.py
  2. 1 4
      borgmatic/commands/borgmatic.py
  3. 56 47
      borgmatic/tests/unit/test_borg.py

+ 38 - 21
borgmatic/borg.py

@@ -1,10 +1,11 @@
 from datetime import datetime
 from datetime import datetime
+import glob
+import itertools
 import os
 import os
-import re
 import platform
 import platform
+import re
 import subprocess
 import subprocess
-from glob import glob
-from itertools import chain
+import tempfile
 
 
 from borgmatic.verbosity import VERBOSITY_SOME, VERBOSITY_LOTS
 from borgmatic.verbosity import VERBOSITY_SOME, VERBOSITY_LOTS
 
 
@@ -22,18 +23,38 @@ def initialize(storage_config, command=COMMAND):
         os.environ['{}_PASSPHRASE'.format(command.upper())] = passphrase
         os.environ['{}_PASSPHRASE'.format(command.upper())] = passphrase
 
 
 
 
+def _write_exclude_file(exclude_patterns=None):
+    '''
+    Given a sequence of exclude patterns, write them to a named temporary file and return it. Return
+    None if no patterns are provided.
+    '''
+    if not exclude_patterns:
+        return None
+
+    exclude_file = tempfile.NamedTemporaryFile('w')
+    exclude_file.write('\n'.join(exclude_patterns))
+    exclude_file.flush()
+
+    return exclude_file
+
+
 def create_archive(
 def create_archive(
-    excludes_filename, verbosity, storage_config, source_directories, repository, command=COMMAND,
-    one_file_system=None, remote_path=None,
+    verbosity, storage_config, source_directories, repository, exclude_patterns=None,
+    command=COMMAND, one_file_system=None, remote_path=None,
 ):
 ):
     '''
     '''
-    Given an excludes filename (or None), a vebosity flag, a storage config dict, a space-separated
-    list of source directories, a local or remote repository path, and a command to run, create an
-    attic archive.
+    Given a vebosity flag, a storage config dict, a list of source directories, a local or remote
+    repository path, a list of exclude patterns, and a command to run, create an attic archive.
     '''
     '''
-    sources = re.split('\s+', source_directories)
-    sources = tuple(chain.from_iterable(glob(x) or [x] for x in sources))
-    exclude_flags = ('--exclude-from', excludes_filename) if excludes_filename else ()
+    sources = tuple(
+        itertools.chain.from_iterable(
+            glob.glob(directory) or [directory]
+            for directory in source_directories
+        )
+    )
+
+    exclude_file = _write_exclude_file(exclude_patterns)
+    exclude_flags = ('--exclude-from', exclude_file.name) if exclude_file else ()
     compression = storage_config.get('compression', None)
     compression = storage_config.get('compression', None)
     compression_flags = ('--compression', compression) if compression else ()
     compression_flags = ('--compression', compression) if compression else ()
     umask = storage_config.get('umask', None)
     umask = storage_config.get('umask', None)
@@ -109,12 +130,11 @@ DEFAULT_CHECKS = ('repository', 'archives')
 
 
 def _parse_checks(consistency_config):
 def _parse_checks(consistency_config):
     '''
     '''
-    Given a consistency config with a space-separated "checks" option, transform it to a tuple of
-    named checks to run.
+    Given a consistency config with a "checks" list, transform it to a tuple of named checks to run.
 
 
     For example, given a retention config of:
     For example, given a retention config of:
 
 
-        {'checks': 'repository archives'}
+        {'checks': ['repository', 'archives']}
 
 
     This will be returned as:
     This will be returned as:
 
 
@@ -123,14 +143,11 @@ def _parse_checks(consistency_config):
     If no "checks" option is present, return the DEFAULT_CHECKS. If the checks value is the string
     If no "checks" option is present, return the DEFAULT_CHECKS. If the checks value is the string
     "disabled", return an empty tuple, meaning that no checks should be run.
     "disabled", return an empty tuple, meaning that no checks should be run.
     '''
     '''
-    checks = consistency_config.get('checks', '').strip()
-    if not checks:
-        return DEFAULT_CHECKS
+    checks = consistency_config.get('checks', [])
+    if checks == ['disabled']:
+        return ()
 
 
-    return tuple(
-        check for check in consistency_config['checks'].split(' ')
-        if check.lower() not in ('disabled', '')
-    )
+    return tuple(check for check in checks if check.lower() not in ('disabled', '')) or DEFAULT_CHECKS
 
 
 
 
 def _make_check_flags(checks, check_last=None):
 def _make_check_flags(checks, check_last=None):

+ 1 - 4
borgmatic/commands/borgmatic.py

@@ -48,10 +48,7 @@ def main():  # pragma: no cover
         remote_path = config.location['remote_path']
         remote_path = config.location['remote_path']
 
 
         borg.initialize(config.storage)
         borg.initialize(config.storage)
-        # TODO: Use the new exclude_patterns.
-        borg.create_archive(
-            args.excludes_filename, args.verbosity, config.storage, **config.location
-        )
+        borg.create_archive(args.verbosity, config.storage, **config.location)
         borg.prune_archives(args.verbosity, repository, config.retention, remote_path=remote_path)
         borg.prune_archives(args.verbosity, repository, config.retention, remote_path=remote_path)
         borg.check_archives(args.verbosity, repository, config.consistency, remote_path=remote_path)
         borg.check_archives(args.verbosity, repository, config.consistency, remote_path=remote_path)
     except (ValueError, OSError, CalledProcessError) as error:
     except (ValueError, OSError, CalledProcessError) as error:

+ 56 - 47
borgmatic/tests/unit/test_borg.py

@@ -30,6 +30,20 @@ def test_initialize_without_passphrase_should_not_set_environment():
     finally:
     finally:
         os.environ = orig_environ
         os.environ = orig_environ
 
 
+def test_write_exclude_file_does_not_raise():
+    temporary_file = flexmock(
+        name='filename',
+        write=lambda mode: None,
+        flush=lambda: None,
+    )
+    flexmock(module.tempfile).should_receive('NamedTemporaryFile').and_return(temporary_file)
+
+    module._write_exclude_file(['exclude'])
+
+
+def test_write_exclude_file_with_empty_exclude_patterns_does_not_raise():
+    module._write_exclude_file([])
+
 
 
 def insert_subprocess_mock(check_call_command, **kwargs):
 def insert_subprocess_mock(check_call_command, **kwargs):
     subprocess = flexmock(STDOUT=STDOUT)
     subprocess = flexmock(STDOUT=STDOUT)
@@ -53,110 +67,100 @@ def insert_datetime_mock():
     ).mock
     ).mock
 
 
 
 
-CREATE_COMMAND_WITHOUT_EXCLUDES = ('borg', 'create', 'repo::host-now', 'foo', 'bar')
-CREATE_COMMAND = CREATE_COMMAND_WITHOUT_EXCLUDES + ('--exclude-from', 'excludes')
+CREATE_COMMAND = ('borg', 'create', 'repo::host-now', 'foo', 'bar')
 
 
 
 
 def test_create_archive_should_call_borg_with_parameters():
 def test_create_archive_should_call_borg_with_parameters():
+    flexmock(module).should_receive('_write_exclude_file')
     insert_subprocess_mock(CREATE_COMMAND)
     insert_subprocess_mock(CREATE_COMMAND)
     insert_platform_mock()
     insert_platform_mock()
     insert_datetime_mock()
     insert_datetime_mock()
 
 
     module.create_archive(
     module.create_archive(
-        excludes_filename='excludes',
-        verbosity=None,
-        storage_config={},
-        source_directories='foo bar',
-        repository='repo',
-        command='borg',
-    )
-
-
-def test_create_archive_with_two_spaces_in_source_directories():
-    insert_subprocess_mock(CREATE_COMMAND)
-    insert_platform_mock()
-    insert_datetime_mock()
-
-    module.create_archive(
-        excludes_filename='excludes',
+        exclude_patterns=None,
         verbosity=None,
         verbosity=None,
         storage_config={},
         storage_config={},
-        source_directories='foo  bar',
+        source_directories=['foo', 'bar'],
         repository='repo',
         repository='repo',
         command='borg',
         command='borg',
     )
     )
 
 
 
 
-def test_create_archive_with_none_excludes_filename_should_call_borg_without_excludes():
-    insert_subprocess_mock(CREATE_COMMAND_WITHOUT_EXCLUDES)
+def test_create_archive_with_exclude_patterns_should_call_borg_with_excludes():
+    flexmock(module).should_receive('_write_exclude_file').and_return(flexmock(name='excludes'))
+    insert_subprocess_mock(CREATE_COMMAND + ('--exclude-from', 'excludes'))
     insert_platform_mock()
     insert_platform_mock()
     insert_datetime_mock()
     insert_datetime_mock()
 
 
     module.create_archive(
     module.create_archive(
-        excludes_filename=None,
+        exclude_patterns=['exclude'],
         verbosity=None,
         verbosity=None,
         storage_config={},
         storage_config={},
-        source_directories='foo bar',
+        source_directories=['foo', 'bar'],
         repository='repo',
         repository='repo',
         command='borg',
         command='borg',
     )
     )
 
 
 
 
 def test_create_archive_with_verbosity_some_should_call_borg_with_info_parameter():
 def test_create_archive_with_verbosity_some_should_call_borg_with_info_parameter():
+    flexmock(module).should_receive('_write_exclude_file')
     insert_subprocess_mock(CREATE_COMMAND + ('--info', '--stats',))
     insert_subprocess_mock(CREATE_COMMAND + ('--info', '--stats',))
     insert_platform_mock()
     insert_platform_mock()
     insert_datetime_mock()
     insert_datetime_mock()
 
 
     module.create_archive(
     module.create_archive(
-        excludes_filename='excludes',
+        exclude_patterns=None,
         verbosity=VERBOSITY_SOME,
         verbosity=VERBOSITY_SOME,
         storage_config={},
         storage_config={},
-        source_directories='foo bar',
+        source_directories=['foo', 'bar'],
         repository='repo',
         repository='repo',
         command='borg',
         command='borg',
     )
     )
 
 
 
 
 def test_create_archive_with_verbosity_lots_should_call_borg_with_debug_parameter():
 def test_create_archive_with_verbosity_lots_should_call_borg_with_debug_parameter():
+    flexmock(module).should_receive('_write_exclude_file')
     insert_subprocess_mock(CREATE_COMMAND + ('--debug', '--list', '--stats'))
     insert_subprocess_mock(CREATE_COMMAND + ('--debug', '--list', '--stats'))
     insert_platform_mock()
     insert_platform_mock()
     insert_datetime_mock()
     insert_datetime_mock()
 
 
     module.create_archive(
     module.create_archive(
-        excludes_filename='excludes',
+        exclude_patterns=None,
         verbosity=VERBOSITY_LOTS,
         verbosity=VERBOSITY_LOTS,
         storage_config={},
         storage_config={},
-        source_directories='foo bar',
+        source_directories=['foo', 'bar'],
         repository='repo',
         repository='repo',
         command='borg',
         command='borg',
     )
     )
 
 
 
 
 def test_create_archive_with_compression_should_call_borg_with_compression_parameters():
 def test_create_archive_with_compression_should_call_borg_with_compression_parameters():
+    flexmock(module).should_receive('_write_exclude_file')
     insert_subprocess_mock(CREATE_COMMAND + ('--compression', 'rle'))
     insert_subprocess_mock(CREATE_COMMAND + ('--compression', 'rle'))
     insert_platform_mock()
     insert_platform_mock()
     insert_datetime_mock()
     insert_datetime_mock()
 
 
     module.create_archive(
     module.create_archive(
-        excludes_filename='excludes',
+        exclude_patterns=None,
         verbosity=None,
         verbosity=None,
         storage_config={'compression': 'rle'},
         storage_config={'compression': 'rle'},
-        source_directories='foo bar',
+        source_directories=['foo', 'bar'],
         repository='repo',
         repository='repo',
         command='borg',
         command='borg',
     )
     )
 
 
 
 
 def test_create_archive_with_one_file_system_should_call_borg_with_one_file_system_parameters():
 def test_create_archive_with_one_file_system_should_call_borg_with_one_file_system_parameters():
+    flexmock(module).should_receive('_write_exclude_file')
     insert_subprocess_mock(CREATE_COMMAND + ('--one-file-system',))
     insert_subprocess_mock(CREATE_COMMAND + ('--one-file-system',))
     insert_platform_mock()
     insert_platform_mock()
     insert_datetime_mock()
     insert_datetime_mock()
 
 
     module.create_archive(
     module.create_archive(
-        excludes_filename='excludes',
+        exclude_patterns=None,
         verbosity=None,
         verbosity=None,
         storage_config={},
         storage_config={},
-        source_directories='foo bar',
+        source_directories=['foo', 'bar'],
         repository='repo',
         repository='repo',
         command='borg',
         command='borg',
         one_file_system=True,
         one_file_system=True,
@@ -164,15 +168,16 @@ def test_create_archive_with_one_file_system_should_call_borg_with_one_file_syst
 
 
 
 
 def test_create_archive_with_remote_path_should_call_borg_with_remote_path_parameters():
 def test_create_archive_with_remote_path_should_call_borg_with_remote_path_parameters():
+    flexmock(module).should_receive('_write_exclude_file')
     insert_subprocess_mock(CREATE_COMMAND + ('--remote-path', 'borg1'))
     insert_subprocess_mock(CREATE_COMMAND + ('--remote-path', 'borg1'))
     insert_platform_mock()
     insert_platform_mock()
     insert_datetime_mock()
     insert_datetime_mock()
 
 
     module.create_archive(
     module.create_archive(
-        excludes_filename='excludes',
+        exclude_patterns=None,
         verbosity=None,
         verbosity=None,
         storage_config={},
         storage_config={},
-        source_directories='foo bar',
+        source_directories=['foo', 'bar'],
         repository='repo',
         repository='repo',
         command='borg',
         command='borg',
         remote_path='borg1',
         remote_path='borg1',
@@ -180,63 +185,67 @@ def test_create_archive_with_remote_path_should_call_borg_with_remote_path_param
 
 
 
 
 def test_create_archive_with_umask_should_call_borg_with_umask_parameters():
 def test_create_archive_with_umask_should_call_borg_with_umask_parameters():
+    flexmock(module).should_receive('_write_exclude_file')
     insert_subprocess_mock(CREATE_COMMAND + ('--umask', '740'))
     insert_subprocess_mock(CREATE_COMMAND + ('--umask', '740'))
     insert_platform_mock()
     insert_platform_mock()
     insert_datetime_mock()
     insert_datetime_mock()
 
 
     module.create_archive(
     module.create_archive(
-        excludes_filename='excludes',
+        exclude_patterns=None,
         verbosity=None,
         verbosity=None,
         storage_config={'umask': 740},
         storage_config={'umask': 740},
-        source_directories='foo bar',
+        source_directories=['foo', 'bar'],
         repository='repo',
         repository='repo',
         command='borg',
         command='borg',
     )
     )
 
 
 
 
 def test_create_archive_with_source_directories_glob_expands():
 def test_create_archive_with_source_directories_glob_expands():
+    flexmock(module).should_receive('_write_exclude_file')
     insert_subprocess_mock(('borg', 'create', 'repo::host-now', 'foo', 'food'))
     insert_subprocess_mock(('borg', 'create', 'repo::host-now', 'foo', 'food'))
     insert_platform_mock()
     insert_platform_mock()
     insert_datetime_mock()
     insert_datetime_mock()
-    flexmock(module).should_receive('glob').with_args('foo*').and_return(['foo', 'food'])
+    flexmock(module.glob).should_receive('glob').with_args('foo*').and_return(['foo', 'food'])
 
 
     module.create_archive(
     module.create_archive(
-        excludes_filename=None,
+        exclude_patterns=None,
         verbosity=None,
         verbosity=None,
         storage_config={},
         storage_config={},
-        source_directories='foo*',
+        source_directories=['foo*'],
         repository='repo',
         repository='repo',
         command='borg',
         command='borg',
     )
     )
 
 
 
 
 def test_create_archive_with_non_matching_source_directories_glob_passes_through():
 def test_create_archive_with_non_matching_source_directories_glob_passes_through():
+    flexmock(module).should_receive('_write_exclude_file')
     insert_subprocess_mock(('borg', 'create', 'repo::host-now', 'foo*'))
     insert_subprocess_mock(('borg', 'create', 'repo::host-now', 'foo*'))
     insert_platform_mock()
     insert_platform_mock()
     insert_datetime_mock()
     insert_datetime_mock()
-    flexmock(module).should_receive('glob').with_args('foo*').and_return([])
+    flexmock(module.glob).should_receive('glob').with_args('foo*').and_return([])
 
 
     module.create_archive(
     module.create_archive(
-        excludes_filename=None,
+        exclude_patterns=None,
         verbosity=None,
         verbosity=None,
         storage_config={},
         storage_config={},
-        source_directories='foo*',
+        source_directories=['foo*'],
         repository='repo',
         repository='repo',
         command='borg',
         command='borg',
     )
     )
 
 
 
 
 def test_create_archive_with_glob_should_call_borg_with_expanded_directories():
 def test_create_archive_with_glob_should_call_borg_with_expanded_directories():
+    flexmock(module).should_receive('_write_exclude_file')
     insert_subprocess_mock(('borg', 'create', 'repo::host-now', 'foo', 'food'))
     insert_subprocess_mock(('borg', 'create', 'repo::host-now', 'foo', 'food'))
     insert_platform_mock()
     insert_platform_mock()
     insert_datetime_mock()
     insert_datetime_mock()
-    flexmock(module).should_receive('glob').with_args('foo*').and_return(['foo', 'food'])
+    flexmock(module.glob).should_receive('glob').with_args('foo*').and_return(['foo', 'food'])
 
 
     module.create_archive(
     module.create_archive(
-        excludes_filename=None,
+        exclude_patterns=None,
         verbosity=None,
         verbosity=None,
         storage_config={},
         storage_config={},
-        source_directories='foo*',
+        source_directories=['foo*'],
         repository='repo',
         repository='repo',
         command='borg',
         command='borg',
     )
     )
@@ -329,7 +338,7 @@ def test_prune_archive_with_remote_path_should_call_borg_with_remote_path_parame
 
 
 
 
 def test_parse_checks_returns_them_as_tuple():
 def test_parse_checks_returns_them_as_tuple():
-    checks = module._parse_checks({'checks': 'foo disabled bar'})
+    checks = module._parse_checks({'checks': ['foo', 'disabled', 'bar']})
 
 
     assert checks == ('foo', 'bar')
     assert checks == ('foo', 'bar')
 
 
@@ -341,13 +350,13 @@ def test_parse_checks_with_missing_value_returns_defaults():
 
 
 
 
 def test_parse_checks_with_blank_value_returns_defaults():
 def test_parse_checks_with_blank_value_returns_defaults():
-    checks = module._parse_checks({'checks': ''})
+    checks = module._parse_checks({'checks': []})
 
 
     assert checks == module.DEFAULT_CHECKS
     assert checks == module.DEFAULT_CHECKS
 
 
 
 
 def test_parse_checks_with_disabled_returns_no_checks():
 def test_parse_checks_with_disabled_returns_no_checks():
-    checks = module._parse_checks({'checks': 'disabled'})
+    checks = module._parse_checks({'checks': ['disabled']})
 
 
     assert checks == ()
     assert checks == ()