Browse code

Added support for restoring files from Glacier storage.

Includes recursive option.

Robert Palmer authored on 2013/12/30 13:45:32
Showing 3 changed files
... ...
@@ -43,6 +43,7 @@ class Config(object):
43 43
     upload_id = None
44 44
     skip_existing = False
45 45
     recursive = False
46
+    restore_days = 1
46 47
     acl_public = None
47 48
     acl_grants = []
48 49
     acl_revokes = []
... ...
@@ -490,6 +490,18 @@ class S3(object):
490 490
         request = self.create_request("OBJECT_DELETE", uri = uri)
491 491
         response = self.send_request(request)
492 492
         return response
493
+    
494
+    def object_restore(self, uri):
495
+        if uri.type != "s3":
496
+            raise ValueError("Expected URI type 's3', got '%s'" % uri.type)
497
+        body = '<RestoreRequest xmlns="http://s3.amazonaws.com/doc/2006-3-01">'
498
+        body += ('  <Days>%s</Days>' % self.config.restore_days)
499
+        body += '</RestoreRequest>'
500
+        request = self.create_request("OBJECT_POST", uri = uri, extra = "?restore")
501
+        debug("About to send request '%s' with body '%s'" % (request, body))
502
+        response = self.send_request(request, body)
503
+        debug("Received response '%s'" % (response))
504
+        return response
493 505
 
494 506
     def object_copy(self, src_uri, dst_uri, extra_headers = None):
495 507
         if src_uri.type != "s3":
... ...
@@ -538,6 +538,40 @@ def subcmd_object_del_uri(uri_str, recursive = None):
538 538
         item = remote_list[key]
539 539
         response = s3.object_delete(S3Uri(item['object_uri_str']))
540 540
         output(u"File %s deleted" % item['object_uri_str'])
541
+        
542
+def cmd_object_restore(args):
543
+    s3 = S3(cfg)
544
+    
545
+    if cfg.restore_days < 1:
546
+        raise ParameterError("You must restore a file for 1 or more days")
547
+
548
+    remote_list = fetch_remote_list(args, require_attribs = False, recursive = cfg.recursive)
549
+    remote_list, exclude_list = filter_exclude_include(remote_list)
550
+
551
+    remote_count = len(remote_list)
552
+
553
+    info(u"Summary: Restoring %d remote files for %d days" % (remote_count, cfg.restore_days))
554
+
555
+    if cfg.dry_run:
556
+        for key in exclude_list:
557
+            output(u"exclude: %s" % unicodise(key))
558
+        for key in remote_list:
559
+            output(u"restore: %s" % remote_list[key]['object_uri_str'])
560
+
561
+        warning(u"Exiting now because of --dry-run")
562
+        return
563
+
564
+    for key in remote_list:
565
+        item = remote_list[key]
566
+        
567
+        uri = S3Uri(item['object_uri_str'])
568
+        if not item['object_uri_str'].endswith("/"):
569
+            output(u"Restoring obj: %s" % item['object_uri_str'])
570
+            response = s3.object_restore(S3Uri(item['object_uri_str']))
571
+            output(u"File %s restored" % item['object_uri_str'])
572
+        else:
573
+            debug(u"Skipping directory since only files may be restored")
574
+        
541 575
 
542 576
 def subcmd_cp_mv(args, process_fce, action_str, message):
543 577
     if len(args) < 2:
... ...
@@ -1752,6 +1786,7 @@ def get_commands_list():
1752 1752
     {"cmd":"get", "label":"Get file from bucket", "param":"s3://BUCKET/OBJECT LOCAL_FILE", "func":cmd_object_get, "argc":1},
1753 1753
     {"cmd":"del", "label":"Delete file from bucket", "param":"s3://BUCKET/OBJECT", "func":cmd_object_del, "argc":1},
1754 1754
     #{"cmd":"mkdir", "label":"Make a virtual S3 directory", "param":"s3://BUCKET/path/to/dir", "func":cmd_mkdir, "argc":1},
1755
+    {"cmd":"restore", "label":"Restore file from Glacier storage", "param":"s3://BUCKET/OBJECT", "func":cmd_object_restore, "argc":1},
1755 1756
     {"cmd":"sync", "label":"Synchronize a directory tree to S3", "param":"LOCAL_DIR s3://BUCKET[/PREFIX] or s3://BUCKET[/PREFIX] LOCAL_DIR", "func":cmd_sync, "argc":2},
1756 1757
     {"cmd":"du", "label":"Disk usage by buckets", "param":"[s3://BUCKET[/PREFIX]]", "func":cmd_du, "argc":0},
1757 1758
     {"cmd":"info", "label":"Get various information about Buckets or Files", "param":"s3://BUCKET[/OBJECT]", "func":cmd_info, "argc":1},
... ...
@@ -1918,6 +1953,8 @@ def main():
1918 1918
     optparser.add_option(      "--acl-grant", dest="acl_grants", type="s3acl", action="append", metavar="PERMISSION:EMAIL or USER_CANONICAL_ID", help="Grant stated permission to a given amazon user. Permission is one of: read, write, read_acp, write_acp, full_control, all")
1919 1919
     optparser.add_option(      "--acl-revoke", dest="acl_revokes", type="s3acl", action="append", metavar="PERMISSION:USER_CANONICAL_ID", help="Revoke stated permission for a given amazon user. Permission is one of: read, write, read_acp, wr     ite_acp, full_control, all")
1920 1920
 
1921
+    optparser.add_option("-D",  "--restore-days", dest="restore_days", action="store", help="Number of days to keep restored file available (only for 'restore' command).", metavar="NUM")
1922
+
1921 1923
     optparser.add_option(      "--delete-removed", dest="delete_removed", action="store_true", help="Delete remote objects with no corresponding local file [sync]")
1922 1924
     optparser.add_option(      "--no-delete-removed", dest="delete_removed", action="store_false", help="Don't delete remote objects.")
1923 1925
     optparser.add_option(      "--delete-after", dest="delete_after", action="store_true", help="Perform deletes after new uploads [sync]")