right_aws 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,63 @@
1
+ #
2
+ # Copyright (c) 2007 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+
24
+ require 'benchmark'
25
+ require 'net/https'
26
+ require 'uri'
27
+ require 'time'
28
+ require "cgi"
29
+ require "base64"
30
+ require "rexml/document"
31
+ require "openssl"
32
+ require "digest/sha1"
33
+
34
+ require 'rubygems'
35
+ require 'active_support'
36
+ require 'right_http_connection'
37
+
38
+ $:.unshift(File.dirname(__FILE__))
39
+ require 'awsbase/benchmark_fix'
40
+ require 'awsbase/right_awsbase'
41
+ require 'ec2/right_ec2'
42
+ require 's3/right_s3_interface'
43
+ require 's3/right_s3'
44
+ require 'sqs/right_sqs_interface'
45
+ require 'sqs/right_sqs'
46
+
47
+
48
+ module RightAws #:nodoc:
49
+ module VERSION #:nodoc:
50
+ MAJOR = 1
51
+ MINOR = 1
52
+ TINY = 0
53
+
54
+ STRING = [MAJOR, MINOR, TINY].join('.')
55
+ end
56
+ end
57
+
58
+ # We also want everything available in the Rightscale namespace for backward
59
+ # compatibility reasons.
60
+ module Rightscale
61
+ include RightAws
62
+ extend RightAws
63
+ end
@@ -0,0 +1,879 @@
1
+ #
2
+ # Copyright (c) 2007 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+
24
+ module RightAws
25
+
26
+ class S3
27
+ attr_reader :interface
28
+
29
+ def initialize(aws_access_key_id, aws_secret_access_key, params={})
30
+ @interface = S3Interface.new(aws_access_key_id, aws_secret_access_key, params)
31
+ end
32
+
33
+ # Retrieve a list of buckets.
34
+ # Returns an array of Bucket instances.
35
+ #
36
+ # s3 = RightAws::S3.new(aws_access_key_id, aws_secret_access_key)
37
+ # p s3.buckets #=> array of buckets
38
+ #
39
+ def buckets
40
+ @interface.list_all_my_buckets.map! do |entry|
41
+ owner = Owner.new(entry[:owner_id], entry[:owner_display_name])
42
+ Bucket.new(self, entry[:name], entry[:creation_date], owner)
43
+ end
44
+ end
45
+
46
+ # Return an object representing a bucket.
47
+ # If the bucket does not exist and +create+ is set, a new bucket
48
+ # is created on S3. The +create+ parameter has no effect if
49
+ # the bucket alrady exists.
50
+ # Returns Bucket instance or +nil+ if the bucket does not exist
51
+ # and +create+ is not set.
52
+ #
53
+ # s3 = RightAws::S3.new(aws_access_key_id, aws_secret_access_key)
54
+ # bucket1 = s3.bucket('my_awesome_bucket')
55
+ # bucket1.keys #=> exception here if the bucket does not exists
56
+ # ...
57
+ # bucket2 = s3.bucket('my_awesome_bucket', true)
58
+ # bucket2.keys #=> list of keys
59
+ #
60
+ # see http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAccessPolicy.html
61
+ # (section: Canned Access Policies)
62
+ #
63
+ def bucket(name, create=true, perms=nil, headers={})
64
+ headers['x-amz-acl'] = perms if perms
65
+ @interface.create_bucket(name, headers) if create
66
+ buckets.each { |bucket| return bucket if bucket.name == name }
67
+ nil
68
+ end
69
+
70
+
71
+ class Bucket
72
+ attr_reader :s3, :name, :owner, :creation_date
73
+
74
+ # Create a Bucket instance.
75
+ # If the bucket does not exist and +create+ is set, a new bucket
76
+ # is created on S3. The +create+ parameter has no effect if
77
+ # the bucket alrady exists.
78
+ # Returns Bucket instance or +nil+ if the bucket does not exist
79
+ # and +create+ is not set.
80
+ #
81
+ # s3 = RightAws::S3.new(aws_access_key_id, aws_secret_access_key)
82
+ # ...
83
+ # bucket1 = RightAws::S3::Bucket.create(s3, 'my_awesome_bucket')
84
+ # bucket1.keys #=> exception here if the bucket does not exists
85
+ # ...
86
+ # bucket2 = RightAws::S3::Bucket.create(s3, 'my_awesome_bucket', true)
87
+ # bucket2.keys #=> list of keys
88
+ #
89
+ def self.create(s3, name, create=true, perms=nil, headers={})
90
+ s3.bucket(name, create, perms, headers)
91
+ end
92
+
93
+
94
+ # Create a bucket instance. In normal use this method should
95
+ # not be called directly.
96
+ # Use RightAws::S3::Bucket.create or RightAws::S3.bucket instead.
97
+ def initialize(s3, name, creation_date=nil, owner=nil)
98
+ @s3 = s3
99
+ @name = name
100
+ @owner = owner
101
+ @creation_date = creation_date
102
+ if @creation_date && !@creation_date.is_a?(Time)
103
+ @creation_date = Time.parse(@creation_date)
104
+ end
105
+ end
106
+
107
+ # Return bucket name as a String.
108
+ #
109
+ # bucket = RightAws::S3.bucket('my_awesome_bucket')
110
+ # puts bucket #=> 'my_awesome_bucket'
111
+ #
112
+ def to_s
113
+ @name.to_s
114
+ end
115
+ alias_method :full_name, :to_s
116
+
117
+ # Return a public link to bucket.
118
+ #
119
+ # bucket.public_link #=> 'https://s3.amazonaws.com:443/my_awesome_bucket'
120
+ #
121
+ def public_link
122
+ params = @s3.interface.params
123
+ "#{params[:protocol]}://#{params[:server]}:#{params[:port]}/#{full_name}"
124
+ end
125
+
126
+ # Retrieve a group of keys from Amazon.
127
+ # +options+ is a hash: { 'prefix'=>'', 'marker'=>'', 'max-keys'=>5, 'delimiter'=>'' }).
128
+ # Retrieves meta-headers information if +head+ it +true+.
129
+ # Returns an array of Key instances.
130
+ #
131
+ # bucket.keys #=> # returns all keys from bucket
132
+ # bucket.keys('prefix' => 'logs') #=> # returns all keys that starts with 'logs'
133
+ #
134
+ def keys(options={}, head=false)
135
+ keys_and_service(options, head)[0]
136
+ end
137
+
138
+ # Same as +keys+ method but return an array of [keys, service_data].
139
+ # where +service_data+ is a hash with additional output information.
140
+ #
141
+ # keys, service = bucket.keys_and_service({'max-keys'=> 2, 'prefix' => 'logs'})
142
+ # p keys #=> # 2 keys array
143
+ # p service #=> {"max-keys"=>"2", "prefix"=>"logs", "name"=>"my_awesome_bucket", "marker"=>"", "is_truncated"=>true}
144
+ #
145
+ def keys_and_service(options={}, head=false)
146
+ opt = {}; options.each{ |key, value| opt[key.to_s] = value }
147
+ service_data = {}
148
+ thislist = {}
149
+ list = []
150
+ @s3.interface.incrementally_list_bucket(@name, opt) do |thislist|
151
+ thislist[:contents].each do |entry|
152
+ owner = Owner.new(entry[:owner_id], entry[:owner_display_name])
153
+ key = Key.new(self, entry[:key], nil, {}, {}, entry[:last_modified], entry[:e_tag], entry[:size], entry[:storage_class], owner)
154
+ key.head if head
155
+ list << key
156
+ end
157
+ end
158
+ thislist.each_key do |key|
159
+ service_data[key] = thislist[key] unless (key == :contents || key == :common_prefixes)
160
+ end
161
+ [list, service_data]
162
+ end
163
+
164
+ # Retrieve key information from Amazon.
165
+ # The +key_name+ is a +String+ or Key instance.
166
+ # Retrieves meta-header information if +head+ is +true+.
167
+ # Returns new Key instance.
168
+ #
169
+ # key = bucket.key('logs/today/1.log', true) #=> #<RightAws::S3::Key:0xb7b1e240 ... >
170
+ # # is the same as:
171
+ # key = RightAws::S3::Key.create(bucket, 'logs/today/1.log')
172
+ # key.head
173
+ #
174
+ def key(key_name, head=false)
175
+ raise 'Key name can not be empty.' if key_name.blank?
176
+ key_instance = nil
177
+ # if this key exists - find it ....
178
+ keys({'prefix'=>key_name}, head).each do |key|
179
+ if key.name == key_name.to_s
180
+ key_instance = key
181
+ break
182
+ end
183
+ end
184
+ # .... else this key is unknown
185
+ unless key_instance
186
+ key_instance = Key.create(self, key_name.to_s)
187
+ end
188
+ key_instance
189
+ end
190
+
191
+ # Store object data.
192
+ # The +key+ is a +String+ or Key instance.
193
+ # Returns +true+.
194
+ #
195
+ # bucket.put('logs/today/1.log', 'Olala!') #=> true
196
+ #
197
+ def put(key, data=nil, meta_headers={}, perms=nil, headers={})
198
+ key = Key.create(self, key.to_s, data, meta_headers) unless key.is_a?(Key)
199
+ key.put(data, perms, headers)
200
+ end
201
+
202
+ # Retrieve object data from Amazon.
203
+ # The +key+ is a +String+ or Key.
204
+ # Returns Key instance.
205
+ #
206
+ # key = bucket.get('logs/today/1.log') #=>
207
+ # puts key.data #=> 'sasfasfasdf'
208
+ #
209
+ def get(key, headers={})
210
+ key = Key.create(self, key.to_s) unless key.is_a?(Key)
211
+ key.get(headers)
212
+ end
213
+
214
+ # Remove all keys from a bucket.
215
+ # Returns +true+.
216
+ #
217
+ # bucket.clear #=> true
218
+ #
219
+ def clear
220
+ @s3.interface.clear_bucket(@name)
221
+ end
222
+
223
+ # Delete all keys where the 'folder_key' can be interpreted
224
+ # as a 'folder' name.
225
+ # Returns an array of string keys that have been deleted.
226
+ #
227
+ # bucket.keys.map{|key| key.name}.join(', ') #=> 'test, test/2/34, test/3, test1, test1/logs'
228
+ # bucket.delete_folder('test') #=> ['test','test/2/34','test/3']
229
+ #
230
+ def delete_folder(folder, separator='/')
231
+ @s3.interface.delete_folder(@name, folder, separator)
232
+ end
233
+
234
+ # Delete a bucket. Bucket must be empty.
235
+ # If +force+ is set, clears and deletes the bucket.
236
+ # Returns +true+.
237
+ #
238
+ # bucket.delete(true) #=> true
239
+ #
240
+ def delete(force=false)
241
+ force ? @s3.interface.force_delete_bucket(@name) : @s3.interface.delete_bucket(@name)
242
+ end
243
+
244
+ # Return a list of grantees.
245
+ #
246
+ def grantees
247
+ Grantee::grantees(self)
248
+ end
249
+
250
+ end
251
+
252
+
253
+ class Key
254
+ attr_reader :bucket, :name, :last_modified, :e_tag, :size, :storage_class, :owner
255
+ attr_accessor :headers, :meta_headers
256
+ attr_writer :data
257
+
258
+ # Separate Amazon meta headers from other headers
259
+ def self.split_meta(headers) #:nodoc:
260
+ hash = headers.dup
261
+ meta = {}
262
+ hash.each do |key, value|
263
+ if key[/^#{S3Interface::AMAZON_METADATA_PREFIX}/]
264
+ meta[key.gsub(S3Interface::AMAZON_METADATA_PREFIX,'')] = value
265
+ hash.delete(key)
266
+ end
267
+ end
268
+ [hash, meta]
269
+ end
270
+
271
+ def self.add_meta_prefix(meta_headers, prefix=S3Interface::AMAZON_METADATA_PREFIX)
272
+ meta = {}
273
+ meta_headers.each do |meta_header, value|
274
+ if meta_header[/#{prefix}/]
275
+ meta[meta_header] = value
276
+ else
277
+ meta["#{S3Interface::AMAZON_METADATA_PREFIX}#{meta_header}"] = value
278
+ end
279
+ end
280
+ meta
281
+ end
282
+
283
+
284
+ # Create a new Key instance, but do not create the actual key.
285
+ # The +name+ is a +String+.
286
+ # Returns a new Key instance.
287
+ #
288
+ # key = RightAws::S3::Key.create(bucket, 'logs/today/1.log') #=> #<RightAws::S3::Key:0xb7b1e240 ... >
289
+ # key.exists? #=> true | false
290
+ # key.put('Woohoo!') #=> true
291
+ # key.exists? #=> true
292
+ #
293
+ def self.create(bucket, name, data=nil, meta_headers={})
294
+ new(bucket, name, data, {}, meta_headers)
295
+ end
296
+
297
+ # Create a new Key instance, but do not create the actual key.
298
+ # In normal use this method should not be called directly.
299
+ # Use RightAws::S3::Key.create or bucket.key() instead.
300
+ #
301
+ def initialize(bucket, name, data=nil, headers={}, meta_headers={},
302
+ last_modified=nil, e_tag=nil, size=nil, storage_class=nil, owner=nil)
303
+ raise 'Bucket must be a Bucket instance.' unless bucket.is_a?(Bucket)
304
+ @bucket = bucket
305
+ @name = name
306
+ @data = data
307
+ @e_tag = e_tag
308
+ @size = size.to_i
309
+ @storage_class = storage_class
310
+ @owner = owner
311
+ @last_modified = last_modified
312
+ if @last_modified && !@last_modified.is_a?(Time)
313
+ @last_modified = Time.parse(@last_modified)
314
+ end
315
+ @headers, @meta_headers = self.class.split_meta(headers)
316
+ @meta_headers.merge!(meta_headers)
317
+ end
318
+
319
+ # Return key name as a String.
320
+ #
321
+ # key = RightAws::S3::Key.create(bucket, 'logs/today/1.log') #=> #<RightAws::S3::Key:0xb7b1e240 ... >
322
+ # puts key #=> 'logs/today/1.log'
323
+ #
324
+ def to_s
325
+ @name.to_s
326
+ end
327
+
328
+ # Return the full S3 path to this key (bucket/key).
329
+ #
330
+ # key.full_name #=> 'my_awesome_bucket/cool_key'
331
+ #
332
+ def full_name(separator='/')
333
+ "#{@bucket.to_s}#{separator}#{@name}"
334
+ end
335
+
336
+ # Return a public link to a key.
337
+ #
338
+ # key.public_link #=> 'https://s3.amazonaws.com:443/my_awesome_bucket/cool_key'
339
+ #
340
+ def public_link
341
+ params = @bucket.s3.interface.params
342
+ "#{params[:protocol]}://#{params[:server]}:#{params[:port]}/#{full_name('/')}"
343
+ end
344
+
345
+ # Return Key data. Retrieve this data from Amazon if it is the first time call.
346
+ # TODO TRB 6/19/07 What does the above mean? Clarify.
347
+ #
348
+ def data
349
+ get if !@data and exists?
350
+ @data
351
+ end
352
+
353
+ # Retrieve object data and attributes from Amazon.
354
+ # Returns a +String+.
355
+ #
356
+ def get(headers={})
357
+ response = @bucket.s3.interface.get(@bucket.name, @name, headers)
358
+ @data = response[:object]
359
+ @headers, @meta_headers = self.class.split_meta(response[:headers])
360
+ refresh(false)
361
+ @data
362
+ end
363
+
364
+ # Store object data on S3.
365
+ # Parameter +data+ is a +String+ or S3Object instance.
366
+ # Returns +true+.
367
+ #
368
+ # key = RightAws::S3::Key.create(bucket, 'logs/today/1.log')
369
+ # key.data = 'Qwerty'
370
+ # key.put #=> true
371
+ # ...
372
+ # key.put('Olala!') #=> true
373
+ #
374
+ def put(data=nil, perms=nil, headers={})
375
+ headers['x-amz-acl'] = perms if perms
376
+ @data = data || @data
377
+ meta = self.class.add_meta_prefix(@meta_headers)
378
+ @bucket.s3.interface.put(@bucket.name, @name, @data, meta.merge(headers))
379
+ end
380
+
381
+ # Retrieve key info from bucket and update attributes.
382
+ # Refresh meta-headers (by calling +head+ method) if +head+ is set.
383
+ # Returns +true+ if the key exists in bucket and +false+ otherwise.
384
+ #
385
+ # key = RightAws::S3::Key.create(bucket, 'logs/today/1.log')
386
+ # key.e_tag #=> nil
387
+ # key.meta_headers #=> {}
388
+ # key.refresh #=> true
389
+ # key.e_tag #=> '12345678901234567890bf11094484b6'
390
+ # key.meta_headers #=> {"family"=>"qwerty", "name"=>"asdfg"}
391
+ #
392
+ def refresh(head=true)
393
+ new_key = @bucket.key(self)
394
+ @last_modified = new_key.last_modified
395
+ @e_tag = new_key.e_tag
396
+ @size = new_key.size
397
+ @storage_class = new_key.storage_class
398
+ @owner = new_key.owner
399
+ if @last_modified
400
+ self.head
401
+ true
402
+ else
403
+ @headers = @meta_headers = {}
404
+ false
405
+ end
406
+ end
407
+
408
+ # Retrieve meta-headers from S3.
409
+ # Returns +true+.
410
+ #
411
+ # key.meta_headers #=> {"family"=>"qwerty"}
412
+ # key.head #=> true
413
+ # key.meta_headers #=> {"family"=>"qwerty", "name"=>"asdfg"}
414
+ #
415
+ def head
416
+ @headers, @meta_headers = self.class.split_meta(@bucket.s3.interface.head(@bucket, @name))
417
+ true
418
+ end
419
+
420
+ # Check for existence of the key in the given bucket.
421
+ # Returns +true+ or +false+.
422
+ #
423
+ # key = RightAws::S3::Key.create(bucket,'logs/today/1.log')
424
+ # key.exists? #=> false
425
+ # key.put('Woohoo!') #=> true
426
+ # key.exists? #=> true
427
+ #
428
+ def exists?
429
+ @bucket.key(self).last_modified ? true : false
430
+ end
431
+
432
+ # Remove key from bucket.
433
+ # Returns +true+.
434
+ #
435
+ # key.delete #=> true
436
+ #
437
+ def delete
438
+ raise 'Key name must be specified.' if @name.blank?
439
+ @bucket.s3.interface.delete(@bucket, @name)
440
+ end
441
+
442
+ # Return a list of grantees.
443
+ #
444
+ def grantees
445
+ Grantee::grantees(self)
446
+ end
447
+
448
+ end
449
+
450
+
451
+ class Owner
452
+ attr_reader :id, :name
453
+
454
+ def initialize(id, name)
455
+ @id = id
456
+ @name = name
457
+ end
458
+
459
+ # Return Owner name as a +String+.
460
+ def to_s
461
+ @name
462
+ end
463
+ end
464
+
465
+
466
+ # There are 2 ways to set permissions for a bucket or key (called a +thing+ below):
467
+ #
468
+ # 1 . Use +perms+ param to set 'Canned Access Policies' when calling the <tt>bucket.create</tt>,
469
+ # <tt>bucket.put</tt> and <tt>key.put</tt> methods.
470
+ # The +perms+ param can take these values: 'private', 'public-read', 'public-read-write' and
471
+ # 'authenticated-read'.
472
+ # (see http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAccessPolicy.html).
473
+ #
474
+ # bucket = s3.bucket('bucket_for_kd_test_13', true, 'public-read')
475
+ # key.put('Woohoo!','public-read-write' )
476
+ #
477
+ # 2 . Use Grantee instances (the permission is a +String+ or an +Array+ of: 'READ', 'WRITE',
478
+ # 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL'):
479
+ #
480
+ # bucket = s3.bucket('my_awesome_bucket', true)
481
+ # grantee1 = RightAws::S3::Grantee.new(bucket, 'a123b...223c', FULL_CONTROL, :apply)
482
+ # grantee2 = RightAws::S3::Grantee.new(bucket, 'xy3v3...5fhp', [READ, WRITE], :apply)
483
+ #
484
+ # There is only one way to get and to remove permission (via Grantee instances):
485
+ #
486
+ # grantees = bucket.grantees # a list of Grantees that have any access for this bucket
487
+ # grantee1 = RightAws::S3::Grantee.new(bucket, 'a123b...223c')
488
+ # grantee1.perms #=> returns a list of perms for this grantee to that bucket
489
+ # ...
490
+ # grantee1.drop # remove all perms for this grantee
491
+ # grantee2.revoke('WRITE') # revoke write access only
492
+ #
493
+ class Grantee
494
+ # A bucket or a key the grantee has an access to.
495
+ attr_reader :thing
496
+ # Grantee Amazon id.
497
+ attr_reader :id
498
+ # Grantee display name.
499
+ attr_reader :name
500
+ # Array of permissions.
501
+ attr_accessor :perms
502
+
503
+ # Retrieve Owner information and a list of Grantee instances that have
504
+ # a access to this thing (bucket or key).
505
+ #
506
+ # bucket = s3.bucket('my_awesome_bucket', true, 'public-read')
507
+ # ...
508
+ # RightAws::S3::Grantee.owner_and_grantees(bucket) #=> [owner, grantees]
509
+ #
510
+ def self.owner_and_grantees(thing)
511
+ if thing.is_a?(Bucket)
512
+ bucket, key = thing, ''
513
+ else
514
+ bucket, key = thing.bucket, thing
515
+ end
516
+ hash = bucket.s3.interface.get_acl_parse(bucket.to_s, key.to_s)
517
+ owner = Owner.new(hash[:owner][:id], hash[:owner][:display_name])
518
+
519
+ grantees = []
520
+ hash[:grantees].each do |id, params|
521
+ grantees << new(thing, id, params[:permissions], nil, params[:display_name])
522
+ end
523
+ [owner, grantees]
524
+ end
525
+
526
+ # Retrieves a list of Grantees instances that have an access to this thing(bucket or key).
527
+ #
528
+ # bucket = s3.bucket('my_awesome_bucket', true, 'public-read')
529
+ # ...
530
+ # RightAws::S3::Grantee.grantees(bucket) #=> grantees
531
+ #
532
+ def self.grantees(thing)
533
+ owner_and_grantees(thing)[1]
534
+ end
535
+
536
+ def self.put_acl(thing, owner, grantees) #:nodoc:
537
+ if thing.is_a?(Bucket)
538
+ bucket, key = thing, ''
539
+ else
540
+ bucket, key = thing.bucket, thing
541
+ end
542
+ body = "<AccessControlPolicy>" +
543
+ "<Owner>" +
544
+ "<ID>#{owner.id}</ID>" +
545
+ "<DisplayName>#{owner.name}</DisplayName>" +
546
+ "</Owner>" +
547
+ "<AccessControlList>" +
548
+ grantees.map{|grantee| grantee.to_xml}.join +
549
+ "</AccessControlList>" +
550
+ "</AccessControlPolicy>"
551
+ bucket.s3.interface.put_acl(bucket.to_s, key.to_s, body)
552
+ end
553
+
554
+ # Create a new Grantee instance.
555
+ # Grantee +id+ must exist on S3. If +action+ == :refresh, then retrieve
556
+ # permissions from S3 and update @perms. If +action+ == :apply, then apply
557
+ # perms to +thing+ at S3. The default action is :refresh.
558
+ #
559
+ # bucket = s3.bucket('my_awesome_bucket', true, 'public-read')
560
+ # grantee1 = RightAws::S3::Grantee.new(bucket, 'a123b...223c', FULL_CONTROL)
561
+ # ...
562
+ # grantee2 = RightAws::S3::Grantee.new(bucket, 'abcde...asdf', [FULL_CONTROL, READ], :apply)
563
+ #
564
+ #
565
+ def initialize(thing, id, perms=[], action=:refresh, name=nil)
566
+ @thing = thing
567
+ @id = id
568
+ @name = name
569
+ @perms = perms.to_a
570
+ case action
571
+ when :apply; apply
572
+ when :refresh; refresh
573
+ end
574
+ end
575
+
576
+ # Return Grantee type (+String+): "Group" or "CanonicalUser".
577
+ def type
578
+ @id[/^http:/] ? "Group" : "CanonicalUser"
579
+ end
580
+
581
+ # Return a name or an id.
582
+ def to_s
583
+ @name || @id
584
+ end
585
+
586
+ # Add permissions for grantee.
587
+ # Permissions: 'READ', 'WRITE', 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL'.
588
+ # See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/UsingPermissions.html .
589
+ # Returns +true+.
590
+ #
591
+ # grantee.grant('FULL_CONTROL') #=> true
592
+ #
593
+ def grant(permission)
594
+ return true if @perms.include?(permission)
595
+ @perms += permission.to_a
596
+ apply
597
+ end
598
+
599
+ # Revoke permissions for grantee.
600
+ # Permissions: 'READ', 'WRITE', 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL'
601
+ # See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/UsingPermissions.html .
602
+ # Default value is 'FULL_CONTROL'.
603
+ # Returns +true+.
604
+ #
605
+ # grantee.revoke #=> true
606
+ #
607
+ def revoke(permission)
608
+ return true unless @perms.include?(permission)
609
+ @perms -= permission.to_a
610
+ apply
611
+ end
612
+
613
+ # Revoke all permissions for this grantee.
614
+ # Returns +true+.
615
+ #
616
+ # grantee.drop #=> true
617
+ #
618
+ def drop
619
+ @perms = []
620
+ apply
621
+ end
622
+
623
+ # Refresh grantee perms for its +thing+.
624
+ # Returns +true+ if the grantee has perms for this +thing+ or
625
+ # +false+ otherwise, and updates @perms value as a side-effect.
626
+ #
627
+ # grantee.grant('FULL_CONTROL') #=> true
628
+ # grantee.refresh #=> true
629
+ # grantee.drop #=> true
630
+ # grantee.refresh #=> false
631
+ #
632
+ def refresh
633
+ @perms = []
634
+ self.class.grantees(@thing).each do |grantee|
635
+ if @id == grantee.id
636
+ @name = grantee.name
637
+ @perms = grantee.perms
638
+ return true
639
+ end
640
+ end
641
+ false
642
+ end
643
+
644
+ # Apply current grantee @perms to +thing+. This method is called internally by the +grant+
645
+ # and +revoke+ methods. In normal use this method should not
646
+ # be called directly.
647
+ #
648
+ # grantee.perms = ['FULL_CONTROL']
649
+ # grantee.apply #=> true
650
+ #
651
+ def apply
652
+ owner, grantees = self.class.owner_and_grantees(@thing)
653
+ grantees.map! do |grantee|
654
+ grantee.id == @id ? self : grantee
655
+ end
656
+ self.class.put_acl(@thing, owner, grantees)
657
+ end
658
+
659
+ def to_xml # :nodoc:
660
+ id_str = @id[/^http/] ? "<URI>#{@id}</URI>" : "<ID>#{@id}</ID>"
661
+ grants = ''
662
+ @perms.each do |perm|
663
+ grants << "<Grant>" +
664
+ "<Grantee xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
665
+ "xsi:type=\"#{type}\">#{id_str}</Grantee>" +
666
+ "<Permission>#{perm}</Permission>" +
667
+ "</Grant>"
668
+ end
669
+ grants
670
+ end
671
+
672
+ end
673
+
674
+ end
675
+
676
+ # RightAws::S3Generator and RightAws::S3Generator::Bucket methods:
677
+ #
678
+ # s3g = RightAws::S3Generator.new('1...2', 'nx...Y6') #=> #<RightAws::S3Generator:0xb7b5cc94>
679
+ #
680
+ # # List all buckets(method 'GET'):
681
+ # buckets_list = s3g.buckets #=> 'https://s3.amazonaws.com:443/?Signature=Y...D&Expires=1180941864&AWSAccessKeyId=1...2'
682
+ # # Create bucket link (method 'PUT'):
683
+ # bucket = s3g.bucket('my_awesome_bucket') #=> #<RightAws::S3Generator::Bucket:0xb7bcbda8>
684
+ # link_to_create = bucket.create_link(1.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket?Signature=4...D&Expires=1180942132&AWSAccessKeyId=1...2
685
+ # # ... or:
686
+ # bucket = RightAws::S3Generator::Bucket.create(s3g, 'my_awesome_bucket') #=> #<RightAws::S3Generator::Bucket:0xb7bcbda8>
687
+ # link_to_create = bucket.create_link(1.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket?Signature=4...D&Expires=1180942132&AWSAccessKeyId=1...2
688
+ # # ... or:
689
+ # bucket = RightAws::S3Generator::Bucket.new(s3g, 'my_awesome_bucket') #=> #<RightAws::S3Generator::Bucket:0xb7bcbda8>
690
+ # link_to_create = bucket.create_link(1.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket?Signature=4...D&Expires=1180942132&AWSAccessKeyId=1...2
691
+ # # List bucket(method 'GET'):
692
+ # bucket.keys(1.day) #=> https://s3.amazonaws.com:443/my_awesome_bucket?Signature=i...D&Expires=1180942620&AWSAccessKeyId=1...2
693
+ # # Create/put key (method 'PUT'):
694
+ # bucket.put('my_cool_key') #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=q...D&Expires=1180943094&AWSAccessKeyId=1...2
695
+ # # Get key data (method 'GET'):
696
+ # bucket.get('logs/today/1.log', 1.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=h...M%3D&Expires=1180820032&AWSAccessKeyId=1...2
697
+ # # Delete bucket (method 'DELETE'):
698
+ # bucket.delete(2.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket/logs%2Ftoday%2F1.log?Signature=4...D&Expires=1180820032&AWSAccessKeyId=1...2
699
+ #
700
+ # RightAws::S3Generator::Key methods:
701
+ #
702
+ # # Create Key instance:
703
+ # key = RightAws::S3Generator::Key.new(bicket, 'my_cool_key') #=> #<RightAws::S3Generator::Key:0xb7b7394c>
704
+ # # Put key data (method 'PUT'):
705
+ # key.put #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=2...D&Expires=1180943302&AWSAccessKeyId=1...2
706
+ # # Get key data (method 'GET'):
707
+ # key.get #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=a...D&Expires=1180820032&AWSAccessKeyId=1...2
708
+ # # Head key (method 'HEAD'):
709
+ # key.head #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=b...D&Expires=1180820032&AWSAccessKeyId=1...2
710
+ # # Delete key (method 'DELETE'):
711
+ # key.delete #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=x...D&Expires=1180820032&AWSAccessKeyId=1...2
712
+ #
713
+ class S3Generator
714
+ attr_reader :interface
715
+
716
+ def initialize(aws_access_key_id, aws_secret_access_key, params={})
717
+ @interface = S3Interface.new(aws_access_key_id, aws_secret_access_key, params)
718
+ end
719
+
720
+ # Generate link to list all buckets
721
+ #
722
+ # s3.buckets(1.hour)
723
+ #
724
+ def buckets(expires=nil, headers={})
725
+ @interface.list_all_my_buckets_link(expires, headers)
726
+ end
727
+
728
+ # Create new S3LinkBucket instance and generate link to create it at S3.
729
+ #
730
+ # bucket= s3.bucket('my_owesome_bucket')
731
+ #
732
+ def bucket(name, expires=nil, headers={})
733
+ Bucket.create(self, name.to_s)
734
+ end
735
+
736
+ class Bucket
737
+ attr_reader :s3, :name
738
+
739
+ def to_s
740
+ @name
741
+ end
742
+ alias_method :full_name, :to_s
743
+
744
+ # Return a public link to bucket.
745
+ #
746
+ # bucket.public_link #=> 'https://s3.amazonaws.com:443/my_awesome_bucket'
747
+ #
748
+ def public_link
749
+ params = @s3.interface.params
750
+ "#{params[:protocol]}://#{params[:server]}:#{params[:port]}/#{full_name}"
751
+ end
752
+
753
+ # Create new S3LinkBucket instance and generate creation link for it.
754
+ def self.create(s3, name, expires=nil, headers={})
755
+ new(s3, name.to_s)
756
+ end
757
+
758
+ # Create new S3LinkBucket instance.
759
+ def initialize(s3, name)
760
+ @s3, @name = s3, name.to_s
761
+ end
762
+
763
+ # Return a link to create this bucket.
764
+ #
765
+ def create_link(expires=nil, headers={})
766
+ @s3.interface.create_bucket_link(@name, expires, headers)
767
+ end
768
+
769
+ # Generate link to list keys.
770
+ #
771
+ # bucket.keys
772
+ # bucket.keys('prefix'=>'logs')
773
+ #
774
+ def keys(options=nil, expires=nil, headers={})
775
+ @s3.interface.list_bucket_link(@name, options, expires, headers)
776
+ end
777
+
778
+ # Return a S3Generator::Key instance.
779
+ #
780
+ # bucket.key('my_cool_key').get #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=B...D&Expires=1180820032&AWSAccessKeyId=1...2
781
+ # bucket.key('my_cool_key').delete #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=B...D&Expires=1180820098&AWSAccessKeyId=1...2
782
+ #
783
+ def key(name)
784
+ Key.new(self, name)
785
+ end
786
+
787
+ # Generates link to PUT key data.
788
+ #
789
+ # puts bucket.put('logs/today/1.log', 2.hour)
790
+ #
791
+ def put(key, meta_headers={}, expires=nil, headers={})
792
+ meta = RightAws::S3::Key.add_meta_prefix(meta_headers)
793
+ @s3.interface.put_link(@name, key.to_s, nil, expires, meta.merge(headers))
794
+ end
795
+
796
+ # Generate link to GET key data.
797
+ #
798
+ # bucket.get('logs/today/1.log', 1.hour)
799
+ #
800
+ def get(key, expires=nil, headers={})
801
+ @s3.interface.get_link(@name, key.to_s, expires, headers)
802
+ end
803
+
804
+ # Generate link to delete bucket.
805
+ #
806
+ # bucket.delete(2.hour)
807
+ #
808
+ def delete(expires=nil, headers={})
809
+ @s3.interface.delete_bucket_link(@name, expires, headers)
810
+ end
811
+ end
812
+
813
+
814
+ class Key
815
+ attr_reader :bucket, :name
816
+
817
+ def to_s
818
+ @name
819
+ end
820
+
821
+ # Return a full S# name (bucket/key).
822
+ #
823
+ # key.full_name #=> 'my_awesome_bucket/cool_key'
824
+ #
825
+ def full_name(separator='/')
826
+ "#{@bucket.to_s}#{separator}#{@name}"
827
+ end
828
+
829
+ # Return a public link to key.
830
+ #
831
+ # key.public_link #=> 'https://s3.amazonaws.com:443/my_awesome_bucket/cool_key'
832
+ #
833
+ def public_link
834
+ params = @bucket.s3.interface.params
835
+ "#{params[:protocol]}://#{params[:server]}:#{params[:port]}/#{full_name('/')}"
836
+ end
837
+
838
+ def initialize(bucket, name, meta_headers={})
839
+ @bucket = bucket
840
+ @name = name.to_s
841
+ @meta_headers = meta_headers
842
+ raise 'Key name can not be empty.' if @name.blank?
843
+ end
844
+
845
+ # Generate link to PUT key data.
846
+ #
847
+ # puts bucket.put('logs/today/1.log', '123', 2.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket/logs%2Ftoday%2F1.log?Signature=B...D&Expires=1180820032&AWSAccessKeyId=1...2
848
+ #
849
+ def put(expires=nil, headers={})
850
+ @bucket.put(@name.to_s, @meta_headers, expires, headers)
851
+ end
852
+
853
+ # Generate link to GET key data.
854
+ #
855
+ # 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
856
+ #
857
+ def get(expires=nil, headers={})
858
+ @bucket.s3.interface.get_link(@bucket.to_s, @name, expires, headers)
859
+ end
860
+
861
+ # Generate link to delete key.
862
+ #
863
+ # bucket.delete(2.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket/logs%2Ftoday%2F1.log?Signature=4...D&Expires=1180820032&AWSAccessKeyId=1...2
864
+ #
865
+ def delete(expires=nil, headers={})
866
+ @bucket.s3.interface.delete_link(@bucket.to_s, @name, expires, headers)
867
+ end
868
+
869
+ # Generate link to head key.
870
+ #
871
+ # bucket.head(2.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket/logs%2Ftoday%2F1.log?Signature=4...D&Expires=1180820032&AWSAccessKeyId=1...2
872
+ #
873
+ def head(expires=nil, headers={})
874
+ @bucket.s3.interface.head_link(@bucket.to_s, @name, expires, headers)
875
+ end
876
+ end
877
+ end
878
+
879
+ end