s3 0.2.4 → 0.2.5
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/.gitignore +1 -1
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/lib/s3.rb +1 -0
- data/lib/s3/bucket.rb +26 -21
- data/lib/s3/connection.rb +49 -43
- data/lib/s3/exceptions.rb +12 -10
- data/lib/s3/object.rb +36 -19
- data/lib/s3/service.rb +28 -23
- data/lib/s3/signature.rb +47 -21
- data/test/bucket_test.rb +57 -55
- data/test/connection_test.rb +20 -19
- data/test/object_test.rb +23 -24
- data/test/service_test.rb +30 -29
- data/test/signature_test.rb +8 -8
- data/test/test_helper.rb +4 -8
- metadata +9 -9
data/.gitignore
CHANGED
data/Rakefile
CHANGED
@@ -8,12 +8,13 @@ begin
|
|
8
8
|
Jeweler::Tasks.new do |gem|
|
9
9
|
gem.name = "s3"
|
10
10
|
gem.summary = %Q{Library for accessing S3 objects and buckets, with command line tool}
|
11
|
+
gem.description = %Q{S3 library provides access to Amazon's Simple Storage Service. It supports both: European and US buckets through REST API.}
|
11
12
|
gem.email = "qoobaa@gmail.com"
|
12
13
|
gem.homepage = "http://jah.pl/projects/s3.html"
|
13
14
|
gem.authors = ["Jakub Kuźma", "Mirosław Boruta"]
|
14
15
|
gem.add_dependency "trollop", ">=1.14"
|
15
16
|
gem.add_development_dependency "test-unit", ">= 2.0"
|
16
|
-
gem.add_development_dependency "
|
17
|
+
gem.add_development_dependency "mocha"
|
17
18
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
19
|
end
|
19
20
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.5
|
data/lib/s3.rb
CHANGED
data/lib/s3/bucket.rb
CHANGED
@@ -26,15 +26,15 @@ module S3
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
# Compares the bucket with other bucket. Returns true if the
|
30
|
-
# of the
|
31
|
-
#
|
29
|
+
# Compares the bucket with other bucket. Returns true if the names
|
30
|
+
# of the buckets are the same, and both have the same services
|
31
|
+
# (see Service equality)
|
32
32
|
def ==(other)
|
33
33
|
self.name == other.name and self.service == other.service
|
34
34
|
end
|
35
35
|
|
36
|
-
# Similar to retrieve, but catches NoSuchBucket
|
37
|
-
# returns false instead.
|
36
|
+
# Similar to retrieve, but catches S3::Error::NoSuchBucket
|
37
|
+
# exceptions and returns false instead.
|
38
38
|
def exists?
|
39
39
|
retrieve
|
40
40
|
true
|
@@ -42,9 +42,9 @@ module S3
|
|
42
42
|
false
|
43
43
|
end
|
44
44
|
|
45
|
-
# Destroys given bucket. Raises an BucketNotEmpty
|
46
|
-
# bucket is not empty. You can destroy non-empty
|
47
|
-
# true (to force destroy)
|
45
|
+
# Destroys given bucket. Raises an S3::Error::BucketNotEmpty
|
46
|
+
# exception if the bucket is not empty. You can destroy non-empty
|
47
|
+
# bucket passing true (to force destroy)
|
48
48
|
def destroy(force = false)
|
49
49
|
delete_bucket
|
50
50
|
true
|
@@ -58,33 +58,32 @@ module S3
|
|
58
58
|
end
|
59
59
|
|
60
60
|
# Saves the newly built bucket. Optionally you can pass location
|
61
|
-
# of the bucket (
|
61
|
+
# of the bucket (<tt>:eu</tt> or <tt>:us</tt>)
|
62
62
|
def save(location = nil)
|
63
63
|
create_bucket_configuration(location)
|
64
64
|
true
|
65
65
|
end
|
66
66
|
|
67
|
-
# Returns true if the name of the bucket can be used like VHOST
|
67
|
+
# Returns true if the name of the bucket can be used like +VHOST+
|
68
68
|
# name. If the bucket contains characters like underscore it can't
|
69
|
-
# be used as VHOST (e.g. bucket_name.s3.amazonaws.com)
|
69
|
+
# be used as +VHOST+ (e.g. <tt>bucket_name.s3.amazonaws.com</tt>)
|
70
70
|
def vhost?
|
71
71
|
"#@name.#{HOST}" =~ /\A#{URI::REGEXP::PATTERN::HOSTNAME}\Z/
|
72
72
|
end
|
73
73
|
|
74
|
-
# Returns host name of the bucket according (see vhost?)
|
74
|
+
# Returns host name of the bucket according (see #vhost? method)
|
75
75
|
def host
|
76
76
|
vhost? ? "#@name.#{HOST}" : "#{HOST}"
|
77
77
|
end
|
78
78
|
|
79
|
-
# Returns path prefix for non VHOST bucket. Path prefix is used
|
80
|
-
# instead of VHOST name,
|
81
|
-
# e.g. "bucket_name/"
|
79
|
+
# Returns path prefix for non +VHOST+ bucket. Path prefix is used
|
80
|
+
# instead of +VHOST+ name, e.g. "bucket_name/"
|
82
81
|
def path_prefix
|
83
82
|
vhost? ? "" : "#@name/"
|
84
83
|
end
|
85
84
|
|
86
85
|
# Returns the objects in the bucket and caches the result (see
|
87
|
-
# reload).
|
86
|
+
# #reload method).
|
88
87
|
def objects(reload = false)
|
89
88
|
if reload or @objects.nil?
|
90
89
|
@objects = list_bucket
|
@@ -109,11 +108,17 @@ module S3
|
|
109
108
|
alias :find :find_first
|
110
109
|
|
111
110
|
# Finds the objects in the bucket.
|
112
|
-
#
|
113
|
-
#
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
111
|
+
#
|
112
|
+
# ==== Options
|
113
|
+
# * <tt>:prefix</tt> - Limits the response to keys which begin
|
114
|
+
# with the indicated prefix
|
115
|
+
# * <tt>:marker</tt> - Indicates where in the bucket to begin
|
116
|
+
# listing
|
117
|
+
# * <tt>:max_keys</tt> - The maximum number of keys you'd like
|
118
|
+
# to see
|
119
|
+
# * <tt>:delimiter</tt> - Causes keys that contain the same
|
120
|
+
# string between the prefix and the first occurrence of the
|
121
|
+
# delimiter to be rolled up into a single result element
|
117
122
|
def find_all(options = {})
|
118
123
|
proxy_owner.send(:list_bucket, options)
|
119
124
|
end
|
data/lib/s3/connection.rb
CHANGED
@@ -7,46 +7,52 @@ module S3
|
|
7
7
|
attr_accessor :access_key_id, :secret_access_key, :use_ssl, :timeout, :debug
|
8
8
|
alias :use_ssl? :use_ssl
|
9
9
|
|
10
|
-
#
|
11
|
-
# +options+:: Hash of options
|
10
|
+
# Creates new connection object.
|
12
11
|
#
|
13
|
-
# ==== Options
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
12
|
+
# ==== Options
|
13
|
+
# * <tt>:access_key_id</tt> - Access key id (REQUIRED)
|
14
|
+
# * <tt>:secret_access_key</tt> - Secret access key (REQUIRED)
|
15
|
+
# * <tt>:use_ssl</tt> - Use https or http protocol (false by
|
16
|
+
# default)
|
17
|
+
# * <tt>:debug</tt> - Display debug information on the STDOUT
|
18
|
+
# (false by default)
|
19
|
+
# * <tt>:timeout</tt> - Timeout to use by the Net::HTTP object
|
20
|
+
# (60 by default)
|
19
21
|
def initialize(options = {})
|
20
|
-
@access_key_id = options
|
21
|
-
@secret_access_key = options
|
22
|
-
@use_ssl = options
|
23
|
-
@debug = options
|
24
|
-
@timeout = options
|
22
|
+
@access_key_id = options.fetch(:access_key_id)
|
23
|
+
@secret_access_key = options.fetch(:secret_access_key)
|
24
|
+
@use_ssl = options.fetch(:use_ssl, false)
|
25
|
+
@debug = options.fetch(:debug, false)
|
26
|
+
@timeout = options.fetch(:timeout, 60)
|
25
27
|
end
|
26
28
|
|
27
29
|
# Makes request with given HTTP method, sets missing parameters,
|
28
30
|
# adds signature to request header and returns response object
|
29
31
|
# (Net::HTTPResponse)
|
30
32
|
#
|
31
|
-
# ==== Parameters
|
32
|
-
#
|
33
|
-
#
|
33
|
+
# ==== Parameters
|
34
|
+
# * <tt>method</tt> - HTTP Method symbol, can be <tt>:get</tt>,
|
35
|
+
# <tt>:put</tt>, <tt>:delete</tt>
|
34
36
|
#
|
35
37
|
# ==== Options:
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
38
|
+
# * <tt>:host</tt> - Hostname to connecto to, defaults
|
39
|
+
# to <tt>s3.amazonaws.com</tt>
|
40
|
+
# * <tt>:path</tt> - path to send request to (REQUIRED)
|
41
|
+
# * <tt>:body</tt> - Request body, only meaningful for
|
42
|
+
# <tt>:put</tt> request
|
43
|
+
# * <tt>:params</tt> - Parameters to add to query string for
|
44
|
+
# request, can be String or Hash
|
45
|
+
# * <tt>:headers</tt> - Hash of headers fields to add to request
|
46
|
+
# header
|
41
47
|
#
|
42
|
-
# ==== Returns
|
43
|
-
# Net::HTTPResponse object -- response from
|
48
|
+
# ==== Returns
|
49
|
+
# Net::HTTPResponse object -- response from the server
|
44
50
|
def request(method, options)
|
45
|
-
host = options
|
46
|
-
path = options
|
47
|
-
body = options
|
48
|
-
params = options
|
49
|
-
headers = options
|
51
|
+
host = options.fetch(:host, HOST)
|
52
|
+
path = options.fetch(:path)
|
53
|
+
body = options.fetch(:body, "")
|
54
|
+
params = options.fetch(:params, {})
|
55
|
+
headers = options.fetch(:headers, {})
|
50
56
|
|
51
57
|
if params
|
52
58
|
params = params.is_a?(String) ? params : self.class.parse_params(params)
|
@@ -66,13 +72,13 @@ module S3
|
|
66
72
|
send_request(host, request)
|
67
73
|
end
|
68
74
|
|
69
|
-
# Helper function to parser parameters and create single string of
|
70
|
-
# added to questy string
|
75
|
+
# Helper function to parser parameters and create single string of
|
76
|
+
# params added to questy string
|
71
77
|
#
|
72
|
-
# ==== Parameters
|
73
|
-
#
|
78
|
+
# ==== Parameters
|
79
|
+
# * <tt>params</tt> - Hash of parameters
|
74
80
|
#
|
75
|
-
# ==== Returns
|
81
|
+
# ==== Returns
|
76
82
|
# String -- containing all parameters joined in one params string,
|
77
83
|
# i.e. <tt>param1=val¶m2¶m3=0</tt>
|
78
84
|
def self.parse_params(params)
|
@@ -96,14 +102,14 @@ module S3
|
|
96
102
|
# Helper function to change headers from symbols, to in correct
|
97
103
|
# form (i.e. with '-' instead of '_')
|
98
104
|
#
|
99
|
-
# ==== Parameters
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
105
|
+
# ==== Parameters
|
106
|
+
# * <tt>headers</tt> - Hash of pairs <tt>headername => value</tt>,
|
107
|
+
# where value can be Range (for Range header) or any other value
|
108
|
+
# which can be translated to string
|
103
109
|
#
|
104
|
-
# ==== Returns
|
105
|
-
# Hash of headers translated from symbol to string,
|
106
|
-
#
|
110
|
+
# ==== Returns
|
111
|
+
# Hash of headers translated from symbol to string, containing
|
112
|
+
# only interesting headers
|
107
113
|
def self.parse_headers(headers)
|
108
114
|
interesting_keys = [:content_type, :x_amz_acl, :range,
|
109
115
|
:if_modified_since, :if_unmodified_since,
|
@@ -170,9 +176,9 @@ module S3
|
|
170
176
|
end
|
171
177
|
|
172
178
|
request["Authorization"] = Signature.generate(:host => host,
|
173
|
-
|
174
|
-
|
175
|
-
|
179
|
+
:request => request,
|
180
|
+
:access_key_id => access_key_id,
|
181
|
+
:secret_access_key => secret_access_key)
|
176
182
|
http.request(request)
|
177
183
|
end
|
178
184
|
|
data/lib/s3/exceptions.rb
CHANGED
@@ -12,23 +12,25 @@ module S3
|
|
12
12
|
class ResponseError < StandardError
|
13
13
|
attr_reader :response
|
14
14
|
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
15
|
+
# Creates new S3::ResponseError.
|
16
|
+
#
|
17
|
+
# ==== Parameters
|
18
|
+
# * <tt>message</tt> - what went wrong
|
19
|
+
# * <tt>response</tt> - Net::HTTPResponse object or nil
|
18
20
|
def initialize(message, response)
|
19
21
|
@response = response
|
20
22
|
super(message)
|
21
23
|
end
|
22
24
|
|
23
|
-
# Factory for all other Exception classes in module, each for
|
24
|
-
# error response available from AmazonAWS
|
25
|
+
# Factory for all other Exception classes in module, each for
|
26
|
+
# every error response available from AmazonAWS
|
25
27
|
#
|
26
|
-
# ==== Parameters
|
27
|
-
#
|
28
|
+
# ==== Parameters
|
29
|
+
# * <tt>code</tt> - Code name of exception
|
28
30
|
#
|
29
|
-
# ==== Returns
|
30
|
-
# Descendant of ResponseError suitable for that exception code
|
31
|
-
# if no class found
|
31
|
+
# ==== Returns
|
32
|
+
# Descendant of ResponseError suitable for that exception code
|
33
|
+
# or ResponseError class if no class found
|
32
34
|
def self.exception(code)
|
33
35
|
S3::Error.const_get(code)
|
34
36
|
rescue NameError
|
data/lib/s3/object.rb
CHANGED
@@ -10,17 +10,17 @@ module S3
|
|
10
10
|
attr_writer :content
|
11
11
|
|
12
12
|
def_instance_delegators :bucket, :name, :service, :bucket_request, :vhost?, :host, :path_prefix
|
13
|
-
def_instance_delegators :service, :protocol, :port
|
13
|
+
def_instance_delegators :service, :protocol, :port, :secret_access_key
|
14
14
|
private_class_method :new
|
15
15
|
|
16
16
|
# Compares the object with other object. Returns true if the key
|
17
17
|
# of the objects are the same, and both have the same buckets (see
|
18
|
-
#
|
18
|
+
# Bucket equality)
|
19
19
|
def ==(other)
|
20
20
|
self.key == other.key and self.bucket == other.bucket
|
21
21
|
end
|
22
22
|
|
23
|
-
# Returns full key of the object: e.g.
|
23
|
+
# Returns full key of the object: e.g. <tt>bucket-name/object/key.ext</tt>
|
24
24
|
def full_key
|
25
25
|
[name, key].join("/")
|
26
26
|
end
|
@@ -34,6 +34,7 @@ module S3
|
|
34
34
|
|
35
35
|
# Assigns a new ACL to the object. Please note that ACL is not
|
36
36
|
# retrieved from the server and set to "public-read" by default.
|
37
|
+
#
|
37
38
|
# ==== Example
|
38
39
|
# object.acl = :public_read
|
39
40
|
def acl=(acl)
|
@@ -41,17 +42,17 @@ module S3
|
|
41
42
|
end
|
42
43
|
|
43
44
|
# Retrieves the object from the server. Method is used to download
|
44
|
-
# object information only (content
|
45
|
-
# NOT download the content of the object (use the content method
|
45
|
+
# object information only (content type, size and so on). It does
|
46
|
+
# NOT download the content of the object (use the #content method
|
46
47
|
# to do it).
|
47
48
|
def retrieve
|
48
49
|
get_object(:headers => { :range => 0..0 })
|
49
50
|
self
|
50
51
|
end
|
51
52
|
|
52
|
-
# Retrieves the object from the server, returns true if the
|
53
|
-
#
|
54
|
-
#
|
53
|
+
# Retrieves the object from the server, returns true if the object
|
54
|
+
# exists or false otherwise. Uses #retrieve method, but catches
|
55
|
+
# S3::Error::NoSuchKey exception and returns false when it happens
|
55
56
|
def exists?
|
56
57
|
retrieve
|
57
58
|
true
|
@@ -75,11 +76,15 @@ module S3
|
|
75
76
|
end
|
76
77
|
|
77
78
|
# Copies the file to another key and/or bucket.
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
79
|
+
#
|
80
|
+
# ==== Options
|
81
|
+
# * <tt>:key</tt> - New key to store object in
|
82
|
+
# * <tt>:bucket</tt> - New bucket to store object in (instance of
|
83
|
+
# S3::Bucket)
|
84
|
+
# * <tt>:acl</tt> - ACL of the copied object (default:
|
85
|
+
# "public-read")
|
86
|
+
# * <tt>:content_type</tt> - Content type of the copied object
|
87
|
+
# (default: "application/octet-stream")
|
83
88
|
def copy(options = {})
|
84
89
|
copy_object(options)
|
85
90
|
end
|
@@ -90,16 +95,28 @@ module S3
|
|
90
95
|
true
|
91
96
|
end
|
92
97
|
|
93
|
-
# Returns Object's URL using protocol specified in
|
94
|
-
# e.g. http://domain.com.s3.amazonaws.com/key/with/path.extension
|
98
|
+
# Returns Object's URL using protocol specified in service,
|
99
|
+
# e.g. <tt>http://domain.com.s3.amazonaws.com/key/with/path.extension</tt>
|
95
100
|
def url
|
96
101
|
URI.escape("#{protocol}#{host}/#{path_prefix}#{key}")
|
97
102
|
end
|
98
103
|
|
99
|
-
# Returns
|
100
|
-
#
|
101
|
-
|
102
|
-
|
104
|
+
# Returns a temporary url to the object that expires on the
|
105
|
+
# timestamp given. Defaults to one hour expire time.
|
106
|
+
def temporary_url(expires_at = Time.now + 3600)
|
107
|
+
signature = Signature.generate_temporary_url_signature(:bucket => name,
|
108
|
+
:resource => key,
|
109
|
+
:expires_on => expires_at,
|
110
|
+
:secret_access_key => secret_access_key)
|
111
|
+
|
112
|
+
"#{url}?Signature=#{URI.escape(signature)}&Expires=#{URI.escape(expires_at.to_i)}"
|
113
|
+
end
|
114
|
+
|
115
|
+
# Returns Object's CNAME URL (without <tt>s3.amazonaws.com</tt>
|
116
|
+
# suffix) using protocol specified in Service,
|
117
|
+
# e.g. <tt>http://domain.com/key/with/path.extension</tt>. (you
|
118
|
+
# have to set the CNAME in your DNS before using the CNAME URL
|
119
|
+
# schema).
|
103
120
|
def cname_url
|
104
121
|
URI.escape("#{protocol}#{name}/#{key}") if bucket.vhost?
|
105
122
|
end
|
data/lib/s3/service.rb
CHANGED
@@ -5,29 +5,33 @@ module S3
|
|
5
5
|
|
6
6
|
attr_reader :access_key_id, :secret_access_key, :use_ssl
|
7
7
|
|
8
|
-
# Compares service to other, by access_key_id and
|
8
|
+
# Compares service to other, by <tt>access_key_id</tt> and
|
9
|
+
# <tt>secret_access_key</tt>
|
9
10
|
def ==(other)
|
10
11
|
self.access_key_id == other.access_key_id and self.secret_access_key == other.secret_access_key
|
11
12
|
end
|
12
13
|
|
13
|
-
#
|
14
|
-
# +options+:: a hash of options described below
|
14
|
+
# Creates new service.
|
15
15
|
#
|
16
|
-
# ==== Options
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
16
|
+
# ==== Options
|
17
|
+
# * <tt>:access_key_id</tt> - Access key id (REQUIRED)
|
18
|
+
# * <tt>:secret_access_key</tt> - Secret access key (REQUIRED)
|
19
|
+
# * <tt>:use_ssl</tt> - Use https or http protocol (false by
|
20
|
+
# default)
|
21
|
+
# * <tt>:debug</tt> - Display debug information on the STDOUT
|
22
|
+
# (false by default)
|
23
|
+
# * <tt>:timeout</tt> - Timeout to use by the Net::HTTP object
|
24
|
+
# (60 by default)
|
22
25
|
def initialize(options)
|
23
|
-
@access_key_id = options
|
24
|
-
@secret_access_key = options
|
25
|
-
@use_ssl = options
|
26
|
-
@timeout = options
|
27
|
-
@debug = options
|
26
|
+
@access_key_id = options.fetch(:access_key_id)
|
27
|
+
@secret_access_key = options.fetch(:secret_access_key)
|
28
|
+
@use_ssl = options.fetch(:use_ssl, false)
|
29
|
+
@timeout = options.fetch(:timeout, 60)
|
30
|
+
@debug = options.fetch(:debug, false)
|
28
31
|
end
|
29
32
|
|
30
|
-
# Returns all buckets in the service and caches the result (see
|
33
|
+
# Returns all buckets in the service and caches the result (see
|
34
|
+
# +reload+)
|
31
35
|
def buckets(reload = false)
|
32
36
|
if reload or @buckets.nil?
|
33
37
|
@buckets = list_all_my_buckets
|
@@ -36,12 +40,14 @@ module S3
|
|
36
40
|
end
|
37
41
|
end
|
38
42
|
|
39
|
-
# Returns "http://" or "https://", depends on use_ssl
|
43
|
+
# Returns "http://" or "https://", depends on <tt>:use_ssl</tt>
|
44
|
+
# value from initializer
|
40
45
|
def protocol
|
41
46
|
use_ssl ? "https://" : "http://"
|
42
47
|
end
|
43
48
|
|
44
|
-
#
|
49
|
+
# Returns 443 or 80, depends on <tt>:use_ssl</tt> value from
|
50
|
+
# initializer
|
45
51
|
def port
|
46
52
|
use_ssl ? 443 : 80
|
47
53
|
end
|
@@ -97,12 +103,11 @@ module S3
|
|
97
103
|
|
98
104
|
def connection
|
99
105
|
if @connection.nil?
|
100
|
-
@connection = Connection.new
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
@connection.debug = @debug
|
106
|
+
@connection = Connection.new(:access_key_id => @access_key_id,
|
107
|
+
:secret_access_key => @secret_access_key,
|
108
|
+
:use_ssl => @use_ssl,
|
109
|
+
:timeout => @timeout,
|
110
|
+
:debug => @debug)
|
106
111
|
end
|
107
112
|
@connection
|
108
113
|
end
|