Initial import.
This commit is contained in:
commit
16bebe9832
10 changed files with 212 additions and 0 deletions
0
atticmatic/__init__.py
Normal file
0
atticmatic/__init__.py
Normal file
34
atticmatic/attic.py
Normal file
34
atticmatic/attic.py
Normal 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
38
atticmatic/command.py
Normal 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
57
atticmatic/config.py
Normal 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]
|
||||
))
|
||||
)
|
||||
Loading…
Reference in a new issue