right_aws 1.9.0 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +164 -13
- data/Manifest.txt +28 -1
- data/README.txt +12 -10
- data/Rakefile +56 -29
- data/lib/acf/right_acf_interface.rb +343 -172
- data/lib/acf/right_acf_invalidations.rb +144 -0
- data/lib/acf/right_acf_origin_access_identities.rb +230 -0
- data/lib/acf/right_acf_streaming_interface.rb +229 -0
- data/lib/acw/right_acw_interface.rb +248 -0
- data/lib/as/right_as_interface.rb +698 -0
- data/lib/awsbase/right_awsbase.rb +755 -115
- data/lib/awsbase/support.rb +2 -78
- data/lib/awsbase/version.rb +9 -0
- data/lib/ec2/right_ec2.rb +274 -1294
- data/lib/ec2/right_ec2_ebs.rb +514 -0
- data/lib/ec2/right_ec2_images.rb +444 -0
- data/lib/ec2/right_ec2_instances.rb +797 -0
- data/lib/ec2/right_ec2_monitoring.rb +70 -0
- data/lib/ec2/right_ec2_placement_groups.rb +108 -0
- data/lib/ec2/right_ec2_reserved_instances.rb +243 -0
- data/lib/ec2/right_ec2_security_groups.rb +496 -0
- data/lib/ec2/right_ec2_spot_instances.rb +422 -0
- data/lib/ec2/right_ec2_tags.rb +139 -0
- data/lib/ec2/right_ec2_vpc.rb +598 -0
- data/lib/ec2/right_ec2_vpc2.rb +382 -0
- data/lib/ec2/right_ec2_windows_mobility.rb +84 -0
- data/lib/elb/right_elb_interface.rb +573 -0
- data/lib/emr/right_emr_interface.rb +728 -0
- data/lib/iam/right_iam_access_keys.rb +71 -0
- data/lib/iam/right_iam_groups.rb +195 -0
- data/lib/iam/right_iam_interface.rb +341 -0
- data/lib/iam/right_iam_mfa_devices.rb +67 -0
- data/lib/iam/right_iam_users.rb +251 -0
- data/lib/rds/right_rds_interface.rb +1657 -0
- data/lib/right_aws.rb +30 -13
- data/lib/route_53/right_route_53_interface.rb +641 -0
- data/lib/s3/right_s3.rb +108 -41
- data/lib/s3/right_s3_interface.rb +349 -118
- data/lib/sdb/active_sdb.rb +388 -54
- data/lib/sdb/right_sdb_interface.rb +323 -64
- data/lib/sns/right_sns_interface.rb +286 -0
- data/lib/sqs/right_sqs.rb +1 -2
- data/lib/sqs/right_sqs_gen2.rb +73 -17
- data/lib/sqs/right_sqs_gen2_interface.rb +146 -73
- data/lib/sqs/right_sqs_interface.rb +12 -22
- data/right_aws.gemspec +91 -0
- data/test/README.mdown +39 -0
- data/test/acf/test_right_acf.rb +11 -19
- data/test/awsbase/test_helper.rb +2 -0
- data/test/awsbase/test_right_awsbase.rb +11 -0
- data/test/ec2/test_right_ec2.rb +32 -1
- data/test/elb/test_helper.rb +2 -0
- data/test/elb/test_right_elb.rb +43 -0
- data/test/rds/test_helper.rb +2 -0
- data/test/rds/test_right_rds.rb +120 -0
- data/test/route_53/fixtures/a_record.xml +18 -0
- data/test/route_53/fixtures/alias_record.xml +18 -0
- data/test/route_53/test_helper.rb +2 -0
- data/test/route_53/test_right_route_53.rb +141 -0
- data/test/s3/test_right_s3.rb +176 -42
- data/test/s3/test_right_s3_stubbed.rb +6 -4
- data/test/sdb/test_active_sdb.rb +120 -19
- data/test/sdb/test_batch_put_attributes.rb +54 -0
- data/test/sdb/test_right_sdb.rb +71 -16
- data/test/sns/test_helper.rb +2 -0
- data/test/sns/test_right_sns.rb +153 -0
- data/test/sqs/test_right_sqs.rb +0 -6
- data/test/sqs/test_right_sqs_gen2.rb +104 -49
- data/test/ts_right_aws.rb +1 -0
- metadata +181 -22
data/lib/s3/right_s3.rb
CHANGED
@@ -59,7 +59,6 @@ module RightAws
|
|
59
59
|
# {:server => 's3.amazonaws.com' # Amazon service host: 's3.amazonaws.com'(default)
|
60
60
|
# :port => 443 # Amazon service port: 80 or 443(default)
|
61
61
|
# :protocol => 'https' # Amazon service protocol: 'http' or 'https'(default)
|
62
|
-
# :multi_thread => true|false # Multi-threaded (connection per each thread): true or false(default)
|
63
62
|
# :logger => Logger Object} # Logger instance: logs to STDOUT if omitted }
|
64
63
|
def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
|
65
64
|
@interface = S3Interface.new(aws_access_key_id, aws_secret_access_key, params)
|
@@ -98,10 +97,24 @@ module RightAws
|
|
98
97
|
# (section: Canned Access Policies)
|
99
98
|
#
|
100
99
|
def bucket(name, create=false, perms=nil, headers={})
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
100
|
+
result = nil
|
101
|
+
if create
|
102
|
+
headers['x-amz-acl'] = perms if perms
|
103
|
+
@interface.create_bucket(name, headers)
|
104
|
+
end
|
105
|
+
begin
|
106
|
+
buckets.each do |bucket|
|
107
|
+
if bucket.name == name
|
108
|
+
result = bucket
|
109
|
+
break
|
110
|
+
end
|
111
|
+
end
|
112
|
+
rescue RightAws::AwsError => e
|
113
|
+
# With non root creds one can use bucket(s) but can't list them
|
114
|
+
raise e unless e.message['AccessDenied']
|
115
|
+
result = Bucket::new(self, name)
|
116
|
+
end
|
117
|
+
result
|
105
118
|
end
|
106
119
|
|
107
120
|
|
@@ -217,25 +230,23 @@ module RightAws
|
|
217
230
|
#
|
218
231
|
# keys, service = bucket.keys_and_service({'max-keys'=> 2, 'prefix' => 'logs'})
|
219
232
|
# p keys #=> # 2 keys array
|
220
|
-
# p service #=> {"max-keys"=>"2", "prefix"=>"logs", "name"=>"my_awesome_bucket", "marker"=>"", "is_truncated"=>true}
|
233
|
+
# p service #=> {"max-keys"=>"2", "prefix"=>"logs", "name"=>"my_awesome_bucket", "marker"=>"", "is_truncated"=>true, :common_prefixes=>[]}
|
221
234
|
#
|
222
235
|
def keys_and_service(options={}, head=false)
|
223
236
|
opt = {}; options.each{ |key, value| opt[key.to_s] = value }
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
237
|
+
service = {}
|
238
|
+
keys = []
|
239
|
+
@s3.interface.incrementally_list_bucket(@name, opt) do |_service|
|
240
|
+
service = _service
|
241
|
+
service[:contents].each do |entry|
|
229
242
|
owner = Owner.new(entry[:owner_id], entry[:owner_display_name])
|
230
243
|
key = Key.new(self, entry[:key], nil, {}, {}, entry[:last_modified], entry[:e_tag], entry[:size], entry[:storage_class], owner)
|
231
244
|
key.head if head
|
232
|
-
|
245
|
+
keys << key
|
233
246
|
end
|
234
247
|
end
|
235
|
-
|
236
|
-
|
237
|
-
end
|
238
|
-
[list, service_data]
|
248
|
+
service.delete(:contents)
|
249
|
+
[keys, service]
|
239
250
|
end
|
240
251
|
|
241
252
|
# Retrieve key information from Amazon.
|
@@ -248,11 +259,12 @@ module RightAws
|
|
248
259
|
# key = RightAws::S3::Key.create(bucket, 'logs/today/1.log')
|
249
260
|
# key.head
|
250
261
|
#
|
251
|
-
def key(key_name, head=false)
|
252
|
-
raise 'Key name can not be empty.' if key_name.
|
262
|
+
def key(key_name, head=false, &blck)
|
263
|
+
raise 'Key name can not be empty.' if key_name.right_blank?
|
253
264
|
key_instance = nil
|
254
265
|
# if this key exists - find it ....
|
255
266
|
keys({'prefix'=>key_name}, head).each do |key|
|
267
|
+
blck.call if block_given?
|
256
268
|
if key.name == key_name.to_s
|
257
269
|
key_instance = key
|
258
270
|
break
|
@@ -271,17 +283,17 @@ module RightAws
|
|
271
283
|
#
|
272
284
|
# bucket.put('logs/today/1.log', 'Olala!') #=> true
|
273
285
|
#
|
274
|
-
def put(key, data=nil, meta_headers={}, perms=nil, headers={})
|
286
|
+
def put(key, data=nil, meta_headers={}, perms=nil, headers={}, &blck)
|
275
287
|
key = Key.create(self, key.to_s, data, meta_headers) unless key.is_a?(Key)
|
276
|
-
key.put(data, perms, headers)
|
288
|
+
key.put(data, perms, headers, &blck)
|
277
289
|
end
|
278
290
|
|
279
|
-
# Retrieve object
|
291
|
+
# Retrieve data object from Amazon.
|
280
292
|
# The +key+ is a +String+ or Key.
|
281
|
-
# Returns
|
293
|
+
# Returns String instance.
|
282
294
|
#
|
283
|
-
#
|
284
|
-
# puts
|
295
|
+
# data = bucket.get('logs/today/1.log') #=>
|
296
|
+
# puts data #=> 'sasfasfasdf'
|
285
297
|
#
|
286
298
|
def get(key, headers={})
|
287
299
|
key = Key.create(self, key.to_s) unless key.is_a?(Key)
|
@@ -346,9 +358,10 @@ module RightAws
|
|
346
358
|
# If +force+ is set, clears and deletes the bucket.
|
347
359
|
# Returns +true+.
|
348
360
|
#
|
349
|
-
# bucket.delete(true) #=> true
|
361
|
+
# bucket.delete(:force => true) #=> true
|
350
362
|
#
|
351
|
-
def delete(
|
363
|
+
def delete(options={})
|
364
|
+
force = options.is_a?(Hash) && options[:force]==true
|
352
365
|
force ? @s3.interface.force_delete_bucket(@name) : @s3.interface.delete_bucket(@name)
|
353
366
|
end
|
354
367
|
|
@@ -460,6 +473,30 @@ module RightAws
|
|
460
473
|
get if !@data and exists?
|
461
474
|
@data
|
462
475
|
end
|
476
|
+
|
477
|
+
# Getter for the 'content-type' metadata
|
478
|
+
def content_type
|
479
|
+
@headers['content-type'] if @headers
|
480
|
+
end
|
481
|
+
|
482
|
+
# Helper to get and URI-decode a header metadata.
|
483
|
+
# Metadata have to be HTTP encoded (rfc2616) as we use the Amazon S3 REST api
|
484
|
+
# see http://docs.amazonwebservices.com/AmazonS3/latest/index.html?UsingMetadata.html
|
485
|
+
def decoded_meta_headers(key = nil)
|
486
|
+
if key
|
487
|
+
# Get one metadata value by its key
|
488
|
+
URI.decode(@meta_headers[key.to_s])
|
489
|
+
else
|
490
|
+
# Get a hash of all metadata with a decoded value
|
491
|
+
@decoded_meta_headers ||= begin
|
492
|
+
metadata = {}
|
493
|
+
@meta_headers.each do |key, value|
|
494
|
+
metadata[key.to_sym] = URI.decode(value)
|
495
|
+
end
|
496
|
+
metadata
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
463
500
|
|
464
501
|
# Retrieve object data and attributes from Amazon.
|
465
502
|
# Returns a +String+.
|
@@ -482,11 +519,33 @@ module RightAws
|
|
482
519
|
# ...
|
483
520
|
# key.put('Olala!') #=> true
|
484
521
|
#
|
485
|
-
def put(data=nil, perms=nil, headers={})
|
522
|
+
def put(data=nil, perms=nil, headers={}, &blck)
|
486
523
|
headers['x-amz-acl'] = perms if perms
|
487
524
|
@data = data || @data
|
488
525
|
meta = self.class.add_meta_prefix(@meta_headers)
|
489
|
-
@bucket.s3.interface.put(@bucket.name, @name, @data, meta.merge(headers))
|
526
|
+
@bucket.s3.interface.put(@bucket.name, @name, @data, meta.merge(headers), &blck)
|
527
|
+
end
|
528
|
+
|
529
|
+
# Store object data on S3 using the Multipart Upload API. This is useful if you do not know the file size
|
530
|
+
# upfront (for example reading from pipe or socket) or if you are transmitting data over an unreliable network.
|
531
|
+
#
|
532
|
+
# Parameter +data+ is an object which responds to :read or an object which can be converted to a String prior to upload.
|
533
|
+
# Parameter +part_size+ determines the size of each part sent (must be > 5MB per Amazon's API requirements)
|
534
|
+
#
|
535
|
+
# If data is a stream the caller is responsible for calling close() on the stream after this methods returns
|
536
|
+
#
|
537
|
+
# Returns +true+.
|
538
|
+
#
|
539
|
+
# upload_data = StringIO.new('My sample data')
|
540
|
+
# key = RightAws::S3::Key.create(bucket, 'logs/today/1.log')
|
541
|
+
# key.data = upload_data
|
542
|
+
# key.put_multipart(:part_size => 5*1024*1024) #=> true
|
543
|
+
#
|
544
|
+
def put_multipart(data=nil, perms=nil, headers={}, part_size=nil)
|
545
|
+
headers['x-amz-acl'] = perms if perms
|
546
|
+
@data = data || @data
|
547
|
+
meta = self.class.add_meta_prefix(@meta_headers)
|
548
|
+
@bucket.s3.interface.store_object_multipart({:bucket => @bucket.name, :key => @name, :data => @data, :headers => meta.merge(headers), :part_size => part_size})
|
490
549
|
end
|
491
550
|
|
492
551
|
# Rename an object. Returns new object name.
|
@@ -626,7 +685,7 @@ module RightAws
|
|
626
685
|
# key.delete #=> true
|
627
686
|
#
|
628
687
|
def delete
|
629
|
-
raise 'Key name must be specified.' if @name.
|
688
|
+
raise 'Key name must be specified.' if @name.right_blank?
|
630
689
|
@bucket.s3.interface.delete(@bucket, @name)
|
631
690
|
end
|
632
691
|
|
@@ -759,11 +818,11 @@ module RightAws
|
|
759
818
|
@thing = thing
|
760
819
|
@id = id
|
761
820
|
@name = name
|
762
|
-
@perms = perms
|
821
|
+
@perms = Array(perms)
|
763
822
|
case action
|
764
|
-
when :apply
|
765
|
-
when :refresh
|
766
|
-
when :apply_and_refresh
|
823
|
+
when :apply then apply
|
824
|
+
when :refresh then refresh
|
825
|
+
when :apply_and_refresh then apply; refresh
|
767
826
|
end
|
768
827
|
end
|
769
828
|
|
@@ -775,9 +834,13 @@ module RightAws
|
|
775
834
|
false
|
776
835
|
end
|
777
836
|
|
778
|
-
# Return Grantee type (+String+): "Group" or "CanonicalUser".
|
837
|
+
# Return Grantee type (+String+): "Group", "AmazonCustomerByEmail" or "CanonicalUser".
|
779
838
|
def type
|
780
|
-
@id
|
839
|
+
case @id
|
840
|
+
when /^http:/ then "Group"
|
841
|
+
when /@/ then "AmazonCustomerByEmail"
|
842
|
+
else "CanonicalUser"
|
843
|
+
end
|
781
844
|
end
|
782
845
|
|
783
846
|
# Return a name or an id.
|
@@ -872,7 +935,11 @@ module RightAws
|
|
872
935
|
end
|
873
936
|
|
874
937
|
def to_xml # :nodoc:
|
875
|
-
id_str =
|
938
|
+
id_str = case @id
|
939
|
+
when /^http/ then "<URI>#{@id}</URI>"
|
940
|
+
when /@/ then "<EmailAddress>#{@id}</EmailAddress>"
|
941
|
+
else "<ID>#{@id}</ID>"
|
942
|
+
end
|
876
943
|
grants = ''
|
877
944
|
@perms.each do |perm|
|
878
945
|
grants << "<Grant>" +
|
@@ -1012,8 +1079,8 @@ module RightAws
|
|
1012
1079
|
#
|
1013
1080
|
# bucket.get('logs/today/1.log', 1.hour)
|
1014
1081
|
#
|
1015
|
-
def get(key, expires=nil, headers={})
|
1016
|
-
@s3.interface.get_link(@name, key.to_s, expires, headers)
|
1082
|
+
def get(key, expires=nil, headers={}, response_params={})
|
1083
|
+
@s3.interface.get_link(@name, key.to_s, expires, headers, response_params)
|
1017
1084
|
end
|
1018
1085
|
|
1019
1086
|
# Generate link to delete bucket.
|
@@ -1054,7 +1121,7 @@ module RightAws
|
|
1054
1121
|
@bucket = bucket
|
1055
1122
|
@name = name.to_s
|
1056
1123
|
@meta_headers = meta_headers
|
1057
|
-
raise 'Key name can not be empty.' if @name.
|
1124
|
+
raise 'Key name can not be empty.' if @name.right_blank?
|
1058
1125
|
end
|
1059
1126
|
|
1060
1127
|
# Generate link to PUT key data.
|
@@ -1069,8 +1136,8 @@ module RightAws
|
|
1069
1136
|
#
|
1070
1137
|
# bucket.get('logs/today/1.log', 1.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket/logs%2Ftoday%2F1.log?Signature=h...M%3D&Expires=1180820032&AWSAccessKeyId=1...2
|
1071
1138
|
#
|
1072
|
-
def get(expires=nil, headers={})
|
1073
|
-
@bucket.s3.interface.get_link(@bucket.to_s, @name, expires, headers)
|
1139
|
+
def get(expires=nil, headers={}, response_params={})
|
1140
|
+
@bucket.s3.interface.get_link(@bucket.to_s, @name, expires, headers, response_params)
|
1074
1141
|
end
|
1075
1142
|
|
1076
1143
|
# Generate link to delete key.
|
@@ -26,18 +26,37 @@ module RightAws
|
|
26
26
|
class S3Interface < RightAwsBase
|
27
27
|
|
28
28
|
USE_100_CONTINUE_PUT_SIZE = 1_000_000
|
29
|
+
MINIMUM_PART_SIZE = 5 * 1024 * 1024
|
30
|
+
DEFAULT_RETRY_COUNT = 5
|
29
31
|
|
30
32
|
include RightAwsBaseInterface
|
31
33
|
|
32
34
|
DEFAULT_HOST = 's3.amazonaws.com'
|
33
35
|
DEFAULT_PORT = 443
|
34
36
|
DEFAULT_PROTOCOL = 'https'
|
37
|
+
DEFAULT_SERVICE = '/'
|
35
38
|
REQUEST_TTL = 30
|
36
39
|
DEFAULT_EXPIRES_AFTER = 1 * 24 * 60 * 60 # One day's worth of seconds
|
37
40
|
ONE_YEAR_IN_SECONDS = 365 * 24 * 60 * 60
|
38
41
|
AMAZON_HEADER_PREFIX = 'x-amz-'
|
39
42
|
AMAZON_METADATA_PREFIX = 'x-amz-meta-'
|
43
|
+
S3_REQUEST_PARAMETERS = [ 'acl',
|
44
|
+
'location',
|
45
|
+
'logging', # this one is beta, no support for now
|
46
|
+
'partNumber',
|
47
|
+
'response-content-type',
|
48
|
+
'response-content-language',
|
49
|
+
'response-expires',
|
50
|
+
'response-cache-control',
|
51
|
+
'response-content-disposition',
|
52
|
+
'response-content-encoding',
|
53
|
+
'torrent',
|
54
|
+
'uploadId',
|
55
|
+
'uploads',
|
56
|
+
'delete'].sort
|
57
|
+
MULTI_OBJECT_DELETE_MAX_KEYS = 1000
|
40
58
|
|
59
|
+
|
41
60
|
@@bench = AwsBenchmarkingBlock.new
|
42
61
|
def self.bench_xml
|
43
62
|
@@bench.xml
|
@@ -46,23 +65,37 @@ module RightAws
|
|
46
65
|
@@bench.service
|
47
66
|
end
|
48
67
|
|
68
|
+
# Params supported:
|
69
|
+
# :no_subdomains => true # do not use bucket as a part of domain name but as a part of path
|
70
|
+
@@params = {}
|
71
|
+
def self.params
|
72
|
+
@@params
|
73
|
+
end
|
74
|
+
|
75
|
+
# get custom option
|
76
|
+
def param(name)
|
77
|
+
# - check explicitly defined param (@params)
|
78
|
+
# - otherwise check implicitly defined one (@@params)
|
79
|
+
@params.has_key?(name) ? @params[name] : @@params[name]
|
80
|
+
end
|
49
81
|
|
50
82
|
# Creates new RightS3 instance.
|
51
83
|
#
|
52
|
-
# s3 = RightAws::S3Interface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX', {:
|
84
|
+
# s3 = RightAws::S3Interface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX', {:logger => Logger.new('/tmp/x.log')}) #=> #<RightAws::S3Interface:0xb7b3c27c>
|
53
85
|
#
|
54
86
|
# Params is a hash:
|
55
87
|
#
|
56
|
-
# {:server
|
57
|
-
# :port
|
58
|
-
# :protocol
|
59
|
-
# :
|
60
|
-
# :
|
88
|
+
# {:server => 's3.amazonaws.com' # Amazon service host: 's3.amazonaws.com'(default)
|
89
|
+
# :port => 443 # Amazon service port: 80 or 443(default)
|
90
|
+
# :protocol => 'https' # Amazon service protocol: 'http' or 'https'(default)
|
91
|
+
# :logger => Logger Object # Logger instance: logs to STDOUT if omitted
|
92
|
+
# :no_subdomains => true} # Force placing bucket name into path instead of domain name
|
61
93
|
#
|
62
94
|
def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
|
63
95
|
init({ :name => 'S3',
|
64
96
|
:default_host => ENV['S3_URL'] ? URI.parse(ENV['S3_URL']).host : DEFAULT_HOST,
|
65
|
-
:default_port => ENV['S3_URL'] ? URI.parse(ENV['S3_URL']).port : DEFAULT_PORT,
|
97
|
+
:default_port => ENV['S3_URL'] ? URI.parse(ENV['S3_URL']).port : DEFAULT_PORT,
|
98
|
+
:default_service => ENV['S3_URL'] ? URI.parse(ENV['S3_URL']).path : DEFAULT_SERVICE,
|
66
99
|
:default_protocol => ENV['S3_URL'] ? URI.parse(ENV['S3_URL']).scheme : DEFAULT_PROTOCOL },
|
67
100
|
aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'],
|
68
101
|
aws_secret_access_key || ENV['AWS_SECRET_ACCESS_KEY'],
|
@@ -78,7 +111,11 @@ module RightAws
|
|
78
111
|
s3_headers = {}
|
79
112
|
headers.each do |key, value|
|
80
113
|
key = key.downcase
|
81
|
-
|
114
|
+
value = case
|
115
|
+
when value.is_a?(Array) then value.join('')
|
116
|
+
else value.to_s
|
117
|
+
end
|
118
|
+
s3_headers[key] = value.strip if key[/^#{AMAZON_HEADER_PREFIX}|^content-md5$|^content-type$|^date$/o]
|
82
119
|
end
|
83
120
|
s3_headers['content-type'] ||= ''
|
84
121
|
s3_headers['content-md5'] ||= ''
|
@@ -89,13 +126,20 @@ module RightAws
|
|
89
126
|
s3_headers.sort { |a, b| a[0] <=> b[0] }.each do |key, value|
|
90
127
|
out_string << (key[/^#{AMAZON_HEADER_PREFIX}/o] ? "#{key}:#{value}\n" : "#{value}\n")
|
91
128
|
end
|
92
|
-
|
129
|
+
# ignore everything after the question mark by default...
|
93
130
|
out_string << path.gsub(/\?.*$/, '')
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
131
|
+
# ... unless there is a parameter that we care about.
|
132
|
+
S3_REQUEST_PARAMETERS.each do |parameter|
|
133
|
+
if path[/[&?]#{parameter}(=[^&]*)?($|&)/]
|
134
|
+
if $1
|
135
|
+
value = CGI::unescape($1)
|
136
|
+
else
|
137
|
+
value = ''
|
138
|
+
end
|
139
|
+
out_string << (out_string[/[?]/] ? "&#{parameter}#{value}" : "?#{parameter}#{value}")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
99
143
|
out_string
|
100
144
|
end
|
101
145
|
|
@@ -108,37 +152,43 @@ module RightAws
|
|
108
152
|
end
|
109
153
|
true
|
110
154
|
end
|
111
|
-
|
112
|
-
|
113
|
-
# Assumes that headers[:url] is URL encoded (use CGI::escape)
|
114
|
-
def generate_rest_request(method, headers) # :nodoc:
|
155
|
+
|
156
|
+
def fetch_request_params(headers) #:nodoc:
|
115
157
|
# default server to use
|
116
|
-
server
|
117
|
-
|
118
|
-
|
119
|
-
path_to_sign = "/#{path_to_sign}" unless path_to_sign[/^\//]
|
158
|
+
server = @params[:server]
|
159
|
+
service = @params[:service].to_s
|
160
|
+
service.chop! if service[%r{/$}] # remove trailing '/' from service
|
120
161
|
# extract bucket name and check it's dns compartibility
|
121
|
-
|
162
|
+
headers[:url].to_s[%r{^([a-z0-9._-]*)(/[^?]*)?(\?.+)?}i]
|
122
163
|
bucket_name, key_path, params_list = $1, $2, $3
|
164
|
+
key_path = key_path.gsub( '%2F', '/' ) if key_path
|
123
165
|
# select request model
|
124
|
-
if is_dns_bucket?(bucket_name)
|
125
|
-
#
|
166
|
+
if !param(:no_subdomains) && is_dns_bucket?(bucket_name)
|
167
|
+
# fix a path
|
126
168
|
server = "#{bucket_name}.#{server}"
|
127
|
-
|
128
|
-
path = "#{key_path
|
129
|
-
# refactor the path (add '/' before params_list if the key is empty)
|
130
|
-
path_to_sign = "/#{bucket_name}#{path}"
|
169
|
+
key_path ||= '/'
|
170
|
+
path = "#{service}#{key_path}#{params_list}"
|
131
171
|
else
|
132
|
-
path =
|
172
|
+
path = "#{service}/#{bucket_name}#{key_path}#{params_list}"
|
133
173
|
end
|
174
|
+
path_to_sign = "#{service}/#{bucket_name}#{key_path}#{params_list}"
|
175
|
+
# path_to_sign = "/#{bucket_name}#{key_path}#{params_list}"
|
176
|
+
[ server, path, path_to_sign ]
|
177
|
+
end
|
178
|
+
|
179
|
+
# Generates request hash for REST API.
|
180
|
+
# Assumes that headers[:url] is URL encoded (use CGI::escape)
|
181
|
+
def generate_rest_request(method, headers) # :nodoc:
|
182
|
+
# calculate request data
|
183
|
+
server, path, path_to_sign = fetch_request_params(headers)
|
134
184
|
data = headers[:data]
|
135
|
-
#
|
136
|
-
headers
|
185
|
+
# make sure headers are downcased strings
|
186
|
+
headers = AwsUtils::fix_headers(headers)
|
137
187
|
#
|
138
188
|
headers['content-type'] ||= ''
|
139
189
|
headers['date'] = Time.now.httpdate
|
140
190
|
# create request
|
141
|
-
request = "Net::HTTP::#{method.capitalize}".
|
191
|
+
request = "Net::HTTP::#{method.capitalize}".right_constantize.new(path)
|
142
192
|
request.body = data if data
|
143
193
|
# set request headers and meta headers
|
144
194
|
headers.each { |key, value| request[key.to_s] = value }
|
@@ -157,12 +207,9 @@ module RightAws
|
|
157
207
|
# Sends request to Amazon and parses the response.
|
158
208
|
# Raises AwsError if any banana happened.
|
159
209
|
def request_info(request, parser, &block) # :nodoc:
|
160
|
-
|
161
|
-
thread[:s3_connection] ||= Rightscale::HttpConnection.new(:exception => RightAws::AwsError, :logger => @logger)
|
162
|
-
request_info_impl(thread[:s3_connection], @@bench, request, parser, &block)
|
210
|
+
request_info_impl(:s3_connection, @@bench, request, parser, &block)
|
163
211
|
end
|
164
212
|
|
165
|
-
|
166
213
|
# Returns an array of customer's buckets. Each item is a +hash+.
|
167
214
|
#
|
168
215
|
# s3.list_all_my_buckets #=>
|
@@ -187,8 +234,14 @@ module RightAws
|
|
187
234
|
#
|
188
235
|
def create_bucket(bucket, headers={})
|
189
236
|
data = nil
|
190
|
-
|
191
|
-
|
237
|
+
location = case headers[:location].to_s
|
238
|
+
when 'us','US' then ''
|
239
|
+
when 'eu' then 'EU'
|
240
|
+
else headers[:location].to_s
|
241
|
+
end
|
242
|
+
|
243
|
+
unless location.right_blank?
|
244
|
+
data = "<CreateBucketConfiguration><LocationConstraint>#{location}</LocationConstraint></CreateBucketConfiguration>"
|
192
245
|
end
|
193
246
|
req_hash = generate_rest_request('PUT', headers.merge(:url=>bucket, :data => data))
|
194
247
|
request_info(req_hash, RightHttp2xxParser.new)
|
@@ -238,7 +291,7 @@ module RightAws
|
|
238
291
|
AwsUtils.allow_only([:bucket,:xmldoc, :headers], params)
|
239
292
|
params[:headers] = {} unless params[:headers]
|
240
293
|
req_hash = generate_rest_request('PUT', params[:headers].merge(:url=>"#{params[:bucket]}?logging", :data => params[:xmldoc]))
|
241
|
-
request_info(req_hash,
|
294
|
+
request_info(req_hash, RightHttp2xxParser.new)
|
242
295
|
rescue
|
243
296
|
on_exception
|
244
297
|
end
|
@@ -273,7 +326,7 @@ module RightAws
|
|
273
326
|
# 'max-keys' => "5"}, ..., {...}]
|
274
327
|
#
|
275
328
|
def list_bucket(bucket, options={}, headers={})
|
276
|
-
bucket += '?'+options.map{|k, v| "#{k.to_s}=#{CGI::escape v.to_s}"}.join('&') unless options.
|
329
|
+
bucket += '?'+options.map{|k, v| "#{k.to_s}=#{CGI::escape v.to_s}"}.join('&') unless options.right_blank?
|
277
330
|
req_hash = generate_rest_request('GET', headers.merge(:url=>bucket))
|
278
331
|
request_info(req_hash, S3ListBucketParser.new(:logger => @logger))
|
279
332
|
rescue
|
@@ -308,10 +361,10 @@ module RightAws
|
|
308
361
|
# ]
|
309
362
|
# }
|
310
363
|
def incrementally_list_bucket(bucket, options={}, headers={}, &block)
|
311
|
-
internal_options = options.
|
364
|
+
internal_options = options.right_symbolize_keys
|
312
365
|
begin
|
313
366
|
internal_bucket = bucket.dup
|
314
|
-
internal_bucket += '?'+internal_options.map{|k, v| "#{k.to_s}=#{CGI::escape v.to_s}"}.join('&') unless internal_options.
|
367
|
+
internal_bucket += '?'+internal_options.map{|k, v| "#{k.to_s}=#{CGI::escape v.to_s}"}.join('&') unless internal_options.right_blank?
|
315
368
|
req_hash = generate_rest_request('GET', headers.merge(:url=>internal_bucket))
|
316
369
|
response = request_info(req_hash, S3ImprovedListBucketParser.new(:logger => @logger))
|
317
370
|
there_are_more_keys = response[:is_truncated]
|
@@ -382,7 +435,7 @@ module RightAws
|
|
382
435
|
# mode.
|
383
436
|
#
|
384
437
|
|
385
|
-
def put(bucket, key, data=nil, headers={})
|
438
|
+
def put(bucket, key, data=nil, headers={}, &blck)
|
386
439
|
# On Windows, if someone opens a file in text mode, we must reset it so
|
387
440
|
# to binary mode for streaming to work properly
|
388
441
|
if(data.respond_to?(:binmode))
|
@@ -393,7 +446,7 @@ module RightAws
|
|
393
446
|
headers['expect'] = '100-continue'
|
394
447
|
end
|
395
448
|
req_hash = generate_rest_request('PUT', headers.merge(:url=>"#{bucket}/#{CGI::escape key}", :data=>data))
|
396
|
-
request_info(req_hash, RightHttp2xxParser.new)
|
449
|
+
request_info(req_hash, RightHttp2xxParser.new, &blck)
|
397
450
|
rescue
|
398
451
|
on_exception
|
399
452
|
end
|
@@ -478,6 +531,134 @@ module RightAws
|
|
478
531
|
r[:verified_md5] ? (return r) : (raise AwsError.new("Uploaded object failed MD5 checksum verification: #{r.inspect}"))
|
479
532
|
end
|
480
533
|
|
534
|
+
# New experimental API for uploading objects using the multipart upload API.
|
535
|
+
# store_object_multipart is similar in function to the store_object method, but breaks the input into parts and transmits each
|
536
|
+
# part separately. The multipart upload API has the benefit of being be able to retransmit a part in isolation without needing to
|
537
|
+
# restart the entire upload. This makes it ideal for uploading large files over unreliable networks. It also does not
|
538
|
+
# require the file size to be known before starting the upload, making it useful for stream data as it is created (say via reading a pipe or socket).
|
539
|
+
# The hash of the response headers contains useful information like the location (the URI for the newly created object), bucket, key, and etag).
|
540
|
+
#
|
541
|
+
# The optional argument of :headers allows the caller to specify arbitrary request header values.
|
542
|
+
#
|
543
|
+
# s3.store_object_multipart(:bucket => "foobucket", :key => "foo", :data => "polemonium" )
|
544
|
+
# => {:location=>"https://s3.amazonaws.com/right_s3_awesome_test_bucket_000B1_officedrop/test%2Flarge_multipart_file",
|
545
|
+
# :e_tag=>"\"72b81ac08aed4d4d1055c11f56c2a258-1\"",
|
546
|
+
# :key=>"test/large_multipart_file",
|
547
|
+
# :bucket=>"right_s3_awesome_test_bucket_000B1_officedrop"}
|
548
|
+
#
|
549
|
+
# f = File.new("some_file", "r")
|
550
|
+
# s3.store_object_multipart(:bucket => "foobucket", :key => "foo", :data => f )
|
551
|
+
# => {:location=>"https://s3.amazonaws.com/right_s3_awesome_test_bucket_000B1_officedrop/test%2Flarge_multipart_file",
|
552
|
+
# :e_tag=>"\"72b81ac08aed4d4d1055c11f56c2a258-1\"",
|
553
|
+
# :key=>"test/large_multipart_file",
|
554
|
+
# :bucket=>"right_s3_awesome_test_bucket_000B1_officedrop"}
|
555
|
+
def store_object_multipart(params)
|
556
|
+
AwsUtils.allow_only([:bucket, :key, :data, :headers, :part_size, :retry_count], params)
|
557
|
+
AwsUtils.mandatory_arguments([:bucket, :key, :data], params)
|
558
|
+
params[:headers] = {} unless params[:headers]
|
559
|
+
|
560
|
+
params[:data].binmode if(params[:data].respond_to?(:binmode)) # On Windows, if someone opens a file in text mode, we must reset it to binary mode for streaming to work properly
|
561
|
+
|
562
|
+
# detect whether we are using straight read or converting to string first
|
563
|
+
unless(params[:data].respond_to?(:read))
|
564
|
+
params[:data] = StringIO.new(params[:data].to_s)
|
565
|
+
end
|
566
|
+
|
567
|
+
# make sure part size is > 5 MB minimum
|
568
|
+
params[:part_size] ||= MINIMUM_PART_SIZE
|
569
|
+
if params[:part_size] < MINIMUM_PART_SIZE
|
570
|
+
raise AwsError.new("Part size for a multipart upload must be greater than or equal to #{5 * 1024 * 1024} bytes. #{params[:part_size]} bytes was provided.")
|
571
|
+
end
|
572
|
+
|
573
|
+
# make sure retry_count is positive
|
574
|
+
params[:retry_count] ||= DEFAULT_RETRY_COUNT
|
575
|
+
if params[:retry_count] < 0
|
576
|
+
raise AwsError.new("Retry count must be positive. #{params[:retry_count]} bytes was provided.")
|
577
|
+
end
|
578
|
+
|
579
|
+
# Set 100-continue for large part sizes
|
580
|
+
if (params[:part_size] >= USE_100_CONTINUE_PUT_SIZE)
|
581
|
+
params[:headers]['expect'] = '100-continue'
|
582
|
+
end
|
583
|
+
|
584
|
+
# initiate upload
|
585
|
+
initiate_hash = generate_rest_request('POST', params[:headers].merge(:url=>"#{params[:bucket]}/#{CGI::escape params[:key]}?uploads"))
|
586
|
+
initiate_resp = request_info(initiate_hash, S3MultipartUploadInitiateResponseParser.new)
|
587
|
+
upload_id = initiate_resp[:upload_id]
|
588
|
+
|
589
|
+
# split into parts and upload each one, re-trying if necessary
|
590
|
+
# upload occurs serially at this time.
|
591
|
+
part_etags = []
|
592
|
+
part_data = ""
|
593
|
+
index = 1
|
594
|
+
until params[:data].eof?
|
595
|
+
part_data = params[:data].read(params[:part_size])
|
596
|
+
unless part_data.size == 0
|
597
|
+
retry_attempts = 1
|
598
|
+
while true
|
599
|
+
begin
|
600
|
+
send_part_hash = generate_rest_request('PUT', params[:headers].merge({ :url=>"#{params[:bucket]}/#{CGI::escape params[:key]}?partNumber=#{index}&uploadId=#{upload_id}", :data=>part_data } ))
|
601
|
+
send_part_resp = request_info(send_part_hash, S3HttpResponseHeadParser.new)
|
602
|
+
part_etags << {:part_num => index, :etag => send_part_resp['etag']}
|
603
|
+
index += 1
|
604
|
+
break # successful, can move to next part
|
605
|
+
rescue AwsError => e
|
606
|
+
if retry_attempts >= params[:retry_count]
|
607
|
+
raise e
|
608
|
+
else
|
609
|
+
#Hit an error attempting to transmit part, retry until retry_attemts have been exhausted
|
610
|
+
retry_attempts += 1
|
611
|
+
end
|
612
|
+
end
|
613
|
+
end
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
# assemble complete upload message
|
618
|
+
complete_body = "<CompleteMultipartUpload>"
|
619
|
+
part_etags.each do |part_hash|
|
620
|
+
complete_body << "<Part><PartNumber>#{part_hash[:part_num]}</PartNumber><ETag>#{part_hash[:etag]}</ETag></Part>"
|
621
|
+
end
|
622
|
+
complete_body << "</CompleteMultipartUpload>"
|
623
|
+
complete_req_hash = generate_rest_request('POST', {:url=>"#{params[:bucket]}/#{CGI::escape params[:key]}?uploadId=#{upload_id}", :data => complete_body})
|
624
|
+
return request_info(complete_req_hash, S3CompleteMultipartParser.new)
|
625
|
+
rescue
|
626
|
+
on_exception
|
627
|
+
end
|
628
|
+
|
629
|
+
class S3MultipartUploadInitiateResponseParser < RightAWSParser
|
630
|
+
def reset
|
631
|
+
@result = {}
|
632
|
+
end
|
633
|
+
def headers_to_string(headers)
|
634
|
+
result = {}
|
635
|
+
headers.each do |key, value|
|
636
|
+
value = value.first if value.is_a?(Array) && value.size<2
|
637
|
+
result[key] = value
|
638
|
+
end
|
639
|
+
result
|
640
|
+
end
|
641
|
+
def tagend(name)
|
642
|
+
case name
|
643
|
+
when 'UploadId' then @result[:upload_id] = @text
|
644
|
+
end
|
645
|
+
end
|
646
|
+
end
|
647
|
+
|
648
|
+
class S3CompleteMultipartParser < RightAWSParser # :nodoc:
|
649
|
+
def reset
|
650
|
+
@result = {}
|
651
|
+
end
|
652
|
+
def tagend(name)
|
653
|
+
case name
|
654
|
+
when 'Location' then @result[:location] = @text
|
655
|
+
when 'Bucket' then @result[:bucket] = @text
|
656
|
+
when 'Key' then @result[:key] = @text
|
657
|
+
when 'ETag' then @result[:e_tag] = @text
|
658
|
+
end
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
481
662
|
# Retrieves object data from Amazon. Returns a +hash+ or an exception.
|
482
663
|
#
|
483
664
|
# s3.get('my_awesome_bucket', 'log/curent/1.log') #=>
|
@@ -609,6 +790,34 @@ module RightAws
|
|
609
790
|
on_exception
|
610
791
|
end
|
611
792
|
|
793
|
+
# Deletes multiple keys. Returns an array with errors, if any.
|
794
|
+
#
|
795
|
+
# s3.delete_multiple('my_awesome_bucket', ['key1', 'key2', ...)
|
796
|
+
# #=> [ { :key => 'key2', :code => 'AccessDenied', :message => "Access Denied" } ]
|
797
|
+
#
|
798
|
+
def delete_multiple(bucket, keys=[], headers={})
|
799
|
+
errors = []
|
800
|
+
keys = Array.new(keys)
|
801
|
+
while keys.length > 0
|
802
|
+
data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
803
|
+
data += "<Delete>\n<Quiet>true</Quiet>\n"
|
804
|
+
keys.take(MULTI_OBJECT_DELETE_MAX_KEYS).each do |key|
|
805
|
+
data += "<Object><Key>#{AwsUtils::xml_escape(key)}</Key></Object>\n"
|
806
|
+
end
|
807
|
+
data += "</Delete>"
|
808
|
+
req_hash = generate_rest_request('POST', headers.merge(
|
809
|
+
:url => "#{bucket}?delete",
|
810
|
+
:data => data,
|
811
|
+
'content-md5' => AwsUtils::content_md5(data)
|
812
|
+
))
|
813
|
+
errors += request_info(req_hash, S3DeleteMultipleParser.new)
|
814
|
+
keys = keys.drop(MULTI_OBJECT_DELETE_MAX_KEYS)
|
815
|
+
end
|
816
|
+
errors
|
817
|
+
rescue
|
818
|
+
on_exception
|
819
|
+
end
|
820
|
+
|
612
821
|
# Copy an object.
|
613
822
|
# directive: :copy - copy meta-headers from source (default value)
|
614
823
|
# :replace - replace meta-headers by passed ones
|
@@ -674,7 +883,7 @@ module RightAws
|
|
674
883
|
# <Permission>FULL_CONTROL</Permission></Grant></AccessControlList></AccessControlPolicy>" }
|
675
884
|
#
|
676
885
|
def get_acl(bucket, key='', headers={})
|
677
|
-
key = key.
|
886
|
+
key = key.right_blank? ? '' : "/#{CGI::escape key}"
|
678
887
|
req_hash = generate_rest_request('GET', headers.merge(:url=>"#{bucket}#{key}?acl"))
|
679
888
|
request_info(req_hash, S3HttpResponseBodyParser.new)
|
680
889
|
rescue
|
@@ -704,7 +913,7 @@ module RightAws
|
|
704
913
|
# :display_name=>"root"}}
|
705
914
|
#
|
706
915
|
def get_acl_parse(bucket, key='', headers={})
|
707
|
-
key = key.
|
916
|
+
key = key.right_blank? ? '' : "/#{CGI::escape key}"
|
708
917
|
req_hash = generate_rest_request('GET', headers.merge(:url=>"#{bucket}#{key}?acl"))
|
709
918
|
acl = request_info(req_hash, S3AclParser.new(:logger => @logger))
|
710
919
|
result = {}
|
@@ -717,7 +926,7 @@ module RightAws
|
|
717
926
|
else
|
718
927
|
result[:grantees][key] =
|
719
928
|
{ :display_name => grantee[:display_name] || grantee[:uri].to_s[/[^\/]*$/],
|
720
|
-
:permissions => grantee[:permissions]
|
929
|
+
:permissions => Array(grantee[:permissions]),
|
721
930
|
:attributes => grantee[:attributes] }
|
722
931
|
end
|
723
932
|
end
|
@@ -728,7 +937,7 @@ module RightAws
|
|
728
937
|
|
729
938
|
# Sets the ACL on a bucket or object.
|
730
939
|
def put_acl(bucket, key, acl_xml_doc, headers={})
|
731
|
-
key = key.
|
940
|
+
key = key.right_blank? ? '' : "/#{CGI::escape key}"
|
732
941
|
req_hash = generate_rest_request('PUT', headers.merge(:url=>"#{bucket}#{key}?acl", :data=>acl_xml_doc))
|
733
942
|
request_info(req_hash, S3HttpResponseBodyParser.new)
|
734
943
|
rescue
|
@@ -806,36 +1015,25 @@ module RightAws
|
|
806
1015
|
# Query API: Links
|
807
1016
|
#-----------------------------------------------------------------
|
808
1017
|
|
1018
|
+
def s3_link_escape(text)
|
1019
|
+
#CGI::escape(text.to_s).gsub(/[+]/, '%20')
|
1020
|
+
AwsUtils::amz_escape(text.to_s)
|
1021
|
+
end
|
1022
|
+
|
809
1023
|
# Generates link for QUERY API
|
810
1024
|
def generate_link(method, headers={}, expires=nil) #:nodoc:
|
811
|
-
|
812
|
-
server =
|
813
|
-
|
814
|
-
|
815
|
-
path_to_sign = "/#{path_to_sign}" unless path_to_sign[/^\//]
|
816
|
-
# extract bucket name and check it's dns compartibility
|
817
|
-
path_to_sign[%r{^/([a-z0-9._-]*)(/[^?]*)?(\?.+)?}i]
|
818
|
-
bucket_name, key_path, params_list = $1, $2, $3
|
819
|
-
# select request model
|
820
|
-
if is_dns_bucket?(bucket_name)
|
821
|
-
# add backet to a server name
|
822
|
-
server = "#{bucket_name}.#{server}"
|
823
|
-
# remove bucket from the path
|
824
|
-
path = "#{key_path || '/'}#{params_list}"
|
825
|
-
# refactor the path (add '/' before params_list if the key is empty)
|
826
|
-
path_to_sign = "/#{bucket_name}#{path}"
|
827
|
-
else
|
828
|
-
path = path_to_sign
|
829
|
-
end
|
830
|
-
# expiration time
|
1025
|
+
# calculate request data
|
1026
|
+
server, path, path_to_sign = fetch_request_params(headers)
|
1027
|
+
|
1028
|
+
# expiration time
|
831
1029
|
expires ||= DEFAULT_EXPIRES_AFTER
|
832
1030
|
expires = Time.now.utc + expires if expires.is_a?(Fixnum) && (expires < ONE_YEAR_IN_SECONDS)
|
833
1031
|
expires = expires.to_i
|
834
|
-
#
|
835
|
-
headers
|
1032
|
+
# make sure headers are downcased strings
|
1033
|
+
headers = AwsUtils::fix_headers(headers)
|
836
1034
|
#generate auth strings
|
837
1035
|
auth_string = canonical_string(method, path_to_sign, headers, expires)
|
838
|
-
signature = CGI::escape(
|
1036
|
+
signature = CGI::escape(AwsUtils::sign( @aws_secret_access_key, auth_string))
|
839
1037
|
# path building
|
840
1038
|
addon = "Signature=#{signature}&Expires=#{expires}&AWSAccessKeyId=#{@aws_access_key_id}"
|
841
1039
|
path += path[/\?/] ? "&#{addon}" : "?#{addon}"
|
@@ -879,7 +1077,7 @@ module RightAws
|
|
879
1077
|
# s3.list_bucket_link('my_awesome_bucket') #=> url string
|
880
1078
|
#
|
881
1079
|
def list_bucket_link(bucket, options=nil, expires=nil, headers={})
|
882
|
-
bucket += '?' + options.map{|k, v| "#{k.to_s}=#{
|
1080
|
+
bucket += '?' + options.map{|k, v| "#{k.to_s}=#{s3_link_escape(v)}"}.join('&') unless options.right_blank?
|
883
1081
|
generate_link('GET', headers.merge(:url=>bucket), expires)
|
884
1082
|
rescue
|
885
1083
|
on_exception
|
@@ -890,7 +1088,7 @@ module RightAws
|
|
890
1088
|
# s3.put_link('my_awesome_bucket',key, object) #=> url string
|
891
1089
|
#
|
892
1090
|
def put_link(bucket, key, data=nil, expires=nil, headers={})
|
893
|
-
generate_link('PUT', headers.merge(:url=>"#{bucket}/#{
|
1091
|
+
generate_link('PUT', headers.merge(:url=>"#{bucket}/#{s3_link_escape(key)}", :data=>data), expires)
|
894
1092
|
rescue
|
895
1093
|
on_exception
|
896
1094
|
end
|
@@ -907,8 +1105,20 @@ module RightAws
|
|
907
1105
|
# s3.get_link('my_awesome_bucket',key) #=> https://s3.amazonaws.com:443/my_awesome_bucket/asia%2Fcustomers?Signature=QAO...
|
908
1106
|
#
|
909
1107
|
# see http://docs.amazonwebservices.com/AmazonS3/2006-03-01/VirtualHosting.html
|
910
|
-
|
911
|
-
|
1108
|
+
#
|
1109
|
+
# To specify +response+-* parameters, define them in the response_params hash:
|
1110
|
+
#
|
1111
|
+
# s3.get_link('my_awesome_bucket',key,nil,{},{ "response-content-disposition" => "attachment; filename=caf�.png", "response-content-type" => "image/png"})
|
1112
|
+
#
|
1113
|
+
# #=> https://s3.amazonaws.com:443/my_awesome_bucket/asia%2Fcustomers?response-content-disposition=attachment%3B%20filename%3Dcaf%25C3%25A9.png&response-content-type=image%2Fpng&Signature=wio...
|
1114
|
+
#
|
1115
|
+
def get_link(bucket, key, expires=nil, headers={}, response_params={})
|
1116
|
+
if response_params.size > 0
|
1117
|
+
response_params = '?' + response_params.map { |k, v| "#{k}=#{s3_link_escape(v)}" }.join('&')
|
1118
|
+
else
|
1119
|
+
response_params = ''
|
1120
|
+
end
|
1121
|
+
generate_link('GET', headers.merge(:url=>"#{bucket}/#{s3_link_escape(key)}#{response_params}"), expires)
|
912
1122
|
rescue
|
913
1123
|
on_exception
|
914
1124
|
end
|
@@ -918,7 +1128,7 @@ module RightAws
|
|
918
1128
|
# s3.head_link('my_awesome_bucket',key) #=> url string
|
919
1129
|
#
|
920
1130
|
def head_link(bucket, key, expires=nil, headers={})
|
921
|
-
generate_link('HEAD', headers.merge(:url=>"#{bucket}/#{
|
1131
|
+
generate_link('HEAD', headers.merge(:url=>"#{bucket}/#{s3_link_escape(key)}"), expires)
|
922
1132
|
rescue
|
923
1133
|
on_exception
|
924
1134
|
end
|
@@ -928,7 +1138,7 @@ module RightAws
|
|
928
1138
|
# s3.delete_link('my_awesome_bucket',key) #=> url string
|
929
1139
|
#
|
930
1140
|
def delete_link(bucket, key, expires=nil, headers={})
|
931
|
-
generate_link('DELETE', headers.merge(:url=>"#{bucket}/#{
|
1141
|
+
generate_link('DELETE', headers.merge(:url=>"#{bucket}/#{s3_link_escape(key)}"), expires)
|
932
1142
|
rescue
|
933
1143
|
on_exception
|
934
1144
|
end
|
@@ -939,7 +1149,7 @@ module RightAws
|
|
939
1149
|
# s3.get_acl_link('my_awesome_bucket',key) #=> url string
|
940
1150
|
#
|
941
1151
|
def get_acl_link(bucket, key='', headers={})
|
942
|
-
return generate_link('GET', headers.merge(:url=>"#{bucket}/#{
|
1152
|
+
return generate_link('GET', headers.merge(:url=>"#{bucket}/#{s3_link_escape(key)}?acl"))
|
943
1153
|
rescue
|
944
1154
|
on_exception
|
945
1155
|
end
|
@@ -949,7 +1159,7 @@ module RightAws
|
|
949
1159
|
# s3.put_acl_link('my_awesome_bucket',key) #=> url string
|
950
1160
|
#
|
951
1161
|
def put_acl_link(bucket, key='', headers={})
|
952
|
-
return generate_link('PUT', headers.merge(:url=>"#{bucket}/#{
|
1162
|
+
return generate_link('PUT', headers.merge(:url=>"#{bucket}/#{s3_link_escape(key)}?acl"))
|
953
1163
|
rescue
|
954
1164
|
on_exception
|
955
1165
|
end
|
@@ -974,6 +1184,23 @@ module RightAws
|
|
974
1184
|
on_exception
|
975
1185
|
end
|
976
1186
|
|
1187
|
+
class S3DeleteMultipleParser < RightAWSParser # :nodoc:
|
1188
|
+
def reset
|
1189
|
+
@result = []
|
1190
|
+
end
|
1191
|
+
def tagstart(name, attributes)
|
1192
|
+
@error = {} if name == 'Error'
|
1193
|
+
end
|
1194
|
+
def tagend(name)
|
1195
|
+
case name
|
1196
|
+
when 'Key' then @error[:key] = @text
|
1197
|
+
when 'Code' then @error[:code] = @text
|
1198
|
+
when 'Message' then @error[:message] = @text
|
1199
|
+
when 'Error' then @result << @error
|
1200
|
+
end
|
1201
|
+
end
|
1202
|
+
end
|
1203
|
+
|
977
1204
|
#-----------------------------------------------------------------
|
978
1205
|
# PARSERS:
|
979
1206
|
#-----------------------------------------------------------------
|
@@ -988,11 +1215,11 @@ module RightAws
|
|
988
1215
|
end
|
989
1216
|
def tagend(name)
|
990
1217
|
case name
|
991
|
-
when 'ID'
|
992
|
-
when 'DisplayName'
|
993
|
-
when 'Name'
|
994
|
-
when 'CreationDate'
|
995
|
-
when 'Bucket'
|
1218
|
+
when 'ID' then @owner[:owner_id] = @text
|
1219
|
+
when 'DisplayName' then @owner[:owner_display_name] = @text
|
1220
|
+
when 'Name' then @current_bucket[:name] = @text
|
1221
|
+
when 'CreationDate'then @current_bucket[:creation_date] = @text
|
1222
|
+
when 'Bucket' then @result << @current_bucket.merge(@owner)
|
996
1223
|
end
|
997
1224
|
end
|
998
1225
|
end
|
@@ -1009,21 +1236,23 @@ module RightAws
|
|
1009
1236
|
def tagend(name)
|
1010
1237
|
case name
|
1011
1238
|
# service info
|
1012
|
-
when 'Name'
|
1013
|
-
when 'Prefix'
|
1014
|
-
when 'Marker'
|
1015
|
-
when 'MaxKeys'
|
1016
|
-
when 'Delimiter'
|
1017
|
-
when 'IsTruncated'
|
1239
|
+
when 'Name' then @service['name'] = @text
|
1240
|
+
when 'Prefix' then @service['prefix'] = @text
|
1241
|
+
when 'Marker' then @service['marker'] = @text
|
1242
|
+
when 'MaxKeys' then @service['max-keys'] = @text
|
1243
|
+
when 'Delimiter' then @service['delimiter'] = @text
|
1244
|
+
when 'IsTruncated' then @service['is_truncated'] = (@text =~ /false/ ? false : true)
|
1018
1245
|
# key data
|
1019
|
-
when 'Key'
|
1020
|
-
when 'LastModified'
|
1021
|
-
when 'ETag'
|
1022
|
-
when 'Size'
|
1023
|
-
when 'StorageClass'
|
1024
|
-
when 'ID'
|
1025
|
-
when 'DisplayName'
|
1026
|
-
when 'Contents'
|
1246
|
+
when 'Key' then @current_key[:key] = @text
|
1247
|
+
when 'LastModified'then @current_key[:last_modified] = @text
|
1248
|
+
when 'ETag' then @current_key[:e_tag] = @text
|
1249
|
+
when 'Size' then @current_key[:size] = @text.to_i
|
1250
|
+
when 'StorageClass'then @current_key[:storage_class] = @text
|
1251
|
+
when 'ID' then @current_key[:owner_id] = @text
|
1252
|
+
when 'DisplayName' then @current_key[:owner_display_name] = @text
|
1253
|
+
when 'Contents'
|
1254
|
+
@current_key[:service] = @service
|
1255
|
+
@result << @current_key
|
1027
1256
|
end
|
1028
1257
|
end
|
1029
1258
|
end
|
@@ -1045,27 +1274,29 @@ module RightAws
|
|
1045
1274
|
def tagend(name)
|
1046
1275
|
case name
|
1047
1276
|
# service info
|
1048
|
-
when 'Name'
|
1277
|
+
when 'Name' then @result[:name] = @text
|
1049
1278
|
# Amazon uses the same tag for the search prefix and for the entries
|
1050
1279
|
# in common prefix...so use our simple flag to see which element
|
1051
1280
|
# we are parsing
|
1052
|
-
when 'Prefix'
|
1053
|
-
when 'Marker'
|
1054
|
-
when 'MaxKeys'
|
1055
|
-
when 'Delimiter'
|
1056
|
-
when 'IsTruncated'
|
1057
|
-
when 'NextMarker'
|
1281
|
+
when 'Prefix' then @in_common_prefixes ? @common_prefixes << @text : @result[:prefix] = @text
|
1282
|
+
when 'Marker' then @result[:marker] = @text
|
1283
|
+
when 'MaxKeys' then @result[:max_keys] = @text
|
1284
|
+
when 'Delimiter' then @result[:delimiter] = @text
|
1285
|
+
when 'IsTruncated' then @result[:is_truncated] = (@text =~ /false/ ? false : true)
|
1286
|
+
when 'NextMarker' then @result[:next_marker] = @text
|
1058
1287
|
# key data
|
1059
|
-
when 'Key'
|
1060
|
-
when 'LastModified'
|
1061
|
-
when 'ETag'
|
1062
|
-
when 'Size'
|
1063
|
-
when 'StorageClass'
|
1064
|
-
when 'ID'
|
1065
|
-
when 'DisplayName'
|
1066
|
-
when 'Contents'
|
1288
|
+
when 'Key' then @current_key[:key] = @text
|
1289
|
+
when 'LastModified'then @current_key[:last_modified] = @text
|
1290
|
+
when 'ETag' then @current_key[:e_tag] = @text
|
1291
|
+
when 'Size' then @current_key[:size] = @text.to_i
|
1292
|
+
when 'StorageClass'then @current_key[:storage_class] = @text
|
1293
|
+
when 'ID' then @current_key[:owner_id] = @text
|
1294
|
+
when 'DisplayName' then @current_key[:owner_display_name] = @text
|
1295
|
+
when 'Contents' then @result[:contents] << @current_key
|
1067
1296
|
# Common Prefix stuff
|
1068
|
-
when 'CommonPrefixes'
|
1297
|
+
when 'CommonPrefixes'
|
1298
|
+
@result[:common_prefixes] = @common_prefixes
|
1299
|
+
@in_common_prefixes = false
|
1069
1300
|
end
|
1070
1301
|
end
|
1071
1302
|
end
|
@@ -1140,8 +1371,8 @@ module RightAws
|
|
1140
1371
|
end
|
1141
1372
|
def tagend(name)
|
1142
1373
|
case name
|
1143
|
-
when 'LastModified'
|
1144
|
-
when 'ETag'
|
1374
|
+
when 'LastModified' then @result[:last_modified] = @text
|
1375
|
+
when 'ETag' then @result[:e_tag] = @text
|
1145
1376
|
end
|
1146
1377
|
end
|
1147
1378
|
end
|
@@ -1158,7 +1389,7 @@ module RightAws
|
|
1158
1389
|
def headers_to_string(headers)
|
1159
1390
|
result = {}
|
1160
1391
|
headers.each do |key, value|
|
1161
|
-
value = value.
|
1392
|
+
value = value.first if value.is_a?(Array) && value.size<2
|
1162
1393
|
result[key] = value
|
1163
1394
|
end
|
1164
1395
|
result
|