24
24
from __future__ import print_function
25
25
from __future__ import unicode_literals
26
26
27
+ from functools import partial
27
28
import argparse
28
29
import calendar
29
30
import errno
30
31
import itertools
31
32
import os
32
33
import os .path
33
34
import sys
35
+ import tempfile
34
36
import time
35
37
36
- from boto .glacier .utils import DEFAULT_NUM_THREADS
37
38
from boto .glacier .concurrent import ConcurrentUploader
39
+ from boto .glacier .utils import DEFAULT_NUM_THREADS
38
40
import boto .glacier
39
41
import iso8601
40
42
import sqlalchemy
41
43
import sqlalchemy .ext .declarative
42
44
import sqlalchemy .orm
43
45
46
+ from gpg import Encryptor
47
+
44
48
45
49
# There is a lag between an archive being created and the archive
46
50
# appearing on an inventory. Even if the inventory has an InventoryDate
54
58
55
59
DEFAULT_PART_SIZE = 4194304
56
60
61
+ DEFAULT_REGION = 'us-east-1'
62
+
57
63
58
64
class ConsoleError (RuntimeError ):
59
65
def __init__ (self , m ):
@@ -469,9 +475,13 @@ def archive_list(self, args):
469
475
if archive_list :
470
476
print (* archive_list , sep = "\n " )
471
477
472
- def archive_upload (self , args , multipart = False ,
478
+ def archive_upload (self ,
479
+ args ,
480
+ multipart = False ,
481
+ encryptor = None ,
473
482
part_size = DEFAULT_PART_SIZE ,
474
483
num_threads = DEFAULT_NUM_THREADS ):
484
+
475
485
# XXX: "Leading whitespace in archive descriptions is removed."
476
486
# XXX: "The description must be less than or equal to 1024 bytes. The
477
487
# allowable characters are 7 bit ASCII without control codes,
@@ -486,39 +496,53 @@ def archive_upload(self, args, multipart=False,
486
496
raise RuntimeError ("Archive name not specified. Use --name." )
487
497
name = os .path .basename (full_name )
488
498
499
+ if encryptor :
500
+ tmpfile = tempfile .NamedTemporaryFile ()
501
+ encryptor .encrypt_file (args .file , tmpfile .name )
502
+ filename = tmpfile .name
503
+ else :
504
+ filename = args .file
505
+
489
506
vault = self .connection .get_vault (args .vault )
490
507
491
508
if not multipart :
492
509
archive_id = vault .create_archive_from_file (
493
- filename = args . file , description = name )
510
+ filename = filename , description = name )
494
511
else :
495
512
uploader = ConcurrentUploader (self .connection .layer1 ,
496
513
vault .name ,
497
514
part_size = part_size ,
498
515
num_threads = num_threads )
499
- archive_id = uploader .upload (args . file , description = name )
516
+ archive_id = uploader .upload (filename , description = name )
500
517
501
518
self .cache .add_archive (args .vault , name , archive_id )
502
519
503
- def multipart_archive_upload (self , args ):
504
- return self .archive_upload (args , multipart = True )
520
+ if encryptor :
521
+ tmpfile .close ()
522
+
523
+ def multipart_archive_upload (self , args , encryptor = None ):
524
+ return self .archive_upload (args , multipart = True , encryptor = encryptor )
505
525
506
526
@staticmethod
507
- def _write_archive_retrieval_job (f , job , multipart_size ):
527
+ def _write_archive_retrieval_job (f , job , multipart_size ,
528
+ encryptor = None ):
508
529
if job .archive_size > multipart_size :
509
530
def fetch (start , end ):
510
- byte_range = start , end - 1
531
+ byte_range = start , end - 1
511
532
f .write (job .get_output (byte_range ).read ())
512
533
513
534
whole_parts = job .archive_size // multipart_size
514
535
for first_byte in xrange (0 , whole_parts * multipart_size ,
515
- multipart_size ):
536
+ multipart_size ):
516
537
fetch (first_byte , first_byte + multipart_size )
517
538
remainder = job .archive_size % multipart_size
518
539
if remainder :
519
540
fetch (job .archive_size - remainder , job .archive_size )
520
541
else :
521
- f .write (job .get_output ().read ())
542
+ data = job .get_output ().read ()
543
+ if encryptor :
544
+ data = encryptor .decrypt (data )
545
+ f .write (data )
522
546
523
547
# Make sure that the file now exactly matches the downloaded archive,
524
548
# even if the file existed before and was longer.
@@ -531,19 +555,21 @@ def fetch(start, end):
531
555
raise
532
556
533
557
@classmethod
534
- def _archive_retrieve_completed (cls , args , job , name ):
558
+ def _archive_retrieve_completed (cls , args , job , name , encryptor = None ):
535
559
if args .output_filename == '-' :
536
560
cls ._write_archive_retrieval_job (
537
- sys .stdout , job , args .multipart_size )
561
+ sys .stdout , job , args .multipart_size ,
562
+ encryptor = encryptor )
538
563
else :
539
564
if args .output_filename :
540
565
filename = args .output_filename
541
566
else :
542
567
filename = os .path .basename (name )
543
568
with open (filename , 'wb' ) as f :
544
- cls ._write_archive_retrieval_job (f , job , args .multipart_size )
569
+ cls ._write_archive_retrieval_job (f , job , args .multipart_size ,
570
+ encryptor = encryptor )
545
571
546
- def archive_retrieve_one (self , args , name ):
572
+ def archive_retrieve_one (self , args , name , encryptor = None ):
547
573
try :
548
574
archive_id = self .cache .get_archive_id (args .vault , name )
549
575
except KeyError :
@@ -554,30 +580,35 @@ def archive_retrieve_one(self, args, name):
554
580
555
581
complete_job = find_complete_job (retrieval_jobs )
556
582
if complete_job :
557
- self ._archive_retrieve_completed (args , complete_job , name )
583
+ self ._archive_retrieve_completed (args , complete_job , name ,
584
+ encryptor = encryptor )
558
585
elif has_pending_job (retrieval_jobs ):
559
586
if args .wait :
560
587
complete_job = wait_until_job_completed (retrieval_jobs )
561
- self ._archive_retrieve_completed (args , complete_job , name )
588
+ self ._archive_retrieve_completed (args , complete_job , name ,
589
+ encryptor = encryptor )
562
590
else :
563
- raise RetryConsoleError ('job still pending for archive %r' % name )
591
+ raise RetryConsoleError ('job still pending for archive %r'
592
+ % name )
564
593
else :
565
594
# create an archive retrieval job
566
595
job = vault .retrieve_archive (archive_id )
567
596
if args .wait :
568
597
wait_until_job_completed ([job ])
569
598
self ._archive_retrieve_completed (args , job , name )
570
599
else :
571
- raise RetryConsoleError ('queued retrieval job for archive %r' % name )
600
+ raise RetryConsoleError ('queued retrieval job for archive %r'
601
+ % name )
572
602
573
- def archive_retrieve (self , args ):
603
+ def archive_retrieve (self , args , encryptor = None ):
574
604
if len (args .names ) > 1 and args .output_filename :
575
- raise ConsoleError ('cannot specify output filename with multi-archive retrieval' )
605
+ raise ConsoleError ("cannot specify output filename with "
606
+ "multi-archive retrieval" )
576
607
success_list = []
577
608
retry_list = []
578
609
for name in args .names :
579
610
try :
580
- self .archive_retrieve_one (args , name )
611
+ self .archive_retrieve_one (args , name , encryptor = encryptor )
581
612
except RetryConsoleError , e :
582
613
retry_list .append (e .message )
583
614
else :
@@ -642,7 +673,7 @@ def too_old(last_seen):
642
673
643
674
def main (self ):
644
675
parser = argparse .ArgumentParser ()
645
- parser .add_argument ('--region' , default = 'us-east-1' )
676
+ parser .add_argument ('--region' , default = DEFAULT_REGION )
646
677
subparsers = parser .add_subparsers ()
647
678
vault_subparser = subparsers .add_parser ('vault' ).add_subparsers ()
648
679
vault_subparser .add_parser ('list' ).set_defaults (func = self .vault_list )
@@ -661,18 +692,23 @@ def main(self):
661
692
archive_list_subparser .set_defaults (func = self .archive_list )
662
693
archive_list_subparser .add_argument ('vault' )
663
694
695
+ encryptor = Encryptor ()
696
+
664
697
# Upload command
698
+ archive_upload_func = partial (self .archive_upload , encryptor = encryptor )
665
699
archive_upload_subparser = archive_subparser .add_parser ('upload' )
666
- archive_upload_subparser .set_defaults (func = self . archive_upload )
700
+ archive_upload_subparser .set_defaults (func = archive_upload_func )
667
701
archive_upload_subparser .add_argument ('vault' )
668
702
archive_upload_subparser .add_argument ('file' )
669
703
archive_upload_subparser .add_argument ('--name' )
670
704
671
705
# Multipart upload command
706
+ multipart_archive_upload_func = partial (
707
+ self .archive_upload , encryptor = encryptor )
672
708
archive_multipart_upload_subparser = archive_subparser .add_parser (
673
709
'multipart_upload' )
674
710
archive_multipart_upload_subparser .set_defaults (
675
- func = self . multipart_archive_upload )
711
+ func = multipart_archive_upload_func )
676
712
archive_multipart_upload_subparser .add_argument ('vault' )
677
713
archive_multipart_upload_subparser .add_argument ('file' )
678
714
archive_multipart_upload_subparser .add_argument ('--name' )
@@ -687,32 +723,40 @@ def main(self):
687
723
dest = "num_threads"
688
724
)
689
725
726
+ # Retrieve command
727
+ archive_retrieve_func = partial (
728
+ self .archive_retrieve , encryptor = encryptor )
690
729
archive_retrieve_subparser = archive_subparser .add_parser ('retrieve' )
691
- archive_retrieve_subparser .set_defaults (func = self . archive_retrieve )
730
+ archive_retrieve_subparser .set_defaults (func = archive_retrieve_func )
692
731
archive_retrieve_subparser .add_argument ('vault' )
693
732
archive_retrieve_subparser .add_argument ('names' , nargs = '+' ,
694
733
metavar = 'name' )
695
734
archive_retrieve_subparser .add_argument ('--multipart-size' , type = int ,
696
- default = (8 * 1024 * 1024 ))
735
+ default = (8 * 1024 * 1024 ))
697
736
archive_retrieve_subparser .add_argument ('-o' , dest = 'output_filename' ,
698
737
metavar = 'OUTPUT_FILENAME' )
699
738
archive_retrieve_subparser .add_argument ('--wait' , action = 'store_true' )
739
+
740
+ # Delete command
700
741
archive_delete_subparser = archive_subparser .add_parser ('delete' )
701
742
archive_delete_subparser .set_defaults (func = self .archive_delete )
702
743
archive_delete_subparser .add_argument ('vault' )
703
744
archive_delete_subparser .add_argument ('name' )
745
+
746
+ # Checkpresent command
704
747
archive_checkpresent_subparser = archive_subparser .add_parser (
705
- 'checkpresent' )
748
+ 'checkpresent' )
706
749
archive_checkpresent_subparser .set_defaults (
707
- func = self .archive_checkpresent )
750
+ func = self .archive_checkpresent )
708
751
archive_checkpresent_subparser .add_argument ('vault' )
709
752
archive_checkpresent_subparser .add_argument ('name' )
710
753
archive_checkpresent_subparser .add_argument ('--wait' ,
711
754
action = 'store_true' )
712
755
archive_checkpresent_subparser .add_argument ('--quiet' ,
713
756
action = 'store_true' )
714
757
archive_checkpresent_subparser .add_argument (
715
- '--max-age' , type = int , default = 80 , dest = 'max_age_hours' )
758
+ '--max-age' , type = int , default = 80 , dest = 'max_age_hours' )
759
+
716
760
job_subparser = subparsers .add_parser ('job' ).add_subparsers ()
717
761
job_subparser .add_parser ('list' ).set_defaults (func = self .job_list )
718
762
args = parser .parse_args ()
0 commit comments