Initial import.

This commit is contained in:
Dan Helfman 2014-10-30 22:34:03 -07:00
commit 16bebe9832
10 changed files with 212 additions and 0 deletions

0
atticmatic/__init__.py Normal file
View file

34
atticmatic/attic.py Normal file
View file

@ -0,0 +1,34 @@
from datetime import datetime
import platform
import subprocess
def create_archive(excludes_filename, verbose, source_directories, repository):
sources = tuple(source_directories.split(' '))
command = (
'attic', 'create',
'--exclude-from', excludes_filename,
'{repo}::{hostname}-{timestamp}'.format(
repo=repository,
hostname=platform.node(),
timestamp=datetime.now().isoformat(),
),
) + sources + (
('--verbose', '--stats') if verbose else ()
)
subprocess.check_call(command)
def prune_archives(repository, verbose, keep_daily, keep_weekly, keep_monthly):
command = (
'attic', 'prune',
repository,
'--keep-daily', str(keep_daily),
'--keep-weekly', str(keep_weekly),
'--keep-monthly', str(keep_monthly),
) + (('--verbose',) if verbose else ())
subprocess.check_call(command)

38
atticmatic/command.py Normal file
View file

@ -0,0 +1,38 @@
from __future__ import print_function
from argparse import ArgumentParser
from subprocess import CalledProcessError
import sys
from atticmatic.attic import create_archive, prune_archives
from atticmatic.config import parse_configuration
def main():
parser = ArgumentParser()
parser.add_argument(
'--config',
dest='config_filename',
default='/etc/atticmatic/config',
help='Configuration filename',
)
parser.add_argument(
'--excludes',
dest='excludes_filename',
default='/etc/atticmatic/excludes',
help='Excludes filename',
)
parser.add_argument(
'--verbose',
action='store_true',
help='Display verbose progress information',
)
args = parser.parse_args()
try:
location_config, retention_config = parse_configuration(args.config_filename)
create_archive(args.excludes_filename, args.verbose, *location_config)
prune_archives(location_config.repository, args.verbose, *retention_config)
except (ValueError, CalledProcessError), error:
print(error, file=sys.stderr)
sys.exit(1)

57
atticmatic/config.py Normal file
View file

@ -0,0 +1,57 @@
from collections import namedtuple
from ConfigParser import SafeConfigParser
CONFIG_SECTION_LOCATION = 'location'
CONFIG_SECTION_RETENTION = 'retention'
CONFIG_FORMAT = {
CONFIG_SECTION_LOCATION: ('source_directories', 'repository'),
CONFIG_SECTION_RETENTION: ('keep_daily', 'keep_weekly', 'keep_monthly'),
}
LocationConfig = namedtuple('LocationConfig', CONFIG_FORMAT[CONFIG_SECTION_LOCATION])
RetentionConfig = namedtuple('RetentionConfig', CONFIG_FORMAT[CONFIG_SECTION_RETENTION])
def parse_configuration(config_filename):
'''
Given a config filename of the expected format, return the parse configuration as a tuple of
(LocationConfig, RetentionConfig). Raise if the format is not as expected.
'''
parser = SafeConfigParser()
parser.read((config_filename,))
section_names = parser.sections()
expected_section_names = CONFIG_FORMAT.keys()
if set(section_names) != set(expected_section_names):
raise ValueError(
'Expected config sections {} but found sections: {}'.format(
', '.join(expected_section_names),
', '.join(section_names)
)
)
for section_name in section_names:
option_names = parser.options(section_name)
expected_option_names = CONFIG_FORMAT[section_name]
if set(option_names) != set(expected_option_names):
raise ValueError(
'Expected options {} in config section {} but found options: {}'.format(
', '.join(expected_option_names),
section_name,
', '.join(option_names)
)
)
return (
LocationConfig(*(
parser.get(CONFIG_SECTION_LOCATION, option_name)
for option_name in CONFIG_FORMAT[CONFIG_SECTION_LOCATION]
)),
RetentionConfig(*(
parser.getint(CONFIG_SECTION_RETENTION, option_name)
for option_name in CONFIG_FORMAT[CONFIG_SECTION_RETENTION]
))
)