s4 0.0.2 → 0.0.3
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/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: []
|