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 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: []