Skip to content

Commit

Permalink
Add a restore command (#43)
Browse files Browse the repository at this point in the history
* Add restore command to restore deleted objects in a versioned bucket

* Bump version

* fix lint

* Flush stdout for people who are not using PYTHONUNBUFFERED

* fix lint
  • Loading branch information
algrs authored Aug 23, 2017
1 parent b90692f commit 71538ca
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 4 deletions.
16 changes: 15 additions & 1 deletion baiji/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ListCommand(BaijiCommand):
DESCRIPTION = "list files on s3"
uri = cli.Flag(["-B", "--uri"], help='This option does nothing. It used to return URIs instead of paths, but this is now the default.')
detail = cli.Flag(['-l', '--detail'], help='print details, like `ls -l`')
shallow = cli.Flag("--shallow", help='process key names hierarchically and return only immediate "children" (like ls, instead of like find)')
shallow = cli.Flag(['-s', "--shallow"], help='process key names hierarchically and return only immediate "children" (like ls, instead of like find)')
list_versions = cli.Flag(['--list-versions'], help='print all versions')

def main(self, key):
Expand All @@ -39,6 +39,20 @@ def main(self, key):
print e
return 1

class RestoreCommand(BaijiCommand):
DESCRIPTION = "restore deleted files on s3"
def main(self, prefix):
from baiji.util.console import LabeledSpinner
if s3.path.islocal(prefix):
raise ValueError("restore command only works on s3")
spin = LabeledSpinner()
for key in s3.ls(prefix, return_full_urls=True, require_s3_scheme=True, list_versions=True):
if not s3.exists(key):
spin.drop("Restoring deleted file {}".format(key))
s3.restore(key)
else:
spin.spin(key)

class InfoCommand(BaijiCommand):
DESCRIPTION = "info for file on s3"
def main(self, key):
Expand Down
11 changes: 11 additions & 0 deletions baiji/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,17 @@ def ls(self, s3prefix, return_full_urls=False, require_s3_scheme=False, shallow=
else:
raise InvalidSchemeException("URI Scheme %s is not implemented" % k.scheme)

def restore(self, key):
from boto.s3.deletemarker import DeleteMarker
k = path.parse(key)
prefix = k.path
if prefix.startswith(path.sep):
prefix = prefix[len(path.sep):]
versions = self._bucket(k.netloc).list_versions(prefix)
delete_marker = [x for x in versions if x.name == prefix and isinstance(x, DeleteMarker) and x.is_latest]
if delete_marker:
self._bucket(k.netloc).delete_key(delete_marker[0].name, version_id=delete_marker[0].version_id)

def glob(self, prefix, pattern):
'''
Given a path prefix and a pattern, iterate over matching paths.
Expand Down
3 changes: 1 addition & 2 deletions baiji/copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,6 @@ def remote_copy(self):
'''
With copy_key, if metadata is None, it will be copied from the existing key
'''
src = self.src.remote_path
headers = {}
if self.policy:
headers['x-amz-acl'] = self.policy
Expand All @@ -422,7 +421,7 @@ def remote_copy(self):
self.dst.bucket.copy_key(
self.dst.remote_path,
self.src.bucket_name,
src,
self.src.remote_path,
preserve_acl=self.preserve_acl,
metadata=meta,
headers=headers,
Expand Down
2 changes: 1 addition & 1 deletion baiji/package_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
#
# See https://www.python.org/dev/peps/pep-0420/#namespace-packages-today

__version__ = '2.9.0'
__version__ = '2.10.0'
3 changes: 3 additions & 0 deletions baiji/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ def glob(*args, **kwargs):
def info(*args, **kwargs):
return S3Connection().info(*args, **kwargs)

def restore(*args, **kwargs):
return S3Connection().restore(*args, **kwargs)

def exists(*args, **kwargs):
return S3Connection().exists(*args, **kwargs)

Expand Down
18 changes: 18 additions & 0 deletions baiji/util/console.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
from __future__ import print_function

class LabeledSpinner(object):
def __init__(self, style=None):
from pyspin.spin import Default, Spinner
if style is None:
style = Default
self.spinner = Spinner(style)
self.last_len = 0
def spin(self, text):
self.display(u"{} {}".format(self.spinner.next(), text))
def drop(self, text):
self.display(u"{}\n".format(text))
def display(self, text):
import sys
print(u'\r{}{}'.format(text, " " * (self.last_len - len(text))), end='')
sys.stdout.flush()
self.last_len = len(text)


# Adapted from http://code.activestate.com/recipes/577058/
def confirm(question, default="no"):
"""Ask a yes/no question via raw_input() and return their answer.
Expand Down
1 change: 1 addition & 0 deletions bin/s3
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class S3App(cli.Application):
DESCRIPTION = "AWS S3 tool\nkeys are a kind of URL, of the form s3://BUCKET/PATH/TO/FILE"

S3App.subcommand("ls", "baiji.cli.ListCommand")
S3App.subcommand("restore", "baiji.cli.RestoreCommand")
S3App.subcommand("info", "baiji.cli.InfoCommand")
S3App.subcommand("rm", "baiji.cli.RemoveCommand")
S3App.subcommand("cp", "baiji.cli.CopyCommand")
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ pyyaml>=3.11,<4
progressbar>=2.3,<3
requests>=2.9.1,<3
plumbum>=1.6.2,<2
pyspin>=1.1.1,<2

0 comments on commit 71538ca

Please sign in to comment.