aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Resnick <max@ofmax.li>2020-01-19 10:14:18 -0800
committerMax Resnick <max@ofmax.li>2020-01-19 10:14:18 -0800
commitd0db3585a66cc9e2f190b2ff381beb79a82bd304 (patch)
tree1e3e16cd8ef58b2fc9002c464c844bcbc77c7ad1
parent1b8767584a41f3163293f890abc901c23e78dce2 (diff)
downloadgit-snapshot-d0db3585a66cc9e2f190b2ff381beb79a82bd304.tar.gz
cleaned up and working
-rw-r--r--gitsnap/restore.py122
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)