s4 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +85 -0
- data/lib/s4.rb +175 -38
- data/{Rakefile → rakefile} +0 -0
- data/s4.gemspec +4 -5
- data/test/s4_test.rb +129 -32
- metadata +6 -5
data/README.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
S4
|
2
|
+
==
|
3
|
+
|
4
|
+
A Simpler API for Amazon Web Services S3.
|
5
|
+
|
6
|
+
It does not implement the full S3 API, nor is that the intention. It just does
|
7
|
+
the basics (managing files in a bucket) in a very simple way with a
|
8
|
+
<del>very</del> small code footprint.
|
9
|
+
|
10
|
+
Usage
|
11
|
+
-----
|
12
|
+
|
13
|
+
$assets = S4.connect("s3://0PN5J17HBGZHT7JJ3X82:k3nL7gH3+PadhTEVn5EXAMPLE@s3.amazonaws.com/assets.mysite.com")
|
14
|
+
|
15
|
+
$assets.upload("puppy.jpg", "animals/puppy.jpg")
|
16
|
+
$assets.upload("penguin.jpg", "animals/penguin.jpg")
|
17
|
+
$assets.list("animals/") #=> [ "animals/puppy.jpg", "animals/penguin.jpg" ]
|
18
|
+
|
19
|
+
$assets.download("animals/penguin.jpg", "penguin.jpg")
|
20
|
+
|
21
|
+
$assets.delete("animals/penguin.jpg")
|
22
|
+
$assets.list("animals/") #=> [ "animals/puppy.jpg" ]
|
23
|
+
|
24
|
+
$assets.upload("ufo.jpg")
|
25
|
+
$assets.list #=> [ "ufo.jpg", "animals/puppy.jpg" ]
|
26
|
+
|
27
|
+
Low-level access:
|
28
|
+
|
29
|
+
$assets.get("animals/gigantic_penguin_movie.mp4") do |response|
|
30
|
+
File.open("gigantic_penguin_movie.mp4", "wb") do |io|
|
31
|
+
response.read_body do |chunk|
|
32
|
+
io.write(chunk)
|
33
|
+
puts "."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
$assets.put(StringIO.new("My Novel -- By Ben Alavi...", "r"), "novel.txt", "text/plain")
|
39
|
+
|
40
|
+
Create a bucket (returns the bucket if it already exists and is accessible):
|
41
|
+
|
42
|
+
$musics = S4.create("s3://0PN5J17HBGZHT7JJ3X82:k3nL7gH3+PadhTEVn5EXAMPLE@s3.amazonaws.com/musics.mysite.com")
|
43
|
+
|
44
|
+
Make a bucket into a static website:
|
45
|
+
|
46
|
+
$site = S4.connect("s3://0PN5J17HBGZHT7JJ3X82:k3nL7gH3+PadhTEVn5EXAMPLE@s3.amazonaws.com/website.mysite.com")
|
47
|
+
$site.website!
|
48
|
+
$site.put(StringIO.new("<!DOCTYPE html><html><head><title>My Website</title></head><body><h1><blink><font color="yellow">HELLO! WELCOME TO MY WEBSITE</font></blink></h1></body></html>", "r"), "index.html")
|
49
|
+
Net::HTTP.get("http://#{$site.website}/") #=> ...HELLO! WELCOME TO MY WEBSITE...
|
50
|
+
|
51
|
+
Plus a handful of other miscellaneous things...
|
52
|
+
|
53
|
+
Acknowledgements
|
54
|
+
----------------
|
55
|
+
|
56
|
+
* Michel Martens
|
57
|
+
* Chris Schneider
|
58
|
+
* Damian Janowski
|
59
|
+
* Cyril David
|
60
|
+
|
61
|
+
License
|
62
|
+
-------
|
63
|
+
|
64
|
+
Copyright (c) 2011 Ben Alavi
|
65
|
+
|
66
|
+
Permission is hereby granted, free of charge, to any person
|
67
|
+
obtaining a copy of this software and associated documentation
|
68
|
+
files (the "Software"), to deal in the Software without
|
69
|
+
restriction, including without limitation the rights to use,
|
70
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
71
|
+
copies of the Software, and to permit persons to whom the
|
72
|
+
Software is furnished to do so, subject to the following
|
73
|
+
conditions:
|
74
|
+
|
75
|
+
The above copyright notice and this permission notice shall be
|
76
|
+
included in all copies or substantial portions of the Software.
|
77
|
+
|
78
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
79
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
80
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
81
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
82
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
83
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
84
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
85
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/s4.rb
CHANGED
@@ -2,21 +2,43 @@ require "net/http/persistent"
|
|
2
2
|
require "rexml/document"
|
3
3
|
require "base64"
|
4
4
|
require "time"
|
5
|
+
require "json"
|
5
6
|
|
6
7
|
# Simpler AWS S3 library
|
7
8
|
class S4
|
8
|
-
VERSION = "0.0.
|
9
|
+
VERSION = "0.0.3"
|
9
10
|
|
10
11
|
# sub-resource names which may appear in the query string and also must be
|
11
12
|
# signed against.
|
12
13
|
SubResources = %w( acl location logging notification partNumber policy requestPayment torrent uploadId uploads versionId versioning versions website )
|
13
14
|
|
14
15
|
# Header over-rides which may appear in the query string and also must be
|
15
|
-
# signed against
|
16
|
+
# signed against (in addition those which begin w/ 'x-amz-')
|
16
17
|
HeaderValues = %w( response-content-type response-content-language response-expires reponse-cache-control response-content-disposition response-content-encoding )
|
18
|
+
|
19
|
+
# List of available ACLs on buckets, first is used as default
|
20
|
+
# http://docs.amazonwebservices.com/AmazonS3/latest/API/index.html?RESTBucketPUT.html
|
21
|
+
BucketACLs = %w( private public-read public-read-write authenticated-read bucket-owner-read bucket-owner-full-control )
|
22
|
+
|
23
|
+
# Named policies
|
24
|
+
Policy = {
|
25
|
+
public_read: %Q{\{
|
26
|
+
"Version": "2008-10-17",
|
27
|
+
"Statement": [{
|
28
|
+
"Sid": "AllowPublicRead",
|
29
|
+
"Effect": "Allow",
|
30
|
+
"Principal": {"AWS": "*"},
|
31
|
+
"Action": ["s3:GetObject"],
|
32
|
+
"Resource": ["arn:aws:s3:::%s/*"]
|
33
|
+
}]
|
34
|
+
\}}
|
35
|
+
}.freeze
|
17
36
|
|
18
37
|
attr_reader :connection, :access_key_id, :secret_access_key, :bucket, :host
|
19
38
|
|
39
|
+
# Cannot call #new explicitly (no reason to), use #connect instead
|
40
|
+
private_class_method :new
|
41
|
+
|
20
42
|
class << self
|
21
43
|
# Connect to an S3 bucket.
|
22
44
|
#
|
@@ -27,14 +49,14 @@ class S4
|
|
27
49
|
#
|
28
50
|
# i.e.
|
29
51
|
# bucket = S4.connect #=> Connects to ENV["S3_URL"]
|
30
|
-
# bucket = S4.connect("s3://0PN5J17HBGZHT7JJ3X82:k3nL7gH3+PadhTEVn5EXAMPLE@s3.amazonaws.com/bucket")
|
31
|
-
def connect(
|
32
|
-
|
52
|
+
# bucket = S4.connect(url: "s3://0PN5J17HBGZHT7JJ3X82:k3nL7gH3+PadhTEVn5EXAMPLE@s3.amazonaws.com/bucket")
|
53
|
+
def connect(options={})
|
54
|
+
init(options) do |s4|
|
33
55
|
s4.connect
|
34
56
|
end
|
35
57
|
end
|
36
58
|
|
37
|
-
# Create
|
59
|
+
# Create a new S3 bucket.
|
38
60
|
#
|
39
61
|
# See #connect for S3_URL parameters.
|
40
62
|
#
|
@@ -43,14 +65,21 @@ class S4
|
|
43
65
|
#
|
44
66
|
# i.e.
|
45
67
|
# bucket = S4.create
|
46
|
-
def create(
|
47
|
-
|
48
|
-
s4.create
|
68
|
+
def create(options={})
|
69
|
+
init(options) do |s4|
|
70
|
+
s4.create(options[:acl] || BucketACLs.first)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def init(options={}, &block)
|
77
|
+
new(options.has_key?(:url) ? options[:url] : ENV["S3_URL"]).tap do |s4|
|
78
|
+
yield(s4) if block_given?
|
49
79
|
end
|
50
80
|
end
|
51
81
|
end
|
52
82
|
|
53
|
-
# Initialize a new S3 bucket connection.
|
54
83
|
def initialize(s3_url=ENV["S3_URL"])
|
55
84
|
raise ArgumentError, "No S3 URL provided. You can set ENV['S3_URL'], too." if s3_url.nil? || s3_url.empty?
|
56
85
|
|
@@ -72,13 +101,26 @@ class S4
|
|
72
101
|
# Since S3 doesn't really require a persistent connection this really just
|
73
102
|
# makes sure that it *can* connect (i.e. the bucket exists and you own it).
|
74
103
|
def connect
|
75
|
-
|
76
|
-
end
|
104
|
+
location
|
105
|
+
end
|
77
106
|
|
78
107
|
# Create the S3 bucket.
|
79
|
-
|
80
|
-
|
81
|
-
|
108
|
+
#
|
109
|
+
# If the bucket exists and you own it will not do anything, if it exists and
|
110
|
+
# you don't own it will raise an error.
|
111
|
+
#
|
112
|
+
# Optionally pass an ACL for the new bucket, see BucketACLs for valid ACLs.
|
113
|
+
#
|
114
|
+
# Default ACL is "private"
|
115
|
+
def create(acl=BucketACLs.first)
|
116
|
+
raise ArgumentError.new("Invalid ACL '#{acl}' for bucket. Available ACLs are: #{BucketACLs.join(", ")}.") unless BucketACLs.include?(acl)
|
117
|
+
|
118
|
+
uri = uri("/")
|
119
|
+
req = Net::HTTP::Put.new(uri.request_uri)
|
120
|
+
|
121
|
+
req.add_field "x-amz-acl", acl
|
122
|
+
|
123
|
+
request uri, req
|
82
124
|
end
|
83
125
|
|
84
126
|
# Lower level object get which just yields the successful S3 response to the
|
@@ -120,15 +162,26 @@ class S4
|
|
120
162
|
put File.open(name, "rb"), destination || File.basename(name)
|
121
163
|
end
|
122
164
|
|
123
|
-
|
165
|
+
# Write an IO stream to a file in this bucket.
|
166
|
+
#
|
167
|
+
# Will write file with content_type if given, otherwise will attempt to
|
168
|
+
# determine content type by shelling out to POSIX `file` command (if IO
|
169
|
+
# stream responds to #path). If no content_type could be determined, will
|
170
|
+
# default to application/x-www-form-urlencoded.
|
171
|
+
#
|
172
|
+
# i.e.
|
173
|
+
# bucket.put(StringIO.new("Awesome!"), "awesome.txt", "text/plain")
|
174
|
+
def put(io, name, content_type=nil)
|
124
175
|
uri = uri(name)
|
125
176
|
req = Net::HTTP::Put.new(uri.request_uri)
|
126
|
-
|
127
|
-
|
177
|
+
|
178
|
+
content_type = `file -ib #{io.path}`.chomp if !content_type && io.respond_to?(:path)
|
179
|
+
|
180
|
+
req.add_field "Content-Type", content_type
|
128
181
|
req.add_field "Content-Length", io.size
|
129
|
-
req.
|
182
|
+
req.body_stream = io
|
130
183
|
|
131
|
-
request(
|
184
|
+
request uri("/#{name}"), req
|
132
185
|
end
|
133
186
|
|
134
187
|
# List bucket contents.
|
@@ -141,6 +194,82 @@ class S4
|
|
141
194
|
REXML::Document.new(request(uri("", query: "prefix=#{prefix}"))).elements.collect("//Key", &:text)
|
142
195
|
end
|
143
196
|
|
197
|
+
# Turns this bucket into a S3 static website bucket.
|
198
|
+
#
|
199
|
+
# IMPORTANT: by default a policy will be applied to the bucket allowing read
|
200
|
+
# access to all files contained in the bucket.
|
201
|
+
#
|
202
|
+
# i.e.
|
203
|
+
# site = S4.connect(url: "s3://0PN5J17HBGZHT7JJ3X82:k3nL7gH3+PadhTEVn5EXAMPLE@s3.amazonaws.com/mywebsite")
|
204
|
+
# site.website!
|
205
|
+
# site.put(StringIO.new("<!DOCTYPE html><html><head><title>Robots!</title></head><body><h1>So many robots!!!</h1></body></html>", "r"), "index.html")
|
206
|
+
# Net::HTTP.get(URI.parse("http://mywebsite.s3.amazonaws.com/")) #=> ...<h1>So many robots!!!</h1>...
|
207
|
+
def website!
|
208
|
+
self.policy = Policy[:public_read] % bucket
|
209
|
+
|
210
|
+
uri = uri("/", query: "website")
|
211
|
+
req = Net::HTTP::Put.new(uri.request_uri)
|
212
|
+
|
213
|
+
req.body = <<-XML
|
214
|
+
<WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
215
|
+
<IndexDocument>
|
216
|
+
<Suffix>index.html</Suffix>
|
217
|
+
</IndexDocument>
|
218
|
+
<ErrorDocument>
|
219
|
+
<Key>404.html</Key>
|
220
|
+
</ErrorDocument>
|
221
|
+
</WebsiteConfiguration>
|
222
|
+
XML
|
223
|
+
|
224
|
+
request uri, req
|
225
|
+
end
|
226
|
+
|
227
|
+
# The URL of the bucket for use as a website.
|
228
|
+
def website
|
229
|
+
"#{bucket}.s3-website-#{location}.amazonaws.com"
|
230
|
+
end
|
231
|
+
|
232
|
+
# Sets the given policy on the bucket.
|
233
|
+
#
|
234
|
+
# Policy can be given as a string which will be applied as given, a hash
|
235
|
+
# which will be converted to json, or the name of a pre-defined policy as a
|
236
|
+
# symbol.
|
237
|
+
#
|
238
|
+
# See S4::Policy for pre-defined policies.
|
239
|
+
#
|
240
|
+
# i.e.
|
241
|
+
# $s4 = S4.connect
|
242
|
+
# $s4.policy = :public_read #=> apply named policy
|
243
|
+
# $s4.policy = {"Statement" => "..."} #=> apply policy as hash
|
244
|
+
# $s4.policy = "{\"Statement\": \"...\"}" #=> apply policy as string
|
245
|
+
def policy=(policy)
|
246
|
+
policy = Policy[policy] % bucket if policy.is_a?(Symbol)
|
247
|
+
|
248
|
+
uri = uri("/", query: "policy")
|
249
|
+
req = Net::HTTP::Put.new(uri.request_uri)
|
250
|
+
|
251
|
+
req.body = policy.is_a?(String) ? policy : policy.to_json
|
252
|
+
|
253
|
+
request uri, req
|
254
|
+
end
|
255
|
+
|
256
|
+
# Gets the policy on the bucket.
|
257
|
+
def policy
|
258
|
+
request uri("/", query: "policy")
|
259
|
+
end
|
260
|
+
|
261
|
+
# Gets information about the buckets location.
|
262
|
+
def location
|
263
|
+
response = request uri("/", query: "location")
|
264
|
+
location = REXML::Document.new(response).elements["LocationConstraint"].text
|
265
|
+
|
266
|
+
location || "us-east-1"
|
267
|
+
end
|
268
|
+
|
269
|
+
def inspect
|
270
|
+
"#<S4: bucket='#{bucket}'>"
|
271
|
+
end
|
272
|
+
|
144
273
|
private
|
145
274
|
|
146
275
|
def connection
|
@@ -164,7 +293,7 @@ class S4
|
|
164
293
|
return response.body
|
165
294
|
end
|
166
295
|
else
|
167
|
-
raise Error.
|
296
|
+
raise Error.new(response)
|
168
297
|
end
|
169
298
|
end
|
170
299
|
end
|
@@ -173,19 +302,22 @@ class S4
|
|
173
302
|
date = Time.now.utc.rfc822
|
174
303
|
|
175
304
|
request.add_field "Date", date
|
176
|
-
request.add_field "
|
305
|
+
request.add_field "Content-Type", "application/x-www-form-urlencoded" if request.is_a?(Net::HTTP::Put) && !request["Content-Type"]
|
177
306
|
|
307
|
+
request.add_field "Authorization", "AWS #{access_key_id}:#{signature(uri, request)}"
|
178
308
|
request
|
179
309
|
end
|
180
310
|
|
181
311
|
def signature(uri, request)
|
182
312
|
query = signed_params(uri.query) if uri.query
|
183
313
|
|
314
|
+
string_to_sign = "#{request.class::METHOD}\n\n#{request["Content-Type"]}\n#{request["Date"]}\n#{canonicalized_headers(request)}" + "#{uri.path}" + (query ? "?#{query}" : "")
|
315
|
+
|
184
316
|
Base64.encode64(
|
185
317
|
OpenSSL::HMAC.digest(
|
186
318
|
OpenSSL::Digest::Digest.new("sha1"),
|
187
319
|
secret_access_key,
|
188
|
-
|
320
|
+
string_to_sign
|
189
321
|
)
|
190
322
|
).chomp
|
191
323
|
end
|
@@ -198,28 +330,33 @@ class S4
|
|
198
330
|
collect{ |param| param.split("=") }.
|
199
331
|
reject{ |pair| !SubResources.include?(pair[0]) }.
|
200
332
|
collect{ |pair| pair.join("=") }.
|
333
|
+
sort.
|
201
334
|
join("&")
|
202
|
-
|
335
|
+
|
203
336
|
signed unless signed.empty?
|
204
337
|
end
|
205
338
|
|
339
|
+
def canonicalized_headers(request)
|
340
|
+
headers = request.to_hash.
|
341
|
+
reject{ |k, v| k !~ /x-amz-/ && !HeaderValues.include?(k) }.
|
342
|
+
collect{ |k, v| "#{k}:#{v.join(",")}" }.
|
343
|
+
sort.
|
344
|
+
join("\n")
|
345
|
+
|
346
|
+
"#{headers}\n" unless headers.empty?
|
347
|
+
end
|
348
|
+
|
206
349
|
# Base class of all S3 Errors
|
207
350
|
class Error < ::RuntimeError
|
208
|
-
attr_reader :code, :status
|
209
|
-
|
210
|
-
def
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
new response.code, code, message
|
216
|
-
end
|
217
|
-
|
218
|
-
def initialize(status, code, message)
|
219
|
-
@status = status
|
220
|
-
@code = code
|
351
|
+
attr_reader :code, :status, :response
|
352
|
+
|
353
|
+
def initialize(response)
|
354
|
+
@response = REXML::Document.new(response.body).elements["//Error"]
|
355
|
+
|
356
|
+
@status = response.code
|
357
|
+
@code = @response.elements["Code"].text
|
221
358
|
|
222
|
-
super "#{@
|
359
|
+
super "#{@status}: #{@code} -- " + @response.elements["Message"].text
|
223
360
|
end
|
224
361
|
end
|
225
362
|
end
|
data/{Rakefile → rakefile}
RENAMED
File without changes
|
data/s4.gemspec
CHANGED
@@ -10,15 +10,14 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.homepage = "http://github.com/benalavi/s4"
|
11
11
|
|
12
12
|
s.files = Dir[
|
13
|
-
"
|
14
|
-
"
|
15
|
-
"
|
13
|
+
"README.md",
|
14
|
+
"rakefile",
|
15
|
+
"s4.gemspec",
|
16
16
|
"lib/**/*.rb",
|
17
|
-
"*.gemspec",
|
18
17
|
"test/**/*.rb"
|
19
18
|
]
|
20
19
|
|
21
|
-
s.add_dependency "net-http-persistent", "
|
20
|
+
s.add_dependency "net-http-persistent", ">= 1.7"
|
22
21
|
|
23
22
|
s.add_development_dependency "cutest"
|
24
23
|
s.add_development_dependency "timecop", "~> 0.3"
|
data/test/s4_test.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
+
raise "You need to have ENV[\"S3_URL\"] set for the tests to connect to your testing bucket on S3. Format is: 's3://<access key id>:<secret access key>@s3.amazonaws.com/<s4 test bucket>'." unless ENV["S3_URL"]
|
2
|
+
raise "You need to have ENV[\"S4_NEW_BUCKET\"], which will be dynamically created and destroyed for testing bucket creation. i.e.: 's4-test-bucketthatdoesntexist'." unless ENV["S3_URL"]
|
3
|
+
|
1
4
|
require "contest"
|
2
5
|
require "timecop"
|
3
6
|
require "fileutils"
|
7
|
+
require "open-uri"
|
4
8
|
|
5
9
|
begin
|
6
10
|
require "ruby-debug"
|
@@ -9,18 +13,24 @@ end
|
|
9
13
|
|
10
14
|
require File.expand_path("../lib/s4", File.dirname(__FILE__))
|
11
15
|
|
12
|
-
|
13
|
-
|
14
|
-
end
|
16
|
+
TestBucket = S4.connect.bucket
|
17
|
+
NewBucket = ENV["S4_NEW_BUCKET"]
|
15
18
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
+
class S4Test < Test::Unit::TestCase
|
20
|
+
def fixture(filename="")
|
21
|
+
File.join(File.dirname(__FILE__), "fixtures", filename)
|
22
|
+
end
|
19
23
|
|
20
|
-
|
21
|
-
|
24
|
+
def output(filename="")
|
25
|
+
File.join(File.dirname(__FILE__), "output", filename)
|
26
|
+
end
|
22
27
|
|
23
|
-
|
28
|
+
def delete_test_bucket
|
29
|
+
`s3cmd del 's3://#{TestBucket}/abc/*' 2>&1`
|
30
|
+
`s3cmd del 's3://#{TestBucket}/*' 2>&1`
|
31
|
+
`s3cmd rb 's3://#{TestBucket}' 2>&1`
|
32
|
+
end
|
33
|
+
|
24
34
|
setup do
|
25
35
|
FileUtils.rm_rf(output)
|
26
36
|
FileUtils.mkdir_p(output)
|
@@ -29,52 +39,152 @@ class S4Test < Test::Unit::TestCase
|
|
29
39
|
context "connecting to S3" do
|
30
40
|
should "return connected bucket if can connect" do
|
31
41
|
s4 = S4.connect
|
42
|
+
assert s4
|
43
|
+
end
|
44
|
+
|
45
|
+
should "bark when no URL is provided" do
|
46
|
+
assert_raise(ArgumentError) { S4.connect(url: "") }
|
47
|
+
assert_raise(ArgumentError) { S4.connect(url: nil) }
|
48
|
+
assert_raise(URI::InvalidURIError) { S4.connect(url: "s3://foo:bar/baz") }
|
32
49
|
end
|
33
50
|
|
34
51
|
should "raise error if cannot connect" do
|
52
|
+
`s3cmd del 's3://#{NewBucket}/*' 2>&1`
|
35
53
|
`s3cmd rb 's3://#{NewBucket}' 2>&1`
|
36
54
|
|
37
55
|
assert_raise(S4::Error) do
|
38
|
-
S4.connect ENV["S3_URL"].sub(TestBucket, NewBucket)
|
56
|
+
S4.connect url: ENV["S3_URL"].sub(TestBucket, NewBucket)
|
39
57
|
end
|
40
58
|
end
|
41
59
|
end
|
42
60
|
|
43
61
|
context "when S3 errors occur" do
|
44
62
|
# foo is taken bucket, will cause 409 Conflict on create
|
45
|
-
|
46
63
|
should "raise on S3 errors" do
|
47
64
|
assert_raise(S4::Error) do
|
48
|
-
S4.create
|
65
|
+
S4.create url: ENV["S3_URL"].sub(TestBucket, "foo")
|
49
66
|
end
|
50
67
|
end
|
51
68
|
|
52
69
|
should "capture code of S3 error" do
|
53
70
|
begin
|
54
|
-
S4.create
|
71
|
+
S4.create url: ENV["S3_URL"].sub(TestBucket, "foo")
|
55
72
|
rescue S4::Error => e
|
56
|
-
assert_equal "409", e.status
|
73
|
+
assert_equal "409", e.status, "Expected 409 got #{e.message}"
|
57
74
|
end
|
58
75
|
end
|
59
76
|
end
|
60
77
|
|
61
78
|
context "creating a bucket" do
|
62
79
|
setup do
|
80
|
+
`s3cmd del 's3://#{NewBucket}/*' 2>&1`
|
63
81
|
`s3cmd rb 's3://#{NewBucket}' 2>&1`
|
64
82
|
end
|
65
83
|
|
66
84
|
should "create a bucket" do
|
67
85
|
assert_equal "ERROR: Bucket '#{NewBucket}' does not exist", `s3cmd ls 's3://#{NewBucket}' 2>&1`.chomp
|
68
|
-
|
86
|
+
|
87
|
+
S4.create url: ENV["S3_URL"].sub(TestBucket, NewBucket)
|
88
|
+
|
69
89
|
assert_equal "", `s3cmd ls 's3://#{NewBucket}' 2>&1`.chomp
|
70
90
|
end
|
91
|
+
|
92
|
+
should "create bucket with public-read ACL" do
|
93
|
+
# TODO...
|
94
|
+
end
|
95
|
+
|
96
|
+
should "raise if bucket creation failed" do
|
97
|
+
assert_raise(S4::Error) do
|
98
|
+
S4.create url: ENV["S3_URL"].sub(TestBucket, "foo")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
should "raise if given invalid ACL" do
|
103
|
+
begin
|
104
|
+
S4.create url: ENV["S3_URL"], acl: "foo"
|
105
|
+
rescue ArgumentError => e
|
106
|
+
assert_match /foo/, e.message
|
107
|
+
end
|
108
|
+
end
|
71
109
|
end
|
72
|
-
|
110
|
+
|
111
|
+
context "making a website" do
|
112
|
+
setup do
|
113
|
+
delete_test_bucket
|
114
|
+
end
|
115
|
+
|
116
|
+
should "make bucket a website" do
|
117
|
+
s4 = S4.create
|
118
|
+
|
119
|
+
begin
|
120
|
+
open("http://#{s4.website}/")
|
121
|
+
rescue OpenURI::HTTPError => e
|
122
|
+
assert_match /NoSuchWebsiteConfiguration/, e.io.read
|
123
|
+
end
|
124
|
+
|
125
|
+
s4.put(StringIO.new("<!DOCTYPE html><html><head><title>Robot Page</title></head><body><h1>Robots!</h1></body></html>", "r"), "index.html", "text/html")
|
126
|
+
s4.put(StringIO.new("<!DOCTYPE html><html><head><title>404!</title></head><body><h1>Oh No 404!!!</h1></body></html>", "r"), "404.html", "text/html")
|
127
|
+
s4.website!
|
128
|
+
|
129
|
+
assert_match /Robots!/, open("http://#{s4.website}/").read
|
130
|
+
|
131
|
+
begin
|
132
|
+
open("http://#{s4.website}/foo.html")
|
133
|
+
rescue OpenURI::HTTPError => e
|
134
|
+
raise e unless e.message =~ /404/
|
135
|
+
assert_match /Oh No 404!!!/, e.io.read
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context "setting policy on a bucket" do
|
141
|
+
setup do
|
142
|
+
delete_test_bucket
|
143
|
+
@s4 = S4.create
|
144
|
+
end
|
145
|
+
|
146
|
+
should "make all objects public by policy" do
|
147
|
+
@s4.upload(fixture("foo.txt"))
|
148
|
+
|
149
|
+
begin
|
150
|
+
open("http://s3.amazonaws.com/#{TestBucket}/foo.txt")
|
151
|
+
rescue OpenURI::HTTPError => e
|
152
|
+
assert_match /403 Forbidden/, e.message
|
153
|
+
end
|
154
|
+
|
155
|
+
@s4.policy = :public_read
|
156
|
+
|
157
|
+
assert_equal "abc123", open("http://s3.amazonaws.com/#{TestBucket}/foo.txt").read
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context "uploading to bucket" do
|
162
|
+
setup do
|
163
|
+
delete_test_bucket
|
164
|
+
@s4 = S4.create
|
165
|
+
@s4.policy = :public_read
|
166
|
+
end
|
167
|
+
|
168
|
+
should "upload foo.txt" do
|
169
|
+
@s4.upload(fixture("foo.txt"))
|
170
|
+
|
171
|
+
foo = open("http://s3.amazonaws.com/#{TestBucket}/foo.txt")
|
172
|
+
|
173
|
+
assert_equal "abc123", foo.read
|
174
|
+
assert_equal "text/plain", foo.content_type
|
175
|
+
end
|
176
|
+
|
177
|
+
should "use given content_type" do
|
178
|
+
@s4.put(StringIO.new("abcdef", "r"), "bar.txt", "text/foobar")
|
179
|
+
assert_equal "text/foobar", open("http://s3.amazonaws.com/#{TestBucket}/bar.txt").content_type
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
73
183
|
context "when connected" do
|
74
184
|
setup do
|
75
185
|
@s4 = S4.connect
|
76
186
|
end
|
77
|
-
|
187
|
+
|
78
188
|
should "download foo.txt" do
|
79
189
|
`s3cmd put #{fixture("foo.txt")} s3://#{@s4.bucket}/foo.txt`
|
80
190
|
@s4.download("foo.txt", output("foo.txt"))
|
@@ -111,9 +221,9 @@ class S4Test < Test::Unit::TestCase
|
|
111
221
|
end
|
112
222
|
|
113
223
|
should "return list of items in bucket" do
|
224
|
+
`s3cmd del 's3://#{@s4.bucket}/*'`
|
114
225
|
`s3cmd del 's3://#{@s4.bucket}/abc/*'`
|
115
|
-
|
116
|
-
`s3cmd del 's3://#{@s4.bucket}/foo\ bar+baz.txt'`
|
226
|
+
|
117
227
|
`s3cmd put #{fixture("foo.txt")} s3://#{@s4.bucket}/foo.txt`
|
118
228
|
`s3cmd put #{fixture("foo.txt")} s3://#{@s4.bucket}/bar.txt`
|
119
229
|
`s3cmd put #{fixture("foo.txt")} s3://#{@s4.bucket}/baz.txt`
|
@@ -134,18 +244,5 @@ class S4Test < Test::Unit::TestCase
|
|
134
244
|
|
135
245
|
@s4.get("foo bar+baz.txt") { |response| assert_equal "abc123", response.body }
|
136
246
|
end
|
137
|
-
|
138
|
-
should "upload foo.txt" do
|
139
|
-
`s3cmd del 's3://#{@s4.bucket}/foo.txt'`
|
140
|
-
@s4.upload(fixture("foo.txt"))
|
141
|
-
@s4.get("foo.txt") { |response| assert_equal "abc123", response.body }
|
142
|
-
end
|
143
|
-
|
144
|
-
should "bark when no URL is provided" do
|
145
|
-
assert_raise(ArgumentError) { S4.connect("") }
|
146
|
-
assert_raise(ArgumentError) { S4.connect(nil) }
|
147
|
-
|
148
|
-
assert_raise(URI::InvalidURIError) { S4.connect("s3://foo:bar/baz") }
|
149
|
-
end
|
150
247
|
end
|
151
248
|
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: s4
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.3
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Ben Alavi
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-09-
|
13
|
+
date: 2011-09-04 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: net-http-persistent
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirement: &id001 !ruby/object:Gem::Requirement
|
19
19
|
none: false
|
20
20
|
requirements:
|
21
|
-
- -
|
21
|
+
- - ">="
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: "1.7"
|
24
24
|
type: :runtime
|
@@ -55,9 +55,10 @@ extensions: []
|
|
55
55
|
extra_rdoc_files: []
|
56
56
|
|
57
57
|
files:
|
58
|
-
-
|
59
|
-
-
|
58
|
+
- README.md
|
59
|
+
- rakefile
|
60
60
|
- s4.gemspec
|
61
|
+
- lib/s4.rb
|
61
62
|
- test/s4_test.rb
|
62
63
|
homepage: http://github.com/benalavi/s4
|
63
64
|
licenses: []
|