Compare commits

...
42 Commits
Author SHA1 Message Date
Dan Helfman 9e45da75cb Cutting a release. 2016-04-10 16:01:05 -07:00
Dan Helfman 0ea5824427 Switching from the no-longer-maintained nose test runner to pytest. 2016-04-10 15:59:36 -07:00
Dan Helfman 5b66dc69a1 Refreshing flexmock version in test requirements. 2016-04-10 15:48:10 -07:00
Dan Helfman 8210172d7f Fixing "check" backend tests to support new use of stderr=STDOUT. 2016-04-10 15:46:43 -07:00
Dan Helfman 82e8dae948 At verbosity zero, suppressing Borg check spew to stderr about "Checking segments". 2016-04-10 15:29:42 -07:00
Dan Helfman fa87aed263 Normalizing recent changes. No new content. 2016-04-10 15:27:21 -07:00
Dan Helfman c3b4cb21ed Fixed links to Borg documentation. 2016-04-10 10:23:32 -07:00
Dan Helfman 030b321e39 Merge pull request #11 from jangondol/patch-1
Fix broken link to Borg quickstart
2016-04-10 10:21:23 -07:00
Jan Gondol 15bf273e6e Fix broken link to Borg quickstart 2016-04-06 14:54:06 +02:00
Dan Helfman cf545ae93a Mocking out glob() in test so it doesn't hit the filesystem, and simplifying comprehension. 2016-02-13 16:41:17 -08:00
Dan Helfman 45a2b9cded Merge pull request #5 from ypid/support-file-globs
Added support for file globs in source_directories.
2016-02-13 16:32:05 -08:00
Robin Schneider 953d08ba63 Made globing for source_directories the default.
Don’t remove non existing files/directories from the list and let
attic/borg handle this.
2016-02-13 21:10:05 +01:00
Robin Schneider 88da0c3039 Added support for file globs in source_directories.
source_directories_glob can be used to enable glob support (defaults to
disabled).
2016-02-13 21:07:07 +01:00
Dan Helfman 0012e0cdea Support borg create --umask. (Merge PR from ypid.) 2016-02-13 10:59:43 -08:00
Dan Helfman 049f9c8853 Added support for --one-file-system for Borg. 2016-02-13 10:43:31 -08:00
Dan Helfman 31482ee559 Merge pull request #8 from ypid/fixed-source-split-bug
Use /\s+/ to split source_directories to handle 1+ spaces.
2016-02-13 10:29:31 -08:00
Dan Helfman 1ffff3255a Merge pull request #6 from ypid/added-gitignore
Added .gitignore file.
2016-02-13 10:27:55 -08:00
Robin Schneider 9e52be6ffd Use /\s+/ to split source_directories to handle 1+ spaces.
This bug is can be quite annoying because when you accidentally used
something like:

```ini
[location]
source_directories: backup_one  backup_two
;                              A (Additional space here)
```

It would call Attic/Borg with ('backup_one', '', 'backup_two') which in
turn backups your whole $PWD.
2016-02-07 23:30:54 +01:00
Robin Schneider 978096b402 Added .gitignore file. 2016-02-07 22:14:57 +01:00
Dan Helfman cc6aa7af05 Merge pull request #3 from ypid/README-ini-hightlighting
Also allow the INI example to be highlighted on GitHub.
2016-01-24 20:52:01 -08:00
Robin Schneider 32858fb0b4 Also allow the INI example to be highlighted on GitHub. 2016-01-20 13:11:15 +01:00
Dan Helfman e59845d4e1 Added tag github/master for changeset 28434dd0440c 2015-11-08 17:06:48 -08:00
Dan Helfman 9437e95849 Merge. 2015-11-08 17:04:49 -08:00
Dan Helfman 3a3851d2a5 Removed tag github/yaml_config_files 2015-11-08 17:04:14 -08:00
Dan Helfman 80318e6e30 Removed tag github/yaml_config_files 2015-11-08 17:03:40 -08:00
Dan Helfman 6756ca8311 Merge pull request #2 from ThomasWaldmann/patch-1
fixed typo in README
2015-10-20 18:33:16 -07:00
TW fa7955b8cf fixed typo in README 2015-10-20 23:08:43 +02:00
Dan Helfman 944c0212c3 Added tag 0.1.7 for changeset 5a458ebef804 2015-09-06 16:40:46 -07:00
Dan Helfman 2456fc67f1 Revving version. 2015-09-06 16:40:39 -07:00
Dan Helfman 8a58b72934 Better error message when configuration file is missing. 2015-09-06 15:55:14 -07:00
Dan Helfman 6dc0173b74 #11: Fixed parsing of punctuation in configuration file. 2015-09-06 15:33:56 -07:00
Dan Helfman 5c58f85be1 Added tag 0.1.6 for changeset 4c63f3d90ec2 2015-09-02 22:48:14 -07:00
Dan Helfman 3a9e32a411 #9: New configuration option for the encryption passphrase. #10: Support for Borg's new archive compression feature. 2015-09-02 22:48:07 -07:00
Dan Helfman 30f6ec4f7d Adding documentation note about logging into the issue tracker in order to create issues. 2015-09-02 18:45:15 -07:00
Dan Helfman c67ab09e4d Adding build to hgignore. 2015-08-09 11:04:57 -07:00
Dan Helfman 5299046b6b Added tag 0.1.5 for changeset 0afff209b902 2015-08-09 10:59:40 -07:00
Dan Helfman 204e515bf7 Changes to support release on PyPI. Now pip installable by name! 2015-08-09 10:59:27 -07:00
Dan Helfman 1334da99e2 Added tag 0.1.4 for changeset e58246fc92bb 2015-07-30 08:13:32 -07:00
Dan Helfman 996ca19dac Adding version test. 2015-07-30 08:13:27 -07:00
Dan Helfman 61969d17a2 Added tag 0.1.4 for changeset 6dda59c12de8 2015-07-30 08:12:36 -07:00
Dan Helfman d041e23d35 Adding test that setup.py version matches release version. 2015-07-30 08:12:31 -07:00
Dan Helfman e996e09657 Added tag 0.1.3 for changeset acc7fb61566f 2015-07-27 21:48:21 -07:00
21 changed files with 379 additions and 61 deletions
+6
View File
@@ -0,0 +1,6 @@
*.egg-info
*.pyc
*.swp
.tox
build
dist
+2
View File
@@ -3,3 +3,5 @@ syntax: glob
*.pyc
*.swp
.tox
build
dist
+10
View File
@@ -15,3 +15,13 @@ ac5dfa01e9d14d09845f5e94c2c679e21c5eb2f9 0.1.1
ac5dfa01e9d14d09845f5e94c2c679e21c5eb2f9 0.1.1
7b6c87dca7ea312b2257ac1b46857b3f8c56b39c 0.1.1
83067f995dd391e38544a7722dc3b254b59c5521 0.1.2
acc7fb61566fe8028c179f43ecc735c851220b06 0.1.3
6dda59c12de88f060eb7244e6d330173985a9639 0.1.4
6dda59c12de88f060eb7244e6d330173985a9639 0.1.4
e58246fc92bb22c2b2fd8b86a1227de69d2d0315 0.1.4
0afff209b902698c2266986129d6dc9f5f913101 0.1.5
4c63f3d90ec2bf6af1714a3acec84654a7c9edf3 0.1.6
5a458ebef804be14e30d7375e3e9fbc26aedb80d 0.1.7
977f19c2f6a515be6c5ef69cf17b0e0989532209 github/yaml_config_files
0000000000000000000000000000000000000000 github/yaml_config_files
28434dd0440cc8da44c2f3e9bd7e9402a59c3b40 github/master
+1
View File
@@ -2,3 +2,4 @@ Dan Helfman <witten@torsion.org>: Main developer
Alexander Görtz: Python 3 compatibility
Henning Schroeder: Copy editing
Robin `ypid` Schneider: Support additional options of Borg
+27
View File
@@ -1,3 +1,30 @@
0.1.8
* Fix for handling of spaces in source_directories which resulted in backup up everything.
* Fix for broken links to Borg documentation.
* At verbosity zero, suppressing Borg check stderr spew about "Checking segments".
* Support for Borg --one-file-system.
* Support for Borg create --umask.
* Support for file globs in source_directories.
0.1.7
* #11: Fixed parsing of punctuation in configuration file.
* Better error message when configuration file is missing.
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!
0.1.4
* Adding test that setup.py version matches release version.
0.1.3
* #1: Add support for "borg check --last N" to Borg backend.
+26 -21
View File
@@ -4,7 +4,7 @@ title: Atticmatic
atticmatic is a simple Python wrapper script for the
[Attic](https://attic-backup.org/) and
[Borg](https://borgbackup.github.io/borgbackup/) backup software that
[Borg](https://borgbackup.readthedocs.org/en/stable/) backup software that
initiates a backup, prunes any old backups according to a retention policy,
and validates backups for consistency. The script supports specifying your
settings in a declarative configuration file rather than having to put them
@@ -12,22 +12,25 @@ all on the command-line, and handles common errors.
Here's an example config file:
[location]
# Space-separated list of source directories to backup.
source_directories: /home /etc
```INI
[location]
# Space-separated list of source directories to backup.
# Globs are expanded.
source_directories: /home /etc /var/log/syslog*
# Path to local or remote backup repository.
repository: user@backupserver:sourcehostname.attic
# Path to local or remote backup repository.
repository: user@backupserver:sourcehostname.attic
[retention]
# Retention policy for how many backups to keep in each category.
keep_daily: 7
keep_weekly: 4
keep_monthly: 6
[retention]
# Retention policy for how many backups to keep in each category.
keep_daily: 7
keep_weekly: 4
keep_monthly: 6
[consistency]
# Consistency checks to run, or "disabled" to prevent checks.
checks: repository archives
[consistency]
# Consistency checks to run, or "disabled" to prevent checks.
checks: repository archives
```
Additionally, exclude patterns can be specified in a separate excludes config
file, one pattern per line.
@@ -42,19 +45,19 @@ available](https://torsion.org/hg/atticmatic). It's also mirrored on
To get up and running, follow the [Attic Quick
Start](https://attic-backup.org/quickstart.html) or the [Borg Quick
Start](https://borgbackup.github.io/borgbackup/quickstart.html) to create a
Start](https://borgbackup.readthedocs.org/en/latest/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.
To install atticmatic, run the following command to download and install it:
sudo pip install --upgrade hg+https://torsion.org/hg/atticmatic
sudo pip install --upgrade atticmatic
If you are using Attic, copy the following configuration files:
@@ -117,7 +120,7 @@ Then, to actually run tests, run:
### Broken pipe with remote repository
When running atticmatic on a large remote repository, you may receive errors
like the following, particularly while "attic check" is valiating backups for
like the following, particularly while "attic check" is validating backups for
consistency:
Write failed: Broken pipe
@@ -136,6 +139,8 @@ backups.
## Issues and feedback
Got an issue or an idea for a feature enhancement? Check out the [atticmatic
issue tracker](https://tree.taiga.io/project/witten-atticmatic/issues).
issue tracker](https://tree.taiga.io/project/witten-atticmatic/issues). In
order to create a new issue or comment on an issue, you'll need to [login
first](https://tree.taiga.io/login).
Other questions or comments? Contact <mailto:witten@torsion.org>.
+2
View File
@@ -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)
+19 -2
View File
@@ -7,8 +7,23 @@ from atticmatic.backends import shared
COMMAND = 'borg'
CONFIG_FORMAT = (
shared.CONFIG_FORMAT[0], # location
shared.CONFIG_FORMAT[1], # retention
Section_format(
'location',
(
option('source_directories'),
option('one_file_system', value_type=bool, required=False),
option('repository'),
),
),
Section_format(
'storage',
(
option('encryption_passphrase', required=False),
option('compression', required=False),
option('umask', required=False),
),
),
shared.CONFIG_FORMAT[2], # retention
Section_format(
'consistency',
(
@@ -19,6 +34,8 @@ 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)
+36 -8
View File
@@ -1,7 +1,10 @@
from datetime import datetime
import os
import re
import platform
import subprocess
from glob import glob
from itertools import chain
from atticmatic.config import Section_format, option
from atticmatic.verbosity import VERBOSITY_SOME, VERBOSITY_LOTS
@@ -21,6 +24,12 @@ CONFIG_FORMAT = (
option('repository'),
),
),
Section_format(
'storage',
(
option('encryption_passphrase', required=False),
),
),
Section_format(
'retention',
(
@@ -41,13 +50,31 @@ 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,
one_file_system=None
):
'''
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(' '))
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 ()
compression = storage_config.get('compression', None)
compression_flags = ('--compression', compression) if compression else ()
umask = storage_config.get('umask', None)
umask_flags = ('--umask', str(umask)) if umask else ()
one_file_system_flags = ('--one-file-system',) if one_file_system else ()
verbosity_flags = {
VERBOSITY_SOME: ('--stats',),
VERBOSITY_LOTS: ('--verbose', '--stats'),
@@ -60,7 +87,8 @@ 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 + one_file_system_flags + \
umask_flags + verbosity_flags
subprocess.check_call(full_command)
@@ -148,7 +176,7 @@ def _make_check_flags(checks, check_last=None):
('repository',)
This will be returned as:
('--repository-only',)
Additionally, if a check_last value is given, a "--last" flag will be added. Note that only
@@ -185,7 +213,7 @@ def check_archives(verbosity, repository, consistency_config, command):
repository,
) + _make_check_flags(checks, check_last) + verbosity_flags
# The check command spews to stdout even without the verbose flag. Suppress it.
# The check command spews to stdout/stderr even without the verbose flag. Suppress it.
stdout = None if verbosity_flags else open(os.devnull, 'w')
subprocess.check_call(full_command, stdout=stdout)
subprocess.check_call(full_command, stdout=stdout, stderr=subprocess.STDOUT)
+4 -1
View File
@@ -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:
+8 -6
View File
@@ -2,10 +2,10 @@ from collections import OrderedDict, namedtuple
try:
# Python 2
from ConfigParser import ConfigParser
from ConfigParser import RawConfigParser
except ImportError:
# Python 3
from configparser import ConfigParser
from configparser import RawConfigParser
Section_format = namedtuple('Section_format', ('name', 'options'))
@@ -22,7 +22,7 @@ def option(name, value_type=str, required=True):
def validate_configuration_format(parser, config_format):
'''
Given an open ConfigParser and an expected config file format, validate that the parsed
Given an open RawConfigParser and an expected config file format, validate that the parsed
configuration file has the expected sections, that any required options are present in those
sections, and that there aren't any unexpected options.
@@ -83,7 +83,7 @@ def validate_configuration_format(parser, config_format):
def parse_section_options(parser, section_format):
'''
Given an open ConfigParser and an expected section format, return the option values from that
Given an open RawConfigParser and an expected section format, return the option values from that
section as a dict mapping from option name to value. Omit those options that are not present in
the parsed options.
@@ -92,6 +92,7 @@ def parse_section_options(parser, section_format):
type_getter = {
str: parser.get,
int: parser.getint,
bool: parser.getboolean,
}
return OrderedDict(
@@ -108,8 +109,9 @@ def parse_configuration(config_filename, config_format):
Raise IOError if the file cannot be read, or ValueError if the format is not as expected.
'''
parser = ConfigParser()
parser.read(config_filename)
parser = RawConfigParser()
if not parser.read(config_filename):
raise ValueError('Configuration file cannot be opened: {}'.format(config_filename))
validate_configuration_format(parser, config_format)
+2 -2
View File
@@ -2,7 +2,7 @@ import os
import sys
from flexmock import flexmock
from nose.tools import assert_raises
import pytest
from atticmatic import command as module
@@ -66,7 +66,7 @@ def test_parse_arguments_with_invalid_arguments_exits():
sys.stderr = sys.stdout
try:
with assert_raises(SystemExit):
with pytest.raises(SystemExit):
module.parse_arguments(COMMAND_NAME, '--posix-me-harder')
finally:
sys.stderr = original_stderr
@@ -0,0 +1,29 @@
try:
# Python 2
from cStringIO import StringIO
except ImportError:
# Python 3
from io import StringIO
from collections import OrderedDict
import string
from atticmatic import config as module
def test_parse_section_options_with_punctuation_should_return_section_options():
parser = module.RawConfigParser()
parser.readfp(StringIO('[section]\nfoo: {}\n'.format(string.punctuation)))
section_format = module.Section_format(
'section',
(module.Config_option('foo', str, required=True),),
)
config = module.parse_section_options(parser, section_format)
assert config == OrderedDict(
(
('foo', string.punctuation),
)
)
@@ -0,0 +1,8 @@
import subprocess
def test_setup_version_matches_news_version():
setup_version = subprocess.check_output(('python', 'setup.py', '--version')).decode('ascii')
news_version = open('NEWS').readline()
assert setup_version == news_version
+141 -4
View File
@@ -1,4 +1,6 @@
from collections import OrderedDict
from subprocess import STDOUT
import os
from flexmock import flexmock
@@ -7,8 +9,30 @@ 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 = flexmock(STDOUT=STDOUT)
subprocess.should_receive('check_call').with_args(check_call_command, **kwargs).once()
flexmock(module).subprocess = subprocess
@@ -41,12 +65,28 @@ 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',
)
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',
verbosity=None,
storage_config={},
source_directories='foo bar',
repository='repo',
command='attic',
)
def test_create_archive_with_none_excludes_filename_should_call_attic_without_excludes():
insert_subprocess_mock(CREATE_COMMAND_WITHOUT_EXCLUDES)
insert_platform_mock()
@@ -55,6 +95,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 +110,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,12 +125,107 @@ 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',
)
def test_create_archive_with_one_file_system_should_call_attic_with_one_file_system_parameters():
insert_subprocess_mock(CREATE_COMMAND + ('--one-file-system',))
insert_platform_mock()
insert_datetime_mock()
module.create_archive(
excludes_filename='excludes',
verbosity=None,
storage_config={},
source_directories='foo bar',
repository='repo',
command='attic',
one_file_system=True,
)
def test_create_archive_with_umask_should_call_attic_with_umask_parameters():
insert_subprocess_mock(CREATE_COMMAND + ('--umask', '740'))
insert_platform_mock()
insert_datetime_mock()
module.create_archive(
excludes_filename='excludes',
verbosity=None,
storage_config={'umask': 740},
source_directories='foo bar',
repository='repo',
command='attic',
)
def test_create_archive_with_source_directories_glob_expands():
insert_subprocess_mock(('attic', 'create', 'repo::host-now', 'foo', 'food'))
insert_platform_mock()
insert_datetime_mock()
flexmock(module).should_receive('glob').with_args('foo*').and_return(['foo', 'food'])
module.create_archive(
excludes_filename=None,
verbosity=None,
storage_config={},
source_directories='foo*',
repository='repo',
command='attic',
)
def test_create_archive_with_non_matching_source_directories_glob_passes_through():
insert_subprocess_mock(('attic', 'create', 'repo::host-now', 'foo*'))
insert_platform_mock()
insert_datetime_mock()
flexmock(module).should_receive('glob').with_args('foo*').and_return([])
module.create_archive(
excludes_filename=None,
verbosity=None,
storage_config={},
source_directories='foo*',
repository='repo',
command='attic',
)
def test_create_archive_with_glob_should_call_attic_with_expanded_directories():
insert_subprocess_mock(('attic', 'create', 'repo::host-now', 'foo', 'food'))
insert_platform_mock()
insert_datetime_mock()
flexmock(module).should_receive('glob').with_args('foo*').and_return(['foo', 'food'])
module.create_archive(
excludes_filename=None,
verbosity=None,
storage_config={},
source_directories='foo*',
repository='repo',
command='attic',
)
BASE_PRUNE_FLAGS = (
('--keep-daily', '1'),
('--keep-weekly', '2'),
@@ -217,7 +354,7 @@ def test_check_archives_should_call_attic_with_parameters():
stdout = flexmock()
insert_subprocess_mock(
('attic', 'check', 'repo'),
stdout=stdout,
stdout=stdout, stderr=STDOUT,
)
insert_platform_mock()
insert_datetime_mock()
@@ -238,7 +375,7 @@ def test_check_archives_with_verbosity_some_should_call_attic_with_verbose_param
flexmock(module).should_receive('_make_check_flags').and_return(())
insert_subprocess_mock(
('attic', 'check', 'repo', '--verbose'),
stdout=None,
stdout=None, stderr=STDOUT,
)
insert_platform_mock()
insert_datetime_mock()
@@ -257,7 +394,7 @@ def test_check_archives_with_verbosity_lots_should_call_attic_with_verbose_param
flexmock(module).should_receive('_make_check_flags').and_return(())
insert_subprocess_mock(
('attic', 'check', 'repo', '--verbose'),
stdout=None,
stdout=None, stderr=STDOUT,
)
insert_platform_mock()
insert_datetime_mock()
+17 -7
View File
@@ -1,7 +1,7 @@
from collections import OrderedDict
from flexmock import flexmock
from nose.tools import assert_raises
import pytest
from atticmatic import config as module
@@ -61,7 +61,7 @@ def test_validate_configuration_format_with_missing_required_section_should_rais
),
)
with assert_raises(ValueError):
with pytest.raises(ValueError):
module.validate_configuration_format(parser, config_format)
@@ -96,7 +96,7 @@ def test_validate_configuration_format_with_unknown_section_should_raise():
module.Section_format('section', options=()),
)
with assert_raises(ValueError):
with pytest.raises(ValueError):
module.validate_configuration_format(parser, config_format)
@@ -114,7 +114,7 @@ def test_validate_configuration_format_with_missing_required_option_should_raise
),
)
with assert_raises(ValueError):
with pytest.raises(ValueError):
module.validate_configuration_format(parser, config_format)
@@ -146,7 +146,7 @@ def test_validate_configuration_format_with_extra_option_should_raise():
),
)
with assert_raises(ValueError):
with pytest.raises(ValueError):
module.validate_configuration_format(parser, config_format)
@@ -154,6 +154,7 @@ def test_parse_section_options_should_return_section_options():
parser = flexmock()
parser.should_receive('get').with_args('section', 'foo').and_return('value')
parser.should_receive('getint').with_args('section', 'bar').and_return(1)
parser.should_receive('getboolean').never()
parser.should_receive('has_option').with_args('section', 'foo').and_return(True)
parser.should_receive('has_option').with_args('section', 'bar').and_return(True)
@@ -179,6 +180,7 @@ def test_parse_section_options_for_missing_section_should_return_empty_dict():
parser = flexmock()
parser.should_receive('get').never()
parser.should_receive('getint').never()
parser.should_receive('getboolean').never()
parser.should_receive('has_option').with_args('section', 'foo').and_return(False)
parser.should_receive('has_option').with_args('section', 'bar').and_return(False)
@@ -197,8 +199,8 @@ def test_parse_section_options_for_missing_section_should_return_empty_dict():
def insert_mock_parser():
parser = flexmock()
parser.should_receive('read')
module.ConfigParser = lambda: parser
parser.should_receive('read').and_return([flexmock()])
module.RawConfigParser = lambda: parser
return parser
@@ -220,3 +222,11 @@ def test_parse_configuration_should_return_section_configs():
parsed_config = module.parse_configuration('filename', config_format)
assert parsed_config == type(parsed_config)(*mock_section_configs)
def test_parse_configuration_with_file_open_error_should_raise():
parser = insert_mock_parser()
parser.should_receive('read').and_return([])
with pytest.raises(ValueError):
module.parse_configuration('filename', config_format=flexmock())
+19 -3
View File
@@ -1,14 +1,30 @@
[location]
# Space-separated list of source directories to backup.
source_directories: /home /etc
# Globs are expanded.
source_directories: /home /etc /var/log/syslog*
# For Borg only, you can specify to stay in same file system (do not cross
# mount points).
#one_file_system: True
# 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.readthedocs.org/en/stable/usage.html#borg-create
# for details. Defaults to no compression.
#compression: lz4
# For Borg only, you can specify the umask to be used for borg create.
#umask: 0740
[retention]
# Retention policy for how many backups to keep in each category. See
# https://attic-backup.org/usage.html#attic-prune or
# https://borgbackup.github.io/borgbackup/usage.html#borg-prune for details.
# https://borgbackup.readthedocs.org/en/stable/usage.html#borg-prune for details.
#keep_within: 3H
#keep_hourly: 24
keep_daily: 7
@@ -21,7 +37,7 @@ keep_yearly: 1
# Space-separated list of consistency checks to run: "repository", "archives",
# or both. Defaults to both. Set to "disabled" to disable all consistency
# checks. See https://attic-backup.org/usage.html#attic-check or
# https://borgbackup.github.io/borgbackup/usage.html#borg-check for details.
# https://borgbackup.readthedocs.org/en/stable/usage.html#borg-check for details.
checks: repository archives
# For Borg only, you can restrict the number of checked archives to the last n.
#check_last: 3
+2 -2
View File
@@ -1,2 +1,2 @@
[nosetests]
detailed-errors=1
[metadata]
description-file=README.md
+17 -2
View File
@@ -1,11 +1,26 @@
from setuptools import setup, find_packages
VERSION = '0.1.8'
setup(
name='atticmatic',
version='0.1.1',
version=VERSION,
description='A wrapper script for Attic/Borg backup software that creates and prunes backups',
author='Dan Helfman',
author_email='witten@torsion.org',
url='https://torsion.org/atticmatic',
download_url='https://torsion.org/hg/atticmatic/archive/%s.tar.gz' % VERSION,
classifiers=(
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Programming Language :: Python',
'Topic :: Security :: Cryptography',
'Topic :: System :: Archiving :: Backup',
),
packages=find_packages(),
entry_points={
'console_scripts': [
@@ -15,6 +30,6 @@ setup(
},
tests_require=(
'flexmock',
'nose',
'pytest',
)
)
+2 -2
View File
@@ -1,2 +1,2 @@
flexmock==0.9.7
nose==1.3.4
flexmock==0.10.2
pytest==2.9.1
+1 -1
View File
@@ -5,4 +5,4 @@ skipsdist=True
[testenv]
usedevelop=True
deps=-rtest_requirements.txt
commands = nosetests []
commands = py.test []