right_aws 1.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.
@@ -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