diff options
| author | Max Resnick <max@ofmax.li> | 2020-01-19 10:14:18 -0800 |
|---|---|---|
| committer | Max Resnick <max@ofmax.li> | 2020-01-19 10:14:18 -0800 |
| commit | d0db3585a66cc9e2f190b2ff381beb79a82bd304 (patch) | |
| tree | 1e3e16cd8ef58b2fc9002c464c844bcbc77c7ad1 | |
| parent | 1b8767584a41f3163293f890abc901c23e78dce2 (diff) | |
| download | git-snapshot-d0db3585a66cc9e2f190b2ff381beb79a82bd304.tar.gz | |
cleaned up and working
| -rw-r--r-- | gitsnap/restore.py | 122 |
1 files changed, 88 insertions, 34 deletions
diff --git a/gitsnap/restore.py b/gitsnap/restore.py index 186214b..44e32fb 100644 --- a/gitsnap/restore.py +++ b/gitsnap/restore.py @@ -3,6 +3,9 @@ import argparse from pathlib import Path import datetime from functools import wraps +import collections +import csv +import sh from sh.contrib import git import boto3 @@ -11,66 +14,117 @@ s3 = boto3.resource('s3') parser = argparse.ArgumentParser() -subparsers = parser.add_subparsers(dest='cmd', help='sub-command help') +subparsers = parser.add_subparsers(dest='cmd', help='git snapshot commands') parser_backup= subparsers.add_parser('backup', help='backup repositories') parser_backup.add_argument('repositories', help='directory with repos') -# create the parser for the "b" command -parser_b = subparsers.add_parser('b', help='b help') -parser_b.add_argument('--baz', choices='XYZ', help='baz help') - args = parser.parse_args() - -def logit(func): - @wraps(func) - def with_logging(*args, **kwargs): - print(func.__name__ + " was called") - return func(*args, **kwargs) - return with_logging - -def cloud_object(bundle, prefix, bucket): - obj = s3.Object(bucket, f'{prefix}/{bundle.name}') +Context = collections.namedtuple('Context', + 'repo bucket prefix bundle repo_name wal') + + +class Wal(object): + """WAL writer for server processes""" + + WAL_FILE = os.environ.get('WAL_FILE', 'git-snaphot.wal') + wal = None + fd = None + + def __init__(self): + self.fd = open(self.WAL_FILE, 'a') + self.wal = csv.writer(self.fd, quotechar='|', quoting=csv.QUOTE_ALL) + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.fd_to_disk() + self.fd.close() + + def write(self, row): + ts_row = [datetime.datetime.utcnow().timestamp()] + ts_row.extend(row) + self.wal.writerow(ts_row) + self.fd_to_disk() + + def fd_to_disk(self): + self.fd.flush() + os.fsync(self.fd) + +def logit(log_message): + def logitargs(func): + @wraps(func) + def with_logging(ctx, *args, **kwargs): + ctx.wal.write([log_message, f'started {ctx.repo_name}']) + output = None + try: + output = func(ctx, *args, **kwargs) + except Exception as e: + ctx.wal.write([log_message, f'failed {e}']) + ctx.wal.write([log_message, f' completed {ctx.repo_name}']) + return output + return with_logging + return logitargs + +@logit('s3 object upload') +def cloud_object(ctx, bundle): + obj = s3.Object(ctx.bucket, f'{ctx.prefix}/{bundle.name}') obj.upload_file(str(bundle)) return obj -def backup_repo(repo, repo_name): +@logit('repo bundle') +def create_bundle(ctx): # new repo or backup ts = datetime.datetime.utcnow().timestamp() - bundle_path = Path(f'{repo_name}.{ts}.bundle') - repo.bundle('create', bundle_path, '--all') + bundle_path = Path(f'{ctx.repo_name}.{ts}.bundle') + ctx.repo.bundle('create', bundle_path, '--all') return bundle_path -def tag_checkpoint(repo): - last_hash = repo('rev-list', '-n', 1, '--all').strip() - repo.tag('-f', 'CHECKPOINT', last_hash) +@logit('repo checkpoint tag') +def tag_checkpoint(ctx): + last_hash = ctx.repo('rev-list', '-n', 1, '--all').strip() + ctx.repo.tag('-f', 'CHECKPOINT', last_hash) -def requires_backup(repo): - last_hash = repo.rev_list('rev-list', '-n', 1, '--all').strip() +@logit('ready for backup') +def requires_backup(ctx): + last_hash = ctx.repo('rev-list', '-n', 1, '--all').strip() # empty repo if not last_hash: return False try: - checkpoint = repo.rev_list('rev-list', '-n', 1, 'CHECKPOINT').strip() + checkpoint = ctx.repo('rev-list', '-n', 1, 'CHECKPOINT').strip() # no checkpoint exists - except sh.ErrorReturnCode_1: + except sh.ErrorReturnCode as e: return True - return True ^ (last_hash == checkpoint) + # flip truthy + return last_hash != checkpoint def run_restore(bundle): pass -def run_backup(base): +def run_backup(ctx): + if requires_backup(ctx): + bundle_path = create_bundle(ctx) + obj = cloud_object(ctx, bundle_path) + tag_checkpoint(ctx) + +def run_backups(base): repo_base_path = Path(base) - for repo_path in repo_base_path.glob('*.git'): - repo = git.bake(f'--git-dir={repo_path}/') - if requires_backup: - bundle_path = backup_repo(repo, repo_path.name.split('.')[0]) - obj = cloud_object(bundle_path, '2', 'privategit') - tag_checkpoint(repo) + with Wal() as wal: + for repo_path in repo_base_path.glob('*.git'): + ctx = Context( + repo=git.bake(f'--git-dir={repo_path}/'), + repo_name=repo_path.name.split('.')[0], + bucket='privategit', + prefix='2', + bundle=None, + wal=wal + ) + run_backup(ctx) if args.cmd == 'backup': - run_backup(args.repositories) + run_backups(args.repositories) elif args.cmd == 'restore': run_restore(args.bundle) |