Sfoglia il codice sorgente

#9: New configuration option for the encryption passphrase. #10: Support for Borg's new archive compression feature.

Dan Helfman 9 anni fa
parent
commit
a44212ff00

+ 5 - 0
NEWS

@@ -1,3 +1,8 @@
+0.1.6
+
+ * #9: New configuration option for the encryption passphrase.
+ * #10: Support for Borg's new archive compression feature.
+
 0.1.5
 
  * Changes to support release on PyPI. Now pip installable by name!

+ 3 - 3
README.md

@@ -45,9 +45,9 @@ Start](https://attic-backup.org/quickstart.html) or the [Borg Quick
 Start](https://borgbackup.github.io/borgbackup/quickstart.html) to create a
 repository on a local or remote host. Note that if you plan to run atticmatic
 on a schedule with cron, and you encrypt your attic repository with a
-passphrase instead of a key file, you'll need to set the `ATTIC_PASSPHRASE`
-environment variable. See the repository encryption section of the Quick Start
-for more info.
+passphrase instead of a key file, you'll need to set the atticmatic
+`encryption_passphrase` configuration variable. See the repository encryption
+section of the Quick Start for more info.
 
 If the repository is on a remote host, make sure that your local root user has
 key-based ssh access to the desired user account on the remote host.

+ 2 - 0
atticmatic/backends/attic.py

@@ -7,6 +7,8 @@ from atticmatic.backends import shared
 COMMAND = 'attic'
 CONFIG_FORMAT = shared.CONFIG_FORMAT
 
+
+initialize = partial(shared.initialize, command=COMMAND)
 create_archive = partial(shared.create_archive, command=COMMAND)
 prune_archives = partial(shared.prune_archives, command=COMMAND)
 check_archives = partial(shared.check_archives, command=COMMAND)

+ 9 - 1
atticmatic/backends/borg.py

@@ -8,7 +8,14 @@ from atticmatic.backends import shared
 COMMAND = 'borg'
 CONFIG_FORMAT = (
     shared.CONFIG_FORMAT[0],  # location
-    shared.CONFIG_FORMAT[1],  # retention
+    Section_format(
+        'storage',
+        (
+            option('encryption_passphrase', required=False),
+            option('compression', required=False),
+        ),
+    ),
+    shared.CONFIG_FORMAT[2],  # retention
     Section_format(
         'consistency',
         (
@@ -19,6 +26,7 @@ CONFIG_FORMAT = (
 )
 
 
+initialize = partial(shared.initialize, command=COMMAND)
 create_archive = partial(shared.create_archive, command=COMMAND)
 prune_archives = partial(shared.prune_archives, command=COMMAND)
 check_archives = partial(shared.check_archives, command=COMMAND)

+ 23 - 4
atticmatic/backends/shared.py

@@ -21,6 +21,12 @@ CONFIG_FORMAT = (
             option('repository'),
         ),
     ),
+    Section_format(
+        'storage',
+        (
+            option('encryption_passphrase', required=False),
+        ),
+    ),
     Section_format(
         'retention',
         (
@@ -41,13 +47,26 @@ CONFIG_FORMAT = (
     )
 )
 
-def create_archive(excludes_filename, verbosity, source_directories, repository, command):
+
+def initialize(storage_config, command):
+    passphrase = storage_config.get('encryption_passphrase')
+
+    if passphrase:
+        os.environ['{}_PASSPHRASE'.format(command.upper())] = passphrase
+
+
+def create_archive(
+    excludes_filename, verbosity, storage_config, source_directories, repository, command,
+):
     '''
-    Given an excludes filename (or None), a vebosity flag, a space-separated list of source
-    directories, a local or remote repository path, and a command to run, create an attic archive.
+    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.
     '''
     sources = tuple(source_directories.split(' '))
     exclude_flags = ('--exclude-from', excludes_filename) if excludes_filename else ()
+    compression = storage_config.get('compression', None)
+    compression_flags = ('--compression', compression) if compression else ()
     verbosity_flags = {
         VERBOSITY_SOME: ('--stats',),
         VERBOSITY_LOTS: ('--verbose', '--stats'),
@@ -60,7 +79,7 @@ def create_archive(excludes_filename, verbosity, source_directories, repository,
             hostname=platform.node(),
             timestamp=datetime.now().isoformat(),
         ),
-    ) + sources + exclude_flags + verbosity_flags
+    ) + sources + exclude_flags + compression_flags + verbosity_flags
 
     subprocess.check_call(full_command)
 

+ 4 - 1
atticmatic/command.py

@@ -64,7 +64,10 @@ def main():
         config = parse_configuration(args.config_filename, backend.CONFIG_FORMAT)
         repository = config.location['repository']
 
-        backend.create_archive(args.excludes_filename, args.verbosity, **config.location)
+        backend.initialize(config.storage)
+        backend.create_archive(
+            args.excludes_filename, args.verbosity, config.storage, **config.location
+        )
         backend.prune_archives(args.verbosity, repository, config.retention)
         backend.check_archives(args.verbosity, repository, config.consistency)
     except (ValueError, IOError, CalledProcessError) as error:

+ 42 - 0
atticmatic/tests/unit/backends/test_shared.py

@@ -1,4 +1,5 @@
 from collections import OrderedDict
+import os
 
 from flexmock import flexmock
 
@@ -7,6 +8,28 @@ from atticmatic.tests.builtins import builtins_mock
 from atticmatic.verbosity import VERBOSITY_SOME, VERBOSITY_LOTS
 
 
+def test_initialize_with_passphrase_should_set_environment():
+    orig_environ = os.environ
+
+    try:
+        os.environ = {}
+        module.initialize({'encryption_passphrase': 'pass'}, command='attic')
+        assert os.environ.get('ATTIC_PASSPHRASE') == 'pass'
+    finally:
+        os.environ = orig_environ
+
+
+def test_initialize_without_passphrase_should_not_set_environment():
+    orig_environ = os.environ
+
+    try:
+        os.environ = {}
+        module.initialize({}, command='attic')
+        assert os.environ.get('ATTIC_PASSPHRASE') == None
+    finally:
+        os.environ = orig_environ
+
+
 def insert_subprocess_mock(check_call_command, **kwargs):
     subprocess = flexmock()
     subprocess.should_receive('check_call').with_args(check_call_command, **kwargs).once()
@@ -41,6 +64,7 @@ def test_create_archive_should_call_attic_with_parameters():
     module.create_archive(
         excludes_filename='excludes',
         verbosity=None,
+        storage_config={},
         source_directories='foo bar',
         repository='repo',
         command='attic',
@@ -55,6 +79,7 @@ def test_create_archive_with_none_excludes_filename_should_call_attic_without_ex
     module.create_archive(
         excludes_filename=None,
         verbosity=None,
+        storage_config={},
         source_directories='foo bar',
         repository='repo',
         command='attic',
@@ -69,6 +94,7 @@ def test_create_archive_with_verbosity_some_should_call_attic_with_stats_paramet
     module.create_archive(
         excludes_filename='excludes',
         verbosity=VERBOSITY_SOME,
+        storage_config={},
         source_directories='foo bar',
         repository='repo',
         command='attic',
@@ -83,6 +109,22 @@ def test_create_archive_with_verbosity_lots_should_call_attic_with_verbose_param
     module.create_archive(
         excludes_filename='excludes',
         verbosity=VERBOSITY_LOTS,
+        storage_config={},
+        source_directories='foo bar',
+        repository='repo',
+        command='attic',
+    )
+
+
+def test_create_archive_with_compression_should_call_attic_with_compression_parameters():
+    insert_subprocess_mock(CREATE_COMMAND + ('--compression', 'rle'))
+    insert_platform_mock()
+    insert_datetime_mock()
+
+    module.create_archive(
+        excludes_filename='excludes',
+        verbosity=None,
+        storage_config={'compression': 'rle'},
         source_directories='foo bar',
         repository='repo',
         command='attic',

+ 9 - 0
sample/config

@@ -5,6 +5,15 @@ source_directories: /home /etc
 # Path to local or remote repository.
 repository: user@backupserver:sourcehostname.attic
 
+[storage]
+# Passphrase to unlock the encryption key with. Only use on repositories that
+# were initialized with passphrase/repokey encryption.
+#encryption_passphrase: foo
+# For Borg only, you can specify the type of compression to use when creating
+# archives. See https://borgbackup.github.io/borgbackup/usage.html#borg-create
+# for details. Defaults to no compression.
+#compression: lz4
+
 [retention]
 # Retention policy for how many backups to keep in each category. See
 # https://attic-backup.org/usage.html#attic-prune or

+ 1 - 1
setup.py

@@ -1,7 +1,7 @@
 from setuptools import setup, find_packages
 
 
-VERSION = '0.1.5'
+VERSION = '0.1.6'
 
 
 setup(