Commit 75f413da by Prem Sichanugrist

Use AWS::SDK instead of AWS::S3

The original commit came from @amazonwebservices and @trevorrowe in #579.
I had to revise the commit and make sure all the test cases are passing.
All credits still goes to those guys, thanks a lot!
parent 5f3b88d1
PATH
remote: .
specs:
paperclip (2.4.5)
activerecord (>= 2.3.0)
activesupport (>= 2.3.2)
cocaine (>= 0.0.2)
mime-types
GEM
remote: http://rubygems.org/
specs:
activemodel (3.1.0)
activesupport (= 3.1.0)
bcrypt-ruby (~> 3.0.0)
activemodel (3.1.2)
activesupport (= 3.1.2)
builder (~> 3.0.0)
i18n (~> 0.6)
activerecord (3.1.0)
activemodel (= 3.1.0)
activesupport (= 3.1.0)
activerecord (3.1.2)
activemodel (= 3.1.2)
activesupport (= 3.1.2)
arel (~> 2.2.1)
tzinfo (~> 0.3.29)
activesupport (3.1.0)
activesupport (3.1.2)
multi_json (~> 1.0)
appraisal (0.3.8)
appraisal (0.4.0)
bundler
rake
arel (2.2.1)
......@@ -23,13 +31,13 @@ GEM
cucumber (>= 1.0.2)
rdiscount (>= 1.6.8)
rspec (>= 2.6.0)
aws-s3 (0.6.2)
builder
mime-types
xml-simple
aws-sdk (1.2.1)
httparty (~> 0.7)
json (~> 1.4)
nokogiri (>= 1.4.4)
uuidtools (~> 2.1)
bcat (0.6.2)
rack (~> 1.0)
bcrypt-ruby (3.0.1)
builder (3.0.0)
capybara (1.1.1)
mime-types (>= 1.16)
......@@ -41,7 +49,6 @@ GEM
childprocess (0.2.2)
ffi (~> 1.0.6)
cocaine (0.2.0)
coderay (1.0.0)
cucumber (1.0.6)
builder (>= 2.1.2)
diff-lcs (>= 1.1.2)
......@@ -65,31 +72,27 @@ GEM
formatador (0.2.1)
gherkin (2.4.21)
json (>= 1.4.6)
httparty (0.8.1)
multi_json
multi_xml
i18n (0.6.0)
json (1.6.1)
json_pure (1.6.1)
metaclass (0.0.1)
method_source (0.6.5)
ruby_parser (>= 2.0.5)
mime-types (1.16)
mocha (0.10.0)
metaclass (~> 0.0.1)
multi_json (1.0.3)
multi_xml (0.4.1)
net-scp (1.0.4)
net-ssh (>= 1.99.1)
net-ssh (2.1.4)
nokogiri (1.5.0)
pry (0.9.6)
coderay (>= 0.9.8)
method_source (>= 0.6.5)
ruby_parser (>= 2.0.5)
slop (~> 2.1.0)
rack (1.3.3)
rack-test (0.6.1)
rack (>= 1.0)
rake (0.9.2)
rdiscount (1.6.8)
rdoc (3.9.4)
rspec (2.6.0)
rspec-core (~> 2.6.0)
rspec-expectations (~> 2.6.0)
......@@ -99,21 +102,17 @@ GEM
diff-lcs (~> 1.1.2)
rspec-mocks (2.6.0)
ruby-hmac (0.4.0)
ruby_parser (2.3.1)
sexp_processor (~> 3.0)
rubyzip (0.9.4)
selenium-webdriver (2.7.0)
childprocess (>= 0.2.1)
ffi (>= 1.0.7)
json_pure
rubyzip
sexp_processor (3.0.7)
shoulda (2.11.3)
slop (2.1.0)
sqlite3 (1.3.4)
term-ansicolor (1.0.6)
tzinfo (0.3.29)
xml-simple (1.1.0)
tzinfo (0.3.31)
uuidtools (2.1.2)
xpath (0.1.4)
nokogiri (~> 1.3)
......@@ -121,10 +120,9 @@ PLATFORMS
ruby
DEPENDENCIES
activerecord
appraisal
appraisal (~> 0.4.0)
aruba
aws-s3
aws-sdk
bundler
capybara
cocaine (~> 0.2)
......@@ -132,10 +130,8 @@ DEPENDENCIES
fakeweb
fog
jruby-openssl
mime-types
mocha
pry
paperclip!
rake
rdoc
shoulda
sqlite3 (~> 1.3.4)
......@@ -10,7 +10,7 @@ Given /^I generate a new rails application$/ do
gem "sqlite3"
gem "capybara"
gem "gherkin"
gem "aws-s3"
gem "aws-sdk"
"""
And I configure the application to use "paperclip" from this project
And I reset Bundler environment variable
......
When /^I attach the file "([^"]*)" to "([^"]*)" on S3$/ do |file_path, field|
definition = User.attachment_definitions[field.downcase.to_sym]
path = "http://s3.amazonaws.com/paperclip#{definition[:path]}"
path = "https://paperclip.s3.amazonaws.com#{definition[:path]}"
path.gsub!(':filename', File.basename(file_path))
path.gsub!(/:([^\/\.]+)/) do |match|
"([^\/\.]+)"
......
PATH
remote: ../
specs:
paperclip (2.4.5)
activerecord (>= 2.3.0)
activesupport (>= 2.3.2)
cocaine (>= 0.0.2)
mime-types
GEM
remote: http://rubygems.org/
specs:
actionmailer (2.3.14)
actionpack (= 2.3.14)
actionpack (2.3.14)
activesupport (= 2.3.14)
rack (~> 1.1.0)
activerecord (2.3.14)
activesupport (= 2.3.14)
activeresource (2.3.14)
activesupport (= 2.3.14)
activesupport (2.3.14)
appraisal (0.4.0)
bundler
rake
aruba (0.4.6)
bcat (>= 0.6.1)
childprocess (>= 0.2.0)
cucumber (>= 1.0.2)
rdiscount (>= 1.6.8)
rspec (>= 2.6.0)
aws-s3 (0.6.2)
builder
mime-types
xml-simple
bcat (0.6.2)
rack (~> 1.0)
builder (3.0.0)
capybara (1.1.1)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
selenium-webdriver (~> 2.0)
xpath (~> 0.1.4)
childprocess (0.2.2)
ffi (~> 1.0.6)
cocaine (0.2.0)
cucumber (1.0.6)
builder (>= 2.1.2)
diff-lcs (>= 1.1.2)
gherkin (~> 2.4.18)
json (>= 1.4.6)
term-ansicolor (>= 1.0.6)
diff-lcs (1.1.3)
excon (0.7.4)
fakeweb (1.3.0)
ffi (1.0.9)
fog (1.0.0)
builder
excon (~> 0.7.3)
formatador (~> 0.2.0)
mime-types
multi_json (~> 1.0.3)
net-scp (~> 1.0.4)
net-ssh (~> 2.1.4)
nokogiri (~> 1.5.0)
ruby-hmac
formatador (0.2.1)
gherkin (2.4.21)
json (>= 1.4.6)
json (1.6.1)
json_pure (1.6.1)
metaclass (0.0.1)
mime-types (1.16)
mocha (0.10.0)
metaclass (~> 0.0.1)
multi_json (1.0.3)
net-scp (1.0.4)
net-ssh (>= 1.99.1)
net-ssh (2.1.4)
nokogiri (1.5.0)
rack (1.1.2)
rack-test (0.6.1)
rack (>= 1.0)
rails (2.3.14)
actionmailer (= 2.3.14)
actionpack (= 2.3.14)
activerecord (= 2.3.14)
activeresource (= 2.3.14)
activesupport (= 2.3.14)
rake (>= 0.8.3)
rake (0.9.2)
rdiscount (1.6.8)
rspec (2.6.0)
rspec-core (~> 2.6.0)
rspec-expectations (~> 2.6.0)
rspec-mocks (~> 2.6.0)
rspec-core (2.6.4)
rspec-expectations (2.6.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.6.0)
ruby-hmac (0.4.0)
rubyzip (0.9.4)
selenium-webdriver (2.7.0)
childprocess (>= 0.2.1)
ffi (>= 1.0.7)
json_pure
rubyzip
shoulda (2.11.3)
sqlite3 (1.3.4)
term-ansicolor (1.0.6)
xml-simple (1.1.0)
xpath (0.1.4)
nokogiri (~> 1.3)
PLATFORMS
ruby
DEPENDENCIES
appraisal (~> 0.4.0)
aruba
aws-s3
bundler
capybara
cocaine (~> 0.2)
cucumber (~> 1.0.0)
fakeweb
fog
jruby-openssl
mocha
paperclip!
rails (~> 2.3.14)
rake
shoulda
sqlite3 (~> 1.3.4)
PATH
remote: ../
specs:
paperclip (2.4.5)
activerecord (>= 2.3.0)
activesupport (>= 2.3.2)
cocaine (>= 0.0.2)
mime-types
GEM
remote: http://rubygems.org/
specs:
abstract (1.0.0)
actionmailer (3.0.10)
actionpack (= 3.0.10)
mail (~> 2.2.19)
actionpack (3.0.10)
activemodel (= 3.0.10)
activesupport (= 3.0.10)
builder (~> 2.1.2)
erubis (~> 2.6.6)
i18n (~> 0.5.0)
rack (~> 1.2.1)
rack-mount (~> 0.6.14)
rack-test (~> 0.5.7)
tzinfo (~> 0.3.23)
activemodel (3.0.10)
activesupport (= 3.0.10)
builder (~> 2.1.2)
i18n (~> 0.5.0)
activerecord (3.0.10)
activemodel (= 3.0.10)
activesupport (= 3.0.10)
arel (~> 2.0.10)
tzinfo (~> 0.3.23)
activeresource (3.0.10)
activemodel (= 3.0.10)
activesupport (= 3.0.10)
activesupport (3.0.10)
appraisal (0.4.0)
bundler
rake
arel (2.0.10)
aruba (0.4.6)
bcat (>= 0.6.1)
childprocess (>= 0.2.0)
cucumber (>= 1.0.2)
rdiscount (>= 1.6.8)
rspec (>= 2.6.0)
aws-s3 (0.6.2)
builder
mime-types
xml-simple
bcat (0.6.2)
rack (~> 1.0)
builder (2.1.2)
capybara (1.1.1)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
selenium-webdriver (~> 2.0)
xpath (~> 0.1.4)
childprocess (0.2.2)
ffi (~> 1.0.6)
cocaine (0.2.0)
cucumber (1.0.6)
builder (>= 2.1.2)
diff-lcs (>= 1.1.2)
gherkin (~> 2.4.18)
json (>= 1.4.6)
term-ansicolor (>= 1.0.6)
diff-lcs (1.1.3)
erubis (2.6.6)
abstract (>= 1.0.0)
excon (0.7.4)
fakeweb (1.3.0)
ffi (1.0.9)
fog (1.0.0)
builder
excon (~> 0.7.3)
formatador (~> 0.2.0)
mime-types
multi_json (~> 1.0.3)
net-scp (~> 1.0.4)
net-ssh (~> 2.1.4)
nokogiri (~> 1.5.0)
ruby-hmac
formatador (0.2.1)
gherkin (2.4.21)
json (>= 1.4.6)
i18n (0.5.0)
json (1.6.1)
json_pure (1.6.1)
mail (2.2.19)
activesupport (>= 2.3.6)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
metaclass (0.0.1)
mime-types (1.16)
mocha (0.10.0)
metaclass (~> 0.0.1)
multi_json (1.0.3)
net-scp (1.0.4)
net-ssh (>= 1.99.1)
net-ssh (2.1.4)
nokogiri (1.5.0)
polyglot (0.3.2)
rack (1.2.4)
rack-mount (0.6.14)
rack (>= 1.0.0)
rack-test (0.5.7)
rack (>= 1.0)
rails (3.0.10)
actionmailer (= 3.0.10)
actionpack (= 3.0.10)
activerecord (= 3.0.10)
activeresource (= 3.0.10)
activesupport (= 3.0.10)
bundler (~> 1.0)
railties (= 3.0.10)
railties (3.0.10)
actionpack (= 3.0.10)
activesupport (= 3.0.10)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (~> 0.14.4)
rake (0.9.2)
rdiscount (1.6.8)
rdoc (3.9.4)
rspec (2.6.0)
rspec-core (~> 2.6.0)
rspec-expectations (~> 2.6.0)
rspec-mocks (~> 2.6.0)
rspec-core (2.6.4)
rspec-expectations (2.6.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.6.0)
ruby-hmac (0.4.0)
rubyzip (0.9.4)
selenium-webdriver (2.7.0)
childprocess (>= 0.2.1)
ffi (>= 1.0.7)
json_pure
rubyzip
shoulda (2.11.3)
sqlite3 (1.3.4)
term-ansicolor (1.0.6)
thor (0.14.6)
treetop (1.4.10)
polyglot
polyglot (>= 0.3.1)
tzinfo (0.3.30)
xml-simple (1.1.0)
xpath (0.1.4)
nokogiri (~> 1.3)
PLATFORMS
ruby
DEPENDENCIES
appraisal (~> 0.4.0)
aruba
aws-s3
bundler
capybara
cocaine (~> 0.2)
cucumber (~> 1.0.0)
fakeweb
fog
jruby-openssl
mocha
paperclip!
rails (~> 3.0.10)
rake
shoulda
sqlite3 (~> 1.3.4)
PATH
remote: ../
specs:
paperclip (2.4.5)
activerecord (>= 2.3.0)
activesupport (>= 2.3.2)
cocaine (>= 0.0.2)
mime-types
GEM
remote: http://rubygems.org/
specs:
actionmailer (3.1.0)
actionpack (= 3.1.0)
mail (~> 2.3.0)
actionpack (3.1.0)
activemodel (= 3.1.0)
activesupport (= 3.1.0)
builder (~> 3.0.0)
erubis (~> 2.7.0)
i18n (~> 0.6)
rack (~> 1.3.2)
rack-cache (~> 1.0.3)
rack-mount (~> 0.8.2)
rack-test (~> 0.6.1)
sprockets (~> 2.0.0)
activemodel (3.1.0)
activesupport (= 3.1.0)
bcrypt-ruby (~> 3.0.0)
builder (~> 3.0.0)
i18n (~> 0.6)
activerecord (3.1.0)
activemodel (= 3.1.0)
activesupport (= 3.1.0)
arel (~> 2.2.1)
tzinfo (~> 0.3.29)
activeresource (3.1.0)
activemodel (= 3.1.0)
activesupport (= 3.1.0)
activesupport (3.1.0)
multi_json (~> 1.0)
appraisal (0.4.0)
bundler
rake
arel (2.2.1)
aruba (0.4.6)
bcat (>= 0.6.1)
childprocess (>= 0.2.0)
cucumber (>= 1.0.2)
rdiscount (>= 1.6.8)
rspec (>= 2.6.0)
aws-s3 (0.6.2)
builder
mime-types
xml-simple
bcat (0.6.2)
rack (~> 1.0)
bcrypt-ruby (3.0.1)
builder (3.0.0)
capybara (1.1.1)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
selenium-webdriver (~> 2.0)
xpath (~> 0.1.4)
childprocess (0.2.2)
ffi (~> 1.0.6)
cocaine (0.2.0)
cucumber (1.0.6)
builder (>= 2.1.2)
diff-lcs (>= 1.1.2)
gherkin (~> 2.4.18)
json (>= 1.4.6)
term-ansicolor (>= 1.0.6)
diff-lcs (1.1.3)
erubis (2.7.0)
excon (0.7.4)
fakeweb (1.3.0)
ffi (1.0.9)
fog (1.0.0)
builder
excon (~> 0.7.3)
formatador (~> 0.2.0)
mime-types
multi_json (~> 1.0.3)
net-scp (~> 1.0.4)
net-ssh (~> 2.1.4)
nokogiri (~> 1.5.0)
ruby-hmac
formatador (0.2.1)
gherkin (2.4.21)
json (>= 1.4.6)
hike (1.2.1)
i18n (0.6.0)
json (1.6.1)
json_pure (1.6.1)
mail (2.3.0)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
metaclass (0.0.1)
mime-types (1.16)
mocha (0.10.0)
metaclass (~> 0.0.1)
multi_json (1.0.3)
net-scp (1.0.4)
net-ssh (>= 1.99.1)
net-ssh (2.1.4)
nokogiri (1.5.0)
polyglot (0.3.2)
rack (1.3.3)
rack-cache (1.0.3)
rack (>= 0.4)
rack-mount (0.8.3)
rack (>= 1.0.0)
rack-ssl (1.3.2)
rack
rack-test (0.6.1)
rack (>= 1.0)
rails (3.1.0)
actionmailer (= 3.1.0)
actionpack (= 3.1.0)
activerecord (= 3.1.0)
activeresource (= 3.1.0)
activesupport (= 3.1.0)
bundler (~> 1.0)
railties (= 3.1.0)
railties (3.1.0)
actionpack (= 3.1.0)
activesupport (= 3.1.0)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (~> 0.14.6)
rake (0.9.2)
rdiscount (1.6.8)
rdoc (3.9.4)
rspec (2.6.0)
rspec-core (~> 2.6.0)
rspec-expectations (~> 2.6.0)
rspec-mocks (~> 2.6.0)
rspec-core (2.6.4)
rspec-expectations (2.6.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.6.0)
ruby-hmac (0.4.0)
rubyzip (0.9.4)
selenium-webdriver (2.7.0)
childprocess (>= 0.2.1)
ffi (>= 1.0.7)
json_pure
rubyzip
shoulda (2.11.3)
sprockets (2.0.1)
hike (~> 1.2)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.4)
term-ansicolor (1.0.6)
thor (0.14.6)
tilt (1.3.3)
treetop (1.4.10)
polyglot
polyglot (>= 0.3.1)
tzinfo (0.3.30)
xml-simple (1.1.0)
xpath (0.1.4)
nokogiri (~> 1.3)
PLATFORMS
ruby
DEPENDENCIES
appraisal (~> 0.4.0)
aruba
aws-s3
bundler
capybara
cocaine (~> 0.2)
cucumber (~> 1.0.0)
fakeweb
fog
jruby-openssl
mocha
paperclip!
rails (~> 3.1.0)
rake
shoulda
sqlite3 (~> 1.3.4)
......@@ -67,12 +67,21 @@ module Paperclip
# S3 (strictly speaking) does not support directories, you can still use a / to
# separate parts of your file name.
# * +s3_host_name+: If you are using your bucket in Tokyo region etc, write host_name.
# * +s3_metadata+: These key/value pairs will be stored with the
# object. This option works by prefixing each key with
# "x-amz-meta-" before sending it as a header on the object
# upload request.
# * +s3_storage_class+: If this option is set to
# <tt>:reduced_redundancy</tt>, the object will be stored using Reduced
# Redundancy Storage. RRS enables customers to reduce their
# costs by storing non-critical, reproducible data at lower
# levels of redundancy than Amazon S3's standard storage.
module S3
def self.extended base
begin
require 'aws/s3'
rescue LoadError => e
e.message << " (You may need to install the aws-s3 gem)"
e.message << " (You may need to install the aws-sdk gem)"
raise e
end unless defined?(AWS::S3)
......@@ -85,7 +94,19 @@ module Paperclip
permission = permission.call(attachment, style) if permission.is_a?(Proc)
(permission == :public_read) ? 'http' : 'https'
end
@s3_headers = @options[:s3_headers] || {}
@s3_metadata = @options[:s3_metadata] || {}
@s3_headers = (@options[:s3_headers] || {}).inject({}) do |headers,(name,value)|
case name.to_s
when /^x-amz-meta-(.*)/i
@s3_metadata[$1.downcase] = value
else
name = name.to_s.downcase.sub(/^x-amz-/,'').tr("-","_").to_sym
headers[name] = value
end
headers
end
@s3_headers[:storage_class] = @options[:s3_storage_class] if @options[:s3_storage_class]
unless @options[:url].to_s.match(/^:s3.*url$/) || @options[:url] == ":asset_host"
@options[:path] = @options[:path].gsub(/:url/, @options[:url]).gsub(/^:rails_root\/public\/system/, '')
......@@ -94,9 +115,6 @@ module Paperclip
@options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol)
@http_proxy = @options[:http_proxy] || nil
if @http_proxy
@s3_options.merge!({:proxy => @http_proxy})
end
end
Paperclip.interpolates(:s3_alias_url) do |attachment, style|
"#{attachment.s3_protocol(style)}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
......@@ -113,7 +131,9 @@ module Paperclip
end
def expiring_url(time = 3600, style_name = default_style)
path.nil? ? nil : s3_object.url_for(path(style_name), bucket_name, :expires_in => time, :use_ssl => (s3_protocol(style_name) == 'https'))
if path
s3_object(style_name).url_for(:read, :expires => time, :secure => use_secure_protocol?(style_name)).to_s
end
end
def s3_credentials
......@@ -133,7 +153,39 @@ module Paperclip
def bucket_name
@bucket = @options[:bucket] || s3_credentials[:bucket]
@bucket = @bucket.call(self) if @bucket.is_a?(Proc)
@bucket
@bucket or raise ArgumentError, "missing required :bucket option"
end
def s3_interface
@s3_interface ||= begin
config = { :s3_endpoint => s3_host_name }
if using_http_proxy?
proxy_opts = { :host => http_proxy_host }
proxy_opts[:port] = http_proxy_port if http_proxy_port
if http_proxy_user
userinfo = http_proxy_user.to_s
userinfo += ":#{http_proxy_password}" if http_proxy_password
proxy_opts[:userinfo] = userinfo
end
config[:proxy_uri] = URI::HTTP.build(proxy_opts)
end
[:access_key_id, :secret_access_key].each do |opt|
config[opt] = s3_credentials[opt] if s3_credentials[opt]
end
AWS::S3.new(config.merge(@s3_options))
end
end
def s3_bucket
@s3_bucket ||= s3_interface.buckets[bucket_name]
end
def s3_object style_name = default_style
s3_bucket.objects[path(style_name).sub(%r{^/},'')]
end
def using_http_proxy?
......@@ -173,7 +225,7 @@ module Paperclip
def exists?(style = default_style)
if original_filename
s3_object.exists?(path(style), bucket_name)
s3_object(style).exists?
else
false
end
......@@ -202,30 +254,31 @@ module Paperclip
basename = File.basename(filename, extname)
file = Tempfile.new([basename, extname])
file.binmode
file.write(s3_object.value(path(style), bucket_name))
file.write(s3_object(style).read)
file.rewind
return file
end
def create_bucket
s3_bucket.create(bucket_name)
s3_interface.buckets.create(bucket_name)
end
def flush_writes #:nodoc:
@queued_for_write.each do |style, file|
begin
log("saving #{path(style)}")
s3_object.store(path(style),
file,
bucket_name,
{:content_type => file.content_type.to_s.strip,
:access => s3_permissions(style),
}.merge(@s3_headers))
rescue AWS::S3::NoSuchBucket => e
acl = @s3_permissions[style] || @s3_permissions[:default]
acl = acl.call(self, style) if acl.respond_to?(:call)
write_options = {
:content_type => file.content_type.to_s.strip,
:acl => acl
}
write_options[:metadata] = @s3_metadata unless @s3_metadata.empty?
write_options.merge!(@s3_headers)
s3_object(style).write(file, write_options)
rescue AWS::S3::Errors::NoSuchBucket => e
create_bucket
retry
rescue AWS::S3::ResponseError => e
raise
end
end
......@@ -238,8 +291,8 @@ module Paperclip
@queued_for_delete.each do |path|
begin
log("deleting #{path}")
s3_object.delete(path, bucket_name)
rescue AWS::S3::ResponseError
s3_bucket.objects[path.sub(%r{^/},'')].delete
rescue AWS::Errors::Base => e
# Ignore this.
end
end
......@@ -260,18 +313,6 @@ module Paperclip
end
private :find_credentials
def s3_object
establish_connection!
AWS::S3::S3Object
end
private :s3_object
def s3_bucket
establish_connection!
AWS::S3::Bucket
end
private :s3_bucket
def establish_connection!
@connection ||= AWS::S3::Base.establish_connection!( @s3_options.merge(
:access_key_id => s3_credentials[:access_key_id],
......@@ -279,6 +320,11 @@ module Paperclip
))
end
private :establish_connection!
def use_secure_protocol?(style_name)
s3_protocol(style_name) == "https"
end
private :use_secure_protocol?
end
end
end
......@@ -28,7 +28,7 @@ Gem::Specification.new do |s|
s.add_development_dependency('shoulda')
s.add_development_dependency('appraisal', '~> 0.4.0')
s.add_development_dependency('mocha')
s.add_development_dependency('aws-s3')
s.add_development_dependency('aws-sdk')
s.add_development_dependency('sqlite3', '~> 1.3.4')
s.add_development_dependency('cucumber', '~> 1.0.0')
s.add_development_dependency('aruba')
......
require './test/helper'
require 'aws/s3'
require 'aws'
unless ENV["S3_TEST_BUCKET"].blank?
class S3LiveTest < Test::Unit::TestCase
......@@ -72,7 +72,7 @@ unless ENV["S3_TEST_BUCKET"].blank?
rebuild_model :styles => { :thumb => "100x100", :square => "32x32#" },
:storage => :s3,
:bucket => ENV["S3_TEST_BUCKET"],
:s3_credentials => File.new(File.join(File.dirname(__FILE__), "..", "s3.yml"))
:s3_credentials => File.new(File.join(File.dirname(__FILE__), "..", "fixtures", "s3.yml"))
Dummy.delete_all
@dummy = Dummy.new
......@@ -120,9 +120,9 @@ unless ENV["S3_TEST_BUCKET"].blank?
assert_match /.+\/question\?mark\.png/, @dummy.avatar.path
end
# should "return an escaped version for url" do
# assert_match /.+\/question%3Fmark\.png/, @dummy.avatar.url
# end
should "return an escaped version for url" do
assert_match /.+\/question%3Fmark\.png/, @dummy.avatar.url
end
should "be accessible" do
assert_match /200 OK/, `curl -I "#{@dummy.avatar.url}"`
......
require './test/helper'
require 'aws/s3'
require 'aws'
AWS.stub!
AWS.config(:access_key_id => "TESTKEY", :secret_access_key => "TESTSECRET")
class S3Test < Test::Unit::TestCase
def rails_env(env)
......@@ -11,7 +14,6 @@ class S3Test < Test::Unit::TestCase
context "Parsing S3 credentials" do
setup do
@proxy_settings = {:host => "127.0.0.1", :port => 8888, :user => "foo", :password => "bar"}
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:bucket => "testing",
:http_proxy => @proxy_settings,
......@@ -51,9 +53,73 @@ class S3Test < Test::Unit::TestCase
end
context "missing :bucket option" do
setup do
rebuild_model :storage => :s3,
#:bucket => "testing", # intentionally left out
:s3_credentials => {:not => :important}
@dummy = Dummy.new
@dummy.avatar = StringIO.new(".")
end
should "raise an argument error" do
exception = assert_raise(ArgumentError) { @dummy.save }
assert_match /missing required :bucket option/, exception.message
end
end
context ":bucket option via :s3_credentials" do
setup do
rebuild_model :storage => :s3, :s3_credentials => {:bucket => 'testing'}
@dummy = Dummy.new
end
should "populate #bucket_name" do
assert_equal @dummy.avatar.bucket_name, 'testing'
end
end
context ":bucket option" do
setup do
rebuild_model :storage => :s3, :bucket => "testing", :s3_credentials => {}
@dummy = Dummy.new
end
should "populate #bucket_name" do
assert_equal @dummy.avatar.bucket_name, 'testing'
end
end
context "missing :bucket option" do
setup do
rebuild_model :storage => :s3,
#:bucket => "testing", # intentionally left out
:http_proxy => @proxy_settings,
:s3_credentials => {:not => :important}
@dummy = Dummy.new
@dummy.avatar = StringIO.new(".")
end
should "raise an argument error" do
exception = assert_raise(ArgumentError) { @dummy.save }
assert_match /missing required :bucket option/, exception.message
end
end
context "" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:s3_credentials => {},
:bucket => "bucket",
......@@ -66,11 +132,46 @@ class S3Test < Test::Unit::TestCase
should "return a url based on an S3 path" do
assert_match %r{^http://s3.amazonaws.com/bucket/avatars/stringio.txt}, @dummy.avatar.url
end
should "use the correct bucket" do
assert_equal "bucket", @dummy.avatar.s3_bucket.name
end
should "use the correct key" do
assert_equal "avatars/stringio.txt", @dummy.avatar.s3_object.key
end
end
context "An attachment that uses S3 for storage and has the style in the path" do
setup do
rebuild_model :storage => :s3,
:bucket => "testing",
:path => ":attachment/:style/:basename.:extension",
:styles => {
:thumb => "80x80>"
},
:s3_credentials => {
'access_key_id' => "12345",
'secret_access_key' => "54321"
}
@dummy = Dummy.new
@dummy.avatar = StringIO.new(".")
@avatar = @dummy.avatar
end
should "use an S3 object based on the correct path for the default style" do
assert_equal("avatars/original/stringio.txt", @dummy.avatar.s3_object.key)
end
should "use an S3 object based on the correct path for the custom style" do
assert_equal("avatars/thumb/stringio.txt", @dummy.avatar.s3_object(:thumb).key)
end
end
context "s3_host_name" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:s3_credentials => {},
:bucket => "bucket",
......@@ -83,11 +184,14 @@ class S3Test < Test::Unit::TestCase
should "return a url based on an :s3_host_name path" do
assert_match %r{^http://s3-ap-northeast-1.amazonaws.com/bucket/avatars/stringio.txt}, @dummy.avatar.url
end
should "use the S3 bucket with the correct host name" do
assert_equal "s3-ap-northeast-1.amazonaws.com", @dummy.avatar.s3_bucket.config.s3_endpoint
end
end
context "An attachment that uses S3 for storage and has styles that return different file types" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :styles => { :large => ['500x500#', :jpg] },
:storage => :s3,
:bucket => "bucket",
......@@ -105,14 +209,21 @@ class S3Test < Test::Unit::TestCase
assert_match /.+\/5k.png/, @dummy.avatar.url
end
should 'use the correct key for the original file mime type' do
assert_match /.+\/5k.png/, @dummy.avatar.s3_object.key
end
should "return a url containing the correct processed file mime type" do
assert_match /.+\/5k.jpg/, @dummy.avatar.url(:large)
end
should "use the correct key for the processed file mime type" do
assert_match /.+\/5k.jpg/, @dummy.avatar.s3_object(:large).key
end
end
context "An attachment that uses S3 for storage and has spaces in file name" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :styles => { :large => ['500x500#', :jpg] },
:storage => :s3,
:bucket => "bucket",
......@@ -136,7 +247,6 @@ class S3Test < Test::Unit::TestCase
context "" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:s3_credentials => {},
:bucket => "bucket",
......@@ -153,7 +263,6 @@ class S3Test < Test::Unit::TestCase
context "" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:s3_credentials => {
:production => { :bucket => "prod_bucket" },
......@@ -173,7 +282,6 @@ class S3Test < Test::Unit::TestCase
context "generating a url with a proc as the host alias" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:s3_credentials => { :bucket => "prod_bucket" },
:s3_host_alias => Proc.new{|atch| "cdn#{atch.instance.counter % 4}.example.com"},
......@@ -203,7 +311,6 @@ class S3Test < Test::Unit::TestCase
context "" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:s3_credentials => {},
:bucket => "bucket",
......@@ -220,7 +327,6 @@ class S3Test < Test::Unit::TestCase
context "Generating a secure url with an expiration" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:s3_credentials => {
:production => { :bucket => "prod_bucket" },
......@@ -236,7 +342,9 @@ class S3Test < Test::Unit::TestCase
@dummy = Dummy.new
@dummy.avatar = StringIO.new(".")
AWS::S3::S3Object.expects(:url_for).with("avatars/stringio.txt", "prod_bucket", { :expires_in => 3600, :use_ssl => true })
object = stub
@dummy.avatar.stubs(:s3_object).returns(object)
object.expects(:url_for).with(:read, :expires => 3600, :secure => true)
@dummy.avatar.expiring_url
end
......@@ -246,9 +354,8 @@ class S3Test < Test::Unit::TestCase
end
end
context "Generating a url with an expiration" do
context "Generating a url with an expiration for each style" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:s3_credentials => {
:production => { :bucket => "prod_bucket" },
......@@ -263,22 +370,25 @@ class S3Test < Test::Unit::TestCase
@dummy = Dummy.new
@dummy.avatar = StringIO.new(".")
end
AWS::S3::S3Object.expects(:url_for).with("avatars/original/stringio.txt", "prod_bucket", { :expires_in => 3600, :use_ssl => true })
@dummy.avatar.expiring_url
AWS::S3::S3Object.expects(:url_for).with("avatars/thumb/stringio.txt", "prod_bucket", { :expires_in => 1800, :use_ssl => true })
should "should generate a url for the thumb" do
object = stub
@dummy.avatar.stubs(:s3_object).with(:thumb).returns(object)
object.expects(:url_for).with(:read, :expires => 1800, :secure => true)
@dummy.avatar.expiring_url(1800, :thumb)
end
should "should succeed" do
assert true
should "should generate a url for the default style" do
object = stub
@dummy.avatar.stubs(:s3_object).with(:original).returns(object)
object.expects(:url_for).with(:read, :expires => 1800, :secure => true)
@dummy.avatar.expiring_url(1800)
end
end
context "Parsing S3 credentials with a bucket in them" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:s3_credentials => {
:production => { :bucket => "prod_bucket" },
......@@ -290,18 +400,20 @@ class S3Test < Test::Unit::TestCase
should "get the right bucket in production" do
rails_env("production")
assert_equal "prod_bucket", @dummy.avatar.bucket_name
assert_equal "prod_bucket", @dummy.avatar.s3_bucket.name
end
should "get the right bucket in development" do
rails_env("development")
assert_equal "dev_bucket", @dummy.avatar.bucket_name
assert_equal "dev_bucket", @dummy.avatar.s3_bucket.name
end
end
context "Parsing S3 credentials with a s3_host_name in them" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:bucket => 'testing',
:s3_credentials => {
:production => { :s3_host_name => "s3-world-end.amazonaws.com" },
:development => { :s3_host_name => "s3-ap-northeast-1.amazonaws.com" }
......@@ -312,16 +424,19 @@ class S3Test < Test::Unit::TestCase
should "get the right s3_host_name in production" do
rails_env("production")
assert_match %r{^s3-world-end.amazonaws.com}, @dummy.avatar.s3_host_name
assert_match %r{^s3-world-end.amazonaws.com}, @dummy.avatar.s3_bucket.config.s3_endpoint
end
should "get the right s3_host_name in development" do
rails_env("development")
assert_match %r{^s3-ap-northeast-1.amazonaws.com}, @dummy.avatar.s3_host_name
assert_match %r{^s3-ap-northeast-1.amazonaws.com}, @dummy.avatar.s3_bucket.config.s3_endpoint
end
should "get the right s3_host_name if the key does not exist" do
rails_env("test")
assert_match %r{^s3.amazonaws.com}, @dummy.avatar.s3_host_name
assert_match %r{^s3.amazonaws.com}, @dummy.avatar.s3_bucket.config.s3_endpoint
end
end
......@@ -361,7 +476,11 @@ class S3Test < Test::Unit::TestCase
context "and saved" do
setup do
AWS::S3::S3Object.stubs(:store).with(@dummy.avatar.path, anything, 'testing', :content_type => 'image/png', :access => :public_read)
object = stub
@dummy.avatar.stubs(:s3_object).returns(object)
object.expects(:write).with(anything,
:content_type => "image/png",
:acl => :public_read)
@dummy.save
end
......@@ -371,7 +490,6 @@ class S3Test < Test::Unit::TestCase
end
should "delete tempfiles" do
AWS::S3::S3Object.stubs(:store).with(@dummy.avatar.path, anything, 'testing', :content_type => 'image/png', :access => :public_read)
File.stubs(:exist?).returns(true)
Paperclip::Tempfile.any_instance.expects(:close).at_least_once()
Paperclip::Tempfile.any_instance.expects(:unlink).at_least_once()
......@@ -381,11 +499,12 @@ class S3Test < Test::Unit::TestCase
context "and saved without a bucket" do
setup do
class AWS::S3::NoSuchBucket < AWS::S3::ResponseError
# Force the class to be created as a proper subclass of ResponseError thanks to AWS::S3's autocreation of exceptions
end
AWS::S3::Bucket.expects(:create).with("testing")
AWS::S3::S3Object.stubs(:store).raises(AWS::S3::NoSuchBucket.new(:message, :response)).then.returns(true)
AWS::S3::BucketCollection.any_instance.expects(:create).with("testing")
AWS::S3::S3Object.any_instance.stubs(:write).
raises(AWS::S3::Errors::NoSuchBucket.new(stub,
stub(:status => 404,
:body => "<foo/>"))).
then.returns(nil)
@dummy.save
end
......@@ -396,8 +515,8 @@ class S3Test < Test::Unit::TestCase
context "and remove" do
setup do
AWS::S3::S3Object.stubs(:exists?).returns(true)
AWS::S3::S3Object.stubs(:delete)
AWS::S3::S3Object.any_instance.stubs(:exists?).returns(true)
AWS::S3::S3Object.any_instance.stubs(:delete)
@dummy.destroy_attached_files
end
......@@ -410,7 +529,6 @@ class S3Test < Test::Unit::TestCase
context "An attachment with S3 storage and bucket defined as a Proc" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:bucket => lambda { |attachment| "bucket_#{attachment.instance.other}" },
:s3_credentials => {:not => :important}
......@@ -418,13 +536,14 @@ class S3Test < Test::Unit::TestCase
should "get the right bucket name" do
assert "bucket_a", Dummy.new(:other => 'a').avatar.bucket_name
assert "bucket_a", Dummy.new(:other => 'a').avatar.s3_bucket.name
assert "bucket_b", Dummy.new(:other => 'b').avatar.bucket_name
assert "bucket_b", Dummy.new(:other => 'b').avatar.s3_bucket.name
end
end
context "An attachment with S3 storage and specific s3 headers set" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:bucket => "testing",
:path => ":attachment/:style/:basename.:extension",
......@@ -446,13 +565,168 @@ class S3Test < Test::Unit::TestCase
context "and saved" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
AWS::S3::S3Object.stubs(:store).with(@dummy.avatar.path,
anything,
'testing',
:content_type => 'image/png',
:access => :public_read,
'Cache-Control' => 'max-age=31557600')
object = stub
@dummy.avatar.stubs(:s3_object).returns(object)
object.expects(:write).with(anything,
:content_type => "image/png",
:acl => :public_read,
:cache_control => 'max-age=31557600')
@dummy.save
end
should "succeed" do
assert true
end
end
end
end
context "An attachment with S3 storage and metadata set using header names" do
setup do
rebuild_model :storage => :s3,
:bucket => "testing",
:path => ":attachment/:style/:basename.:extension",
:s3_credentials => {
'access_key_id' => "12345",
'secret_access_key' => "54321"
},
:s3_headers => {'x-amz-meta-color' => 'red'}
end
context "when assigned" do
setup do
@file = File.new(File.join(File.dirname(__FILE__), '..', 'fixtures', '5k.png'), 'rb')
@dummy = Dummy.new
@dummy.avatar = @file
end
teardown { @file.close }
context "and saved" do
setup do
object = stub
@dummy.avatar.stubs(:s3_object).returns(object)
object.expects(:write).with(anything,
:content_type => "image/png",
:acl => :public_read,
:metadata => { "color" => "red" })
@dummy.save
end
should "succeed" do
assert true
end
end
end
end
context "An attachment with S3 storage and metadata set using the :s3_metadata option" do
setup do
rebuild_model :storage => :s3,
:bucket => "testing",
:path => ":attachment/:style/:basename.:extension",
:s3_credentials => {
'access_key_id' => "12345",
'secret_access_key' => "54321"
},
:s3_metadata => { "color" => "red" }
end
context "when assigned" do
setup do
@file = File.new(File.join(File.dirname(__FILE__), '..', 'fixtures', '5k.png'), 'rb')
@dummy = Dummy.new
@dummy.avatar = @file
end
teardown { @file.close }
context "and saved" do
setup do
object = stub
@dummy.avatar.stubs(:s3_object).returns(object)
object.expects(:write).with(anything,
:content_type => "image/png",
:acl => :public_read,
:metadata => { "color" => "red" })
@dummy.save
end
should "succeed" do
assert true
end
end
end
end
context "An attachment with S3 storage and storage class set using the header name" do
setup do
rebuild_model :storage => :s3,
:bucket => "testing",
:path => ":attachment/:style/:basename.:extension",
:s3_credentials => {
'access_key_id' => "12345",
'secret_access_key' => "54321"
},
:s3_headers => { "x-amz-storage-class" => "reduced_redundancy" }
end
context "when assigned" do
setup do
@file = File.new(File.join(File.dirname(__FILE__), '..', 'fixtures', '5k.png'), 'rb')
@dummy = Dummy.new
@dummy.avatar = @file
end
teardown { @file.close }
context "and saved" do
setup do
object = stub
@dummy.avatar.stubs(:s3_object).returns(object)
object.expects(:write).with(anything,
:content_type => "image/png",
:acl => :public_read,
:storage_class => "reduced_redundancy")
@dummy.save
end
should "succeed" do
assert true
end
end
end
end
context "An attachment with S3 storage and storage class set using the :storage_class option" do
setup do
rebuild_model :storage => :s3,
:bucket => "testing",
:path => ":attachment/:style/:basename.:extension",
:s3_credentials => {
'access_key_id' => "12345",
'secret_access_key' => "54321"
},
:s3_storage_class => :reduced_redundancy
end
context "when assigned" do
setup do
@file = File.new(File.join(File.dirname(__FILE__), '..', 'fixtures', '5k.png'), 'rb')
@dummy = Dummy.new
@dummy.avatar = @file
end
teardown { @file.close }
context "and saved" do
setup do
object = stub
@dummy.avatar.stubs(:s3_object).returns(object)
object.expects(:write).with(anything,
:content_type => "image/png",
:acl => :public_read,
:storage_class => :reduced_redundancy)
@dummy.save
end
......@@ -476,13 +750,12 @@ class S3Test < Test::Unit::TestCase
Dummy.delete_all
@dummy = Dummy.new
@dummy.avatar.send(:establish_connection!)
end
should "parse the credentials" do
assert_equal 'pathname_bucket', @dummy.avatar.bucket_name
assert_equal 'pathname_key', AWS::S3::Base.connection.options[:access_key_id]
assert_equal 'pathname_secret', AWS::S3::Base.connection.options[:secret_access_key]
assert_equal 'pathname_key', @dummy.avatar.s3_bucket.config.access_key_id
assert_equal 'pathname_secret', @dummy.avatar.s3_bucket.config.secret_access_key
end
end
......@@ -500,13 +773,12 @@ class S3Test < Test::Unit::TestCase
Dummy.delete_all
@dummy = Dummy.new
@dummy.avatar.send(:establish_connection!)
end
should "run the file through ERB" do
assert_equal 'env_bucket', @dummy.avatar.bucket_name
assert_equal 'env_key', AWS::S3::Base.connection.options[:access_key_id]
assert_equal 'env_secret', AWS::S3::Base.connection.options[:secret_access_key]
assert_equal 'env_key', @dummy.avatar.s3_bucket.config.access_key_id
assert_equal 'env_secret', @dummy.avatar.s3_bucket.config.secret_access_key
end
end
......@@ -533,12 +805,11 @@ class S3Test < Test::Unit::TestCase
context "and saved" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
AWS::S3::S3Object.expects(:store).with(@dummy.avatar.path,
anything,
'testing',
:content_type => 'image/png',
:access => :public_read)
object = stub
@dummy.avatar.stubs(:s3_object).returns(object)
object.expects(:write).with(anything,
:content_type => "image/png",
:acl => :public_read)
@dummy.save
end
......@@ -572,12 +843,11 @@ class S3Test < Test::Unit::TestCase
context "and saved" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
AWS::S3::S3Object.expects(:store).with(@dummy.avatar.path,
anything,
'testing',
:content_type => 'image/png',
:access => :private)
object = stub
@dummy.avatar.stubs(:s3_object).returns(object)
object.expects(:write).with(anything,
:content_type => "image/png",
:acl => :private)
@dummy.save
end
......@@ -617,13 +887,12 @@ class S3Test < Test::Unit::TestCase
context "and saved" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
[:thumb, :original].each do |style|
AWS::S3::S3Object.expects(:store).with("avatars/#{style}/5k.png",
anything,
'testing',
:content_type => 'image/png',
:access => style == :thumb ? :public_read : :private)
object = stub
@dummy.avatar.stubs(:s3_object).with(style).returns(object)
object.expects(:write).with(anything,
:content_type => "image/png",
:acl => style == :thumb ? :public_read : :private)
end
@dummy.save
end
......@@ -666,15 +935,12 @@ class S3Test < Test::Unit::TestCase
context "and saved" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
[:thumb, :original].each do |style|
AWS::S3::S3Object.expects(:store).with(
"avatars/#{style}/5k.png",
anything,
'testing',
:content_type => 'image/png',
:access => style == :thumb ? :public_read : :private
)
object = stub
@dummy.avatar.stubs(:s3_object).with(style).returns(object)
object.expects(:write).with(anything,
:content_type => "image/png",
:acl => style == :thumb ? :public_read : :private)
end
@dummy.save
end
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment