s3lib 0.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/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
|