s3lib 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +24 -0
- data/VERSION.yml +5 -0
- data/bin/s3lib +15 -0
- data/bin/s3sh_as +15 -0
- data/github-test.rb +22 -0
- data/lib/acl.rb +134 -0
- data/lib/acl_access.rb +20 -0
- data/lib/acl_creating_a_grant_recipe.rb +95 -0
- data/lib/acl_reading_acl_recipe.rb +59 -0
- data/lib/acl_refreshing_cached_grants_recipe.rb +54 -0
- data/lib/bucket.rb +116 -0
- data/lib/bucket_before_refactoring.rb +120 -0
- data/lib/bucket_create.rb +39 -0
- data/lib/bucket_find.rb +41 -0
- data/lib/bucket_with_acl_mixin.rb +103 -0
- data/lib/error_handling.rb +12 -0
- data/lib/grant.rb +107 -0
- data/lib/grant_creating_a_grant_recipe.rb +103 -0
- data/lib/grant_reading_acl_recipe.rb +51 -0
- data/lib/object.rb +144 -0
- data/lib/object_from_bucket_test.rb +18 -0
- data/lib/object_take1.rb +150 -0
- data/lib/object_with_acl_mixin.rb +131 -0
- data/lib/put_with_curl_test.rb +39 -0
- data/lib/s3_authenticator.rb +155 -0
- data/lib/s3_authenticator_dev.rb +117 -0
- data/lib/s3_authenticator_dev_private.rb +40 -0
- data/lib/s3_errors.rb +58 -0
- data/lib/s3lib.rb +10 -0
- data/lib/s3lib_with_mixin.rb +11 -0
- data/lib/service.rb +24 -0
- data/lib/service_dev.rb +36 -0
- data/s3lib.gemspec +74 -0
- data/sample_usage.rb +45 -0
- data/test/acl_test.rb +89 -0
- data/test/amazon_headers_test.rb +87 -0
- data/test/canonical_resource_test.rb +53 -0
- data/test/canonical_string_tests.rb +73 -0
- data/test/first_test.rb +34 -0
- data/test/first_test_private.rb +55 -0
- data/test/full_test.rb +84 -0
- data/test/s3_authenticator_test.rb +291 -0
- metadata +109 -0
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
task :default => :test
|
5
|
+
|
6
|
+
Rake::TestTask.new do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.test_files = FileList['test/*_test.rb']
|
9
|
+
# t.verbose = true
|
10
|
+
end
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'jeweler'
|
14
|
+
Jeweler::Tasks.new do |gemspec|
|
15
|
+
gemspec.name = "s3lib"
|
16
|
+
gemspec.summary = "An Amazon S3 interface library used as an example in The S3 Cookbook (http://thes3cookbook.com)"
|
17
|
+
gemspec.email = 'scott@scottpatten.ca'
|
18
|
+
gemspec.homepage = 'http://thes3cookbook.com'
|
19
|
+
gemspec.description = "This library forms the basis for building a library to talk to Amazon S3 using Ruby. It is used as an example of how to build an Amazon S3 interface in The S3 Cookbook (http://thes3cookbook.com)"
|
20
|
+
gemspec.authors = ["Scott Patten"]
|
21
|
+
end
|
22
|
+
rescue LoadError
|
23
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
24
|
+
end
|
data/VERSION.yml
ADDED
data/bin/s3lib
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
# Set the S3 keys to the right account if the account name was given in the command line
|
6
|
+
unless ARGV.empty?
|
7
|
+
s3_keys = YAML::load_file(File.join(ENV['HOME'], '.s3_keys.yml'))
|
8
|
+
if s3_keys.has_key?(ARGV[0])
|
9
|
+
keys = s3_keys[ARGV[0]]
|
10
|
+
ENV['AMAZON_ACCESS_KEY_ID'] = keys['amazon_access_key_id']
|
11
|
+
ENV['AMAZON_SECRET_ACCESS_KEY'] = keys['amazon_secret_access_key']
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
exec("irb -r s3lib")
|
data/bin/s3sh_as
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
# Set the S3 keys to the right account if the account name was given in the command line
|
6
|
+
unless ARGV.empty?
|
7
|
+
s3_keys = YAML::load_file(File.join(ENV['HOME'], '.s3_keys.yml'))
|
8
|
+
if s3_keys.has_key?(ARGV[0])
|
9
|
+
keys = s3_keys[ARGV[0]]
|
10
|
+
ENV['AMAZON_ACCESS_KEY_ID'] = keys['amazon_access_key_id']
|
11
|
+
ENV['AMAZON_SECRET_ACCESS_KEY'] = keys['amazon_secret_access_key']
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
exec("s3sh")
|
data/github-test.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
if ARGV.size < 1
|
5
|
+
puts "Usage: github-test.rb my-project.gemspec"
|
6
|
+
exit
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rubygems/specification'
|
10
|
+
data = File.read(ARGV[0])
|
11
|
+
spec = nil
|
12
|
+
|
13
|
+
if data !~ %r{!ruby/object:Gem::Specification}
|
14
|
+
Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
|
15
|
+
else
|
16
|
+
spec = YAML.load(data)
|
17
|
+
end
|
18
|
+
|
19
|
+
spec.validate
|
20
|
+
|
21
|
+
puts spec
|
22
|
+
puts "OK"
|
data/lib/acl.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
module S3Lib
|
2
|
+
|
3
|
+
class Acl
|
4
|
+
|
5
|
+
attr_reader :xml, :parent, :url
|
6
|
+
|
7
|
+
def initialize(parent_or_url)
|
8
|
+
if parent_or_url.respond_to?(:url)
|
9
|
+
@parent = parent_or_url
|
10
|
+
@url = @parent.url.sub(/\/\Z/,'') + '?acl'
|
11
|
+
else
|
12
|
+
@url = parent_or_url.sub(/\/\Z/,'').sub(/\?acl/, '') + '?acl'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def grants(params = {})
|
17
|
+
refresh_grants if params[:refresh]
|
18
|
+
@grants || get_grants
|
19
|
+
end
|
20
|
+
|
21
|
+
def clear_grants
|
22
|
+
@grants = []
|
23
|
+
set_grants
|
24
|
+
end
|
25
|
+
|
26
|
+
# permission must be one of :read, :write, :read_acl, :write_acl or :full_control
|
27
|
+
# The grantee Hash should look like this:
|
28
|
+
# {:type => :canonical|:email|:all_s3|:public,
|
29
|
+
# :grantee => canonical_user_id | email_address}
|
30
|
+
#
|
31
|
+
# The :grantee element of the hash is only required (and meaningful)
|
32
|
+
# for :canonical and :email Grants
|
33
|
+
#
|
34
|
+
# Some examples:
|
35
|
+
# Add public read access to an object:
|
36
|
+
# add_grant(:read, :type => :public)
|
37
|
+
#
|
38
|
+
# Give write access to a user with email of 'test@example.com'
|
39
|
+
# add_grant(:write, :type => :email, :grantee => 'test@example.com')
|
40
|
+
#
|
41
|
+
# Give full control to user with canonical ID of 1234567890
|
42
|
+
# add_grant(:full_control, :type => :canonical, :grantee => 1234567890)
|
43
|
+
#
|
44
|
+
# Note that you still have to PUT your changes to the server.
|
45
|
+
# Do this using set_grants or use the bang version of add_grant
|
46
|
+
#
|
47
|
+
# add_grant(:read, :type => :public)
|
48
|
+
# set_grants()
|
49
|
+
#
|
50
|
+
# add_grant!(:read, :type => :public)
|
51
|
+
def add_grant(permission, grantee)
|
52
|
+
grants.push(S3Lib::Grant.new(permission, grantee))
|
53
|
+
end
|
54
|
+
|
55
|
+
# Add a grant and PUT it to the server right away
|
56
|
+
def add_grant!(permission, grantee)
|
57
|
+
add_grant(permission, grantee)
|
58
|
+
set_grants
|
59
|
+
end
|
60
|
+
|
61
|
+
def remove_grant(grant_num)
|
62
|
+
grants.delete_at(grant_num)
|
63
|
+
refresh_grants
|
64
|
+
end
|
65
|
+
|
66
|
+
def refresh_grants
|
67
|
+
get_grants
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_grants
|
71
|
+
Acl.acl_request(:put, @url, :body => to_xml)
|
72
|
+
refresh_grants
|
73
|
+
end
|
74
|
+
|
75
|
+
def owner
|
76
|
+
get_grants unless @xml
|
77
|
+
@xml.elements['Owner'].elements['ID'].text
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_xml
|
81
|
+
builder = Builder::XmlMarkup.new(:indent => 2)
|
82
|
+
xml = builder.AccessControlPolicy('xmlns' => 'http://s3.amazonaws.com/doc/2006-03-01/') do
|
83
|
+
builder.Owner do
|
84
|
+
builder.ID(owner)
|
85
|
+
end
|
86
|
+
builder.AccessControlList do
|
87
|
+
grants.each do |grant|
|
88
|
+
builder << grant.to_xml
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def inspect
|
95
|
+
grants.collect do |grant|
|
96
|
+
grant.inspect
|
97
|
+
end.join("\n")
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def get_grants
|
103
|
+
response = Acl.acl_request(:get, @url)
|
104
|
+
@xml = REXML::Document.new(response).root
|
105
|
+
@grants = REXML::XPath.match(@xml, '//Grant').collect do |grant|
|
106
|
+
grantee = grant.elements['Grantee']
|
107
|
+
permission = grant.elements['Permission'].text
|
108
|
+
S3Lib::Grant.new(permission, grantee)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.acl_request(verb, url, options = {})
|
113
|
+
begin
|
114
|
+
if verb == :put
|
115
|
+
options = {'content-type' => 'text/xml'}.merge(options) # Make sure content-type is set for :put
|
116
|
+
end
|
117
|
+
response = S3Lib.request(verb, url, options)
|
118
|
+
rescue S3Lib::S3ResponseError => error
|
119
|
+
puts "Error of type #{error.amazon_error_type}"
|
120
|
+
case error.amazon_error_type
|
121
|
+
when 'NoSuchBucket': raise S3Lib::BucketNotFoundError.new("The bucket '#{bucket}' does not exist.", error.io, error.s3requester)
|
122
|
+
when 'NotSignedUp': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by somebody else", error.io, error.s3requester)
|
123
|
+
when 'AccessDenied': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by someone else.", error.io, error.s3requester)
|
124
|
+
when 'MalformedACLError': raise S3Lib::MalformedACLError.new("Your ACL was malformed.", error.io, error.s3requester)
|
125
|
+
else # Re-raise the error if it's not one of the above
|
126
|
+
raise
|
127
|
+
end
|
128
|
+
end
|
129
|
+
response
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
data/lib/acl_access.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
module S3Lib
|
2
|
+
|
3
|
+
class Acl
|
4
|
+
|
5
|
+
attr_reader :xml, :url
|
6
|
+
|
7
|
+
def initialize(url)
|
8
|
+
@url = url.sub(/\/\Z/,'').sub(/\?acl/, '') + '?acl'
|
9
|
+
end
|
10
|
+
|
11
|
+
def grants(params = {})
|
12
|
+
refresh_grants if params[:refresh]
|
13
|
+
@grants || get_grants
|
14
|
+
end
|
15
|
+
|
16
|
+
# permission must be one of :read, :write, :read_acl, :write_acl or :full_control
|
17
|
+
# The grantee Hash should look like this:
|
18
|
+
# {:type => :canonical|:email|:all_s3|:public,
|
19
|
+
# :grantee => canonical_user_id | email_address}
|
20
|
+
#
|
21
|
+
# The :grantee element of the hash is only required (and meaningful)
|
22
|
+
# for :canonical and :email Grants
|
23
|
+
def add_grant(permission, grantee)
|
24
|
+
grants.push(S3Lib::Grant.new(permission, grantee))
|
25
|
+
end
|
26
|
+
|
27
|
+
# Add a grant and PUT it to the server right away
|
28
|
+
def add_grant!(permission, grantee)
|
29
|
+
add_grant(permission, grantee)
|
30
|
+
set_grants
|
31
|
+
end
|
32
|
+
|
33
|
+
def refresh_grants
|
34
|
+
get_grants
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_grants
|
38
|
+
Acl.acl_request(:put, @url, :body => to_xml)
|
39
|
+
refresh_grants
|
40
|
+
end
|
41
|
+
|
42
|
+
def owner
|
43
|
+
get_grants unless @xml
|
44
|
+
@xml.elements['Owner'].elements['ID'].text
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_xml
|
48
|
+
builder = Builder::XmlMarkup.new(:indent => 2)
|
49
|
+
xml = builder.AccessControlPolicy('xmlns' => 'http://s3.amazonaws.com/doc/2006-03-01/') do
|
50
|
+
builder.Owner do
|
51
|
+
builder.ID(owner)
|
52
|
+
end
|
53
|
+
builder.AccessControlList do
|
54
|
+
grants.each do |grant|
|
55
|
+
builder << grant.to_xml
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def get_grants
|
64
|
+
response = Acl.acl_request(:get, @url)
|
65
|
+
@xml = REXML::Document.new(response).root
|
66
|
+
@grants = REXML::XPath.match(@xml, '//Grant').collect do |grant|
|
67
|
+
grantee = grant.elements['Grantee']
|
68
|
+
permission = grant.elements['Permission'].text
|
69
|
+
S3Lib::Grant.new(permission, grantee)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.acl_request(verb, url, options = {})
|
74
|
+
begin
|
75
|
+
if verb == :put
|
76
|
+
options = {'content-type' => 'text/xml'}.merge(options) # Make sure content-type is set for :put
|
77
|
+
end
|
78
|
+
response = S3Lib.request(verb, url, options)
|
79
|
+
rescue S3Lib::S3ResponseError => error
|
80
|
+
puts "Error of type #{error.amazon_error_type}"
|
81
|
+
case error.amazon_error_type
|
82
|
+
when 'NoSuchBucket': raise S3Lib::BucketNotFoundError.new("The bucket '#{bucket}' does not exist.", error.io, error.s3requester)
|
83
|
+
when 'NotSignedUp': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by somebody else", error.io, error.s3requester)
|
84
|
+
when 'AccessDenied': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by someone else.", error.io, error.s3requester)
|
85
|
+
when 'MalformedACLError': raise S3Lib::MalformedACLError.new("Your ACL was malformed.", error.io, error.s3requester)
|
86
|
+
else # Re-raise the error if it's not one of the above
|
87
|
+
raise
|
88
|
+
end
|
89
|
+
end
|
90
|
+
response
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module S3Lib
|
2
|
+
require 'rexml/document'
|
3
|
+
require File.join(File.dirname(__FILE__), 'grant_reading_acl_recipe.rb')
|
4
|
+
require File.join(File.dirname(__FILE__), 's3_authenticator.rb')
|
5
|
+
|
6
|
+
class Acl
|
7
|
+
|
8
|
+
attr_reader :xml, :url
|
9
|
+
|
10
|
+
def initialize(url)
|
11
|
+
@url = url.sub(/\/\Z/,'').sub(/\?acl/, '') + '?acl'
|
12
|
+
end
|
13
|
+
|
14
|
+
def grants(params = {})
|
15
|
+
refresh_grants if params[:refresh]
|
16
|
+
@grants || get_grants
|
17
|
+
end
|
18
|
+
|
19
|
+
def clear_grants
|
20
|
+
@grants = []
|
21
|
+
set_grants
|
22
|
+
end
|
23
|
+
|
24
|
+
def refresh_grants
|
25
|
+
get_grants
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def get_grants
|
31
|
+
response = Acl.acl_request(:get, @url)
|
32
|
+
@xml = REXML::Document.new(response).root
|
33
|
+
@grants = REXML::XPath.match(@xml, '//Grant').collect do |grant|
|
34
|
+
grantee = grant.elements['Grantee']
|
35
|
+
permission = grant.elements['Permission'].text
|
36
|
+
S3Lib::Grant.new(permission, grantee)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.acl_request(verb, url, options = {})
|
41
|
+
begin
|
42
|
+
response = S3Lib.request(verb, url, options)
|
43
|
+
rescue S3Lib::S3ResponseError => error
|
44
|
+
puts "Error of type #{error.amazon_error_type}"
|
45
|
+
case error.amazon_error_type
|
46
|
+
when 'NoSuchBucket': raise S3Lib::BucketNotFoundError.new("The bucket '#{bucket}' does not exist.", error.io, error.s3requester)
|
47
|
+
when 'NotSignedUp': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by somebody else", error.io, error.s3requester)
|
48
|
+
when 'AccessDenied': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by someone else.", error.io, error.s3requester)
|
49
|
+
when 'MalformedACLError': raise S3Lib::MalformedACLError.new("Your ACL was malformed.", error.io, error.s3requester)
|
50
|
+
else # Re-raise the error if it's not one of the above
|
51
|
+
raise
|
52
|
+
end
|
53
|
+
end
|
54
|
+
response
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module S3Lib
|
2
|
+
require 'rexml/document'
|
3
|
+
require File.join(File.dirname(__FILE__), 'grant_reading_acl_recipe.rb')
|
4
|
+
require File.join(File.dirname(__FILE__), 's3_authenticator.rb')
|
5
|
+
|
6
|
+
class Acl
|
7
|
+
|
8
|
+
attr_reader :xml, :url
|
9
|
+
|
10
|
+
def initialize(url)
|
11
|
+
@url = url.sub(/\/\Z/,'').sub(/\?acl/, '') + '?acl'
|
12
|
+
end
|
13
|
+
|
14
|
+
def grants(params = {})
|
15
|
+
refresh_grants if params[:refresh]
|
16
|
+
@grants || get_grants
|
17
|
+
end
|
18
|
+
|
19
|
+
def refresh_grants
|
20
|
+
get_grants
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def get_grants
|
26
|
+
response = Acl.acl_request(:get, @url)
|
27
|
+
@xml = REXML::Document.new(response).root
|
28
|
+
@grants = REXML::XPath.match(@xml, '//Grant').collect do |grant|
|
29
|
+
grantee = grant.elements['Grantee']
|
30
|
+
permission = grant.elements['Permission'].text
|
31
|
+
S3Lib::Grant.new(permission, grantee)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.acl_request(verb, url, options = {})
|
36
|
+
begin
|
37
|
+
response = S3Lib.request(verb, url, options)
|
38
|
+
rescue S3Lib::S3ResponseError => error
|
39
|
+
puts "Error of type #{error.amazon_error_type}"
|
40
|
+
case error.amazon_error_type
|
41
|
+
when 'NoSuchBucket': raise S3Lib::BucketNotFoundError.new("The bucket '#{bucket}' does not exist.", error.io, error.s3requester)
|
42
|
+
when 'NotSignedUp': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by somebody else", error.io, error.s3requester)
|
43
|
+
when 'AccessDenied': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by someone else.", error.io, error.s3requester)
|
44
|
+
when 'MalformedACLError': raise S3Lib::MalformedACLError.new("Your ACL was malformed.", error.io, error.s3requester)
|
45
|
+
else # Re-raise the error if it's not one of the above
|
46
|
+
raise
|
47
|
+
end
|
48
|
+
end
|
49
|
+
response
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
data/lib/bucket.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# bucket.rb
|
2
|
+
module S3Lib
|
3
|
+
|
4
|
+
class Bucket
|
5
|
+
|
6
|
+
attr_reader :name, :xml, :prefix, :marker, :max_keys
|
7
|
+
|
8
|
+
def self.create(name, params = {})
|
9
|
+
params['x-amz-acl'] = params.delete(:access) if params[:access] # translate from :access to 'x-amz-acl'
|
10
|
+
response = self.bucket_request(:put, name, params)
|
11
|
+
response.status[0] == "200" ? true : false
|
12
|
+
end
|
13
|
+
|
14
|
+
# passing :force => true will cause the bucket to be deleted even if it is not empty.
|
15
|
+
def self.delete(name, params = {})
|
16
|
+
if params.delete(:force)
|
17
|
+
self.delete_all(name, params)
|
18
|
+
end
|
19
|
+
response = self.bucket_request(:delete, name, params)
|
20
|
+
end
|
21
|
+
|
22
|
+
def delete(params = {})
|
23
|
+
self.class.delete(@name, @params.merge(params))
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.delete_all(name, params = {})
|
27
|
+
bucket = Bucket.find(name, params)
|
28
|
+
bucket.delete_all
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete_all
|
32
|
+
objects.each do |object|
|
33
|
+
object.delete
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.find(name, params = {})
|
38
|
+
response = self.bucket_request(:get, name, params)
|
39
|
+
doc = REXML::Document.new(response).root
|
40
|
+
Bucket.new(doc, params)
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(doc, params = {})
|
44
|
+
@xml = doc
|
45
|
+
@params = params
|
46
|
+
# The XML has to have a name, but Service::buckets doesn't return
|
47
|
+
# MaxKeys, Prefix or Marker elements
|
48
|
+
@name = @xml.elements['Name'].text
|
49
|
+
@max_keys = @xml.elements['MaxKeys'].text.to_i if @xml.elements['MaxKeys']
|
50
|
+
@prefix = @xml.elements['Prefix'].text if @xml.elements['Prefix']
|
51
|
+
@marker = @xml.elements['Marker'].text if @xml.elements['Marker']
|
52
|
+
end
|
53
|
+
|
54
|
+
def is_truncated?
|
55
|
+
@xml.elements['IsTruncated'].text == 'true'
|
56
|
+
end
|
57
|
+
|
58
|
+
def objects(params = {})
|
59
|
+
refresh if params[:refresh]
|
60
|
+
@objects || get_objects
|
61
|
+
end
|
62
|
+
|
63
|
+
def refresh
|
64
|
+
refreshed_bucket = Bucket.find(@name, @params)
|
65
|
+
@xml = refreshed_bucket.xml
|
66
|
+
@objects = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def refresh_acl
|
70
|
+
get_acl
|
71
|
+
end
|
72
|
+
|
73
|
+
def url
|
74
|
+
@name
|
75
|
+
end
|
76
|
+
|
77
|
+
# access an object in the bucket by key name
|
78
|
+
def [](key)
|
79
|
+
objects.detect{|object| object.key == key}
|
80
|
+
end
|
81
|
+
|
82
|
+
def acl(params = {})
|
83
|
+
refresh_acl if params[:refresh]
|
84
|
+
@acl || get_acl
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def self.bucket_request(verb, name, params = {})
|
90
|
+
begin
|
91
|
+
response = S3Lib.request(verb, name, params)
|
92
|
+
rescue S3Lib::S3ResponseError => error
|
93
|
+
case error.amazon_error_type
|
94
|
+
when "NoSuchBucket": raise S3Lib::BucketNotFoundError.new("The bucket '#{name}' does not exist.", error.io, error.s3requester)
|
95
|
+
when "NotSignedUp": raise S3Lib::NotYourBucketError.new("The bucket '#{name}' is owned by someone else.", error.io, error.s3requester)
|
96
|
+
when "BucketNotEmpty": raise S3Lib::BucketNotEmptyError.new("The bucket '#{name}' is not empty, so you can't delete it.\nTry using Bucket.delete_all('#{name}') first, or Bucket.delete('#{name}', :force => true).", error.io, error.s3requester)
|
97
|
+
else # Re-raise the error if it's not one of the above
|
98
|
+
raise
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def get_objects
|
104
|
+
@objects = REXML::XPath.match(@xml, '//Contents').collect do |object|
|
105
|
+
key = object.elements['Key'].text
|
106
|
+
S3Lib::S3Object.new(self, key, :lazy_load => true)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def get_acl
|
111
|
+
@acl = Acl.new(self)
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# bucket.rb
|
2
|
+
require File.join(File.dirname(__FILE__), 's3_authenticator')
|
3
|
+
require 'rexml/document'
|
4
|
+
|
5
|
+
module S3Lib
|
6
|
+
|
7
|
+
class NotYourBucketError < S3Lib::S3ResponseError
|
8
|
+
end
|
9
|
+
|
10
|
+
class BucketNotFoundError < S3Lib::S3ResponseError
|
11
|
+
end
|
12
|
+
|
13
|
+
class BucketNotEmptyError < S3Lib::S3ResponseError
|
14
|
+
end
|
15
|
+
|
16
|
+
class Bucket
|
17
|
+
|
18
|
+
attr_reader :name, :xml, :prefix, :marker, :max_keys
|
19
|
+
|
20
|
+
def self.create(name, params = {})
|
21
|
+
params['x-amz-acl'] = params.delete(:access) if params[:access] # translate from :access to 'x-amz-acl'
|
22
|
+
begin
|
23
|
+
response = S3Lib.request(:put, name, params)
|
24
|
+
rescue OpenURI::HTTPError => error
|
25
|
+
if error.amazon_error_type == "BucketAlreadyExists"
|
26
|
+
S3Lib::NotYourBucketError.new("The bucket '#{name}' is already owned by somebody else", error.io, error.s3requester)
|
27
|
+
else
|
28
|
+
raise # re-raise the exception if it's not a BucketAlreadyExists error
|
29
|
+
end
|
30
|
+
end
|
31
|
+
response.status[0] == "200" ? true : false
|
32
|
+
end
|
33
|
+
|
34
|
+
# passing :force => true will cause the bucket to be deleted even if it is not empty.
|
35
|
+
def self.delete(name, params = {})
|
36
|
+
if params.delete(:force)
|
37
|
+
self.delete_all(name, params)
|
38
|
+
end
|
39
|
+
begin
|
40
|
+
response = S3Lib.request(:delete, name, params)
|
41
|
+
rescue S3Lib::S3ResponseError => error
|
42
|
+
case error.amazon_error_type
|
43
|
+
when "NoSuchBucket": raise S3Lib::BucketNotFoundError.new("The bucket '#{name}' does not exist.", error.io, error.s3requester)
|
44
|
+
when "NotSignedUp": raise S3Lib::NotYourBucketError.new("The bucket '#{name}' is not owned by you.", error.io, error.s3requester)
|
45
|
+
when "BucketNotEmpty": raise S3Lib::BucketNotEmptyError.new("The bucket '#{name}' is not empty, so you can't delete it.\nTry using Bucket.delete_all('#{name}') first, or Bucket.delete('#{name}', :force => true).", error.io, error.s3requester)
|
46
|
+
else # Re-raise the error if it's not one of the above
|
47
|
+
raise
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete(params = {})
|
53
|
+
self.class.delete(@name, @params.merge(params))
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.delete_all(name, params = {})
|
57
|
+
bucket = Bucket.find(name, params)
|
58
|
+
bucket.delete_all
|
59
|
+
end
|
60
|
+
|
61
|
+
def delete_all
|
62
|
+
objects.each do |object|
|
63
|
+
object.delete
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Errors for find
|
68
|
+
# Trying to find a bucket that doesn't exist will raise a NoSuchBucket error
|
69
|
+
# Trying to find a bucket that you don't have access to will raise a NotSignedUp error
|
70
|
+
def self.find(name, params = {})
|
71
|
+
begin
|
72
|
+
response = S3Lib.request(:get, name)
|
73
|
+
rescue S3Lib::S3ResponseError => error
|
74
|
+
case error.amazon_error_type
|
75
|
+
when "NoSuchBucket": raise S3Lib::BucketNotFoundError.new("The bucket '#{name}' does not exist.", error.io, error.s3requester)
|
76
|
+
when "NotSignedUp": raise S3Lib::NotYourBucketError.new("The bucket '#{name}' is not owned by you", error.io, error.s3requester)
|
77
|
+
else # Re-raise the error if it's not one of the above
|
78
|
+
raise
|
79
|
+
end
|
80
|
+
end
|
81
|
+
doc = REXML::Document.new(response)
|
82
|
+
Bucket.new(doc, params)
|
83
|
+
end
|
84
|
+
|
85
|
+
def initialize(doc, params = {})
|
86
|
+
@xml = doc.root
|
87
|
+
@params = params
|
88
|
+
@name = @xml.elements['Name'].text
|
89
|
+
@max_keys = @xml.elements['MaxKeys'].text.to_i
|
90
|
+
@prefix = @xml.elements['Prefix'].text
|
91
|
+
@marker = @xml.elements['Marker'].text
|
92
|
+
end
|
93
|
+
|
94
|
+
def is_truncated?
|
95
|
+
@xml.elements['IsTruncated'].text == 'true'
|
96
|
+
end
|
97
|
+
|
98
|
+
def objects(params = {})
|
99
|
+
refresh if params[:refresh]
|
100
|
+
@objects || get_objects
|
101
|
+
end
|
102
|
+
|
103
|
+
def refresh
|
104
|
+
refreshed_bucket = Bucket.find(@name, @params)
|
105
|
+
@xml = refreshed_bucket.xml
|
106
|
+
@objects = nil
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def get_objects
|
112
|
+
@objects = REXML::XPath.match(@xml, '//Contents').collect do |object|
|
113
|
+
key = object.elements['Key'].text
|
114
|
+
S3Lib::S3Object.new(self, key, :lazy_load => true)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|