right_aws 1.9.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|