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 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.2"
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(s3_url=ENV["S3_URL"])
32
- new(s3_url).tap do |s4|
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 an S3 bucket.
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(s3_url=ENV["S3_URL"])
47
- new(s3_url).tap do |s4|
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
- raise NoSuchBucket.new(bucket) if request(uri("/", query: "location")).nil?
76
- end
104
+ location
105
+ end
77
106
 
78
107
  # Create the S3 bucket.
79
- def create
80
- uri = URI::HTTP.build(host: host, path: "/#{bucket}")
81
- request uri, Net::HTTP::Put.new(uri.request_uri)
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
- def put(io, name)
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
- req.body_stream = io
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.add_field "Content-Type", "application/x-www-form-urlencoded"
182
+ req.body_stream = io
130
183
 
131
- request(URI::HTTP.build(host: host, path: "/#{bucket}/#{name}"), req)
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.from_response(response)
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 "Authorization", "AWS #{access_key_id}:#{signature(uri, request)}"
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
- "#{request.class::METHOD}\n\n#{request["Content-Type"]}\n#{request.fetch("Date")}\n" + uri.path + (query ? "?#{query}" : "")
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 self.from_response(response)
211
- doc = REXML::Document.new(response.body).elements["//Error"]
212
- code = doc.elements["Code"].text
213
- message = doc.elements["Message"].text
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 "#{@code}: #{message}"
359
+ super "#{@status}: #{@code} -- " + @response.elements["Message"].text
223
360
  end
224
361
  end
225
362
  end
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
- "LICENSE",
14
- "README.markdown",
15
- "Rakefile",
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", "~> 1.7"
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
- def fixture(filename="")
13
- File.join(File.dirname(__FILE__), "fixtures", filename)
14
- end
16
+ TestBucket = S4.connect.bucket
17
+ NewBucket = ENV["S4_NEW_BUCKET"]
15
18
 
16
- def output(filename="")
17
- File.join(File.dirname(__FILE__), "output", filename)
18
- end
19
+ class S4Test < Test::Unit::TestCase
20
+ def fixture(filename="")
21
+ File.join(File.dirname(__FILE__), "fixtures", filename)
22
+ end
19
23
 
20
- NewBucket = "s4-bucketthatdoesntexist"
21
- TestBucket = "s4-test-bucket"
24
+ def output(filename="")
25
+ File.join(File.dirname(__FILE__), "output", filename)
26
+ end
22
27
 
23
- class S4Test < Test::Unit::TestCase
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(ENV["S3_URL"].sub(TestBucket, "foo"))
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(ENV["S3_URL"].sub(TestBucket, "foo"))
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
- s4 = S4.create ENV["S3_URL"].sub(TestBucket, NewBucket)
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
- `s3cmd del 's3://#{@s4.bucket}/boom.txt'`
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.2
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-01 00:00:00 Z
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
- - Rakefile
59
- - lib/s4.rb
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: []