Commit eb45a619 by Isaac Betesh Committed by Jon Yurek

prelimiary steps to supporting aws-sdk-v2: Upgraded Paperclip::Storage::S3 class and the live spec

parent 3cb13318
...@@ -41,7 +41,7 @@ module Paperclip ...@@ -41,7 +41,7 @@ module Paperclip
# * +s3_permissions+: This is a String that should be one of the "canned" access # * +s3_permissions+: This is a String that should be one of the "canned" access
# policies that S3 provides (more information can be found here: # policies that S3 provides (more information can be found here:
# http://docs.aws.amazon.com/AmazonS3/latest/dev/ACLOverview.html) # http://docs.aws.amazon.com/AmazonS3/latest/dev/ACLOverview.html)
# The default for Paperclip is :public_read. # The default for Paperclip is :public_read (aws-sdk v1) / public-read (aws-sdk v2).
# #
# You can set permission on a per style bases by doing the following: # You can set permission on a per style bases by doing the following:
# :s3_permissions => { # :s3_permissions => {
...@@ -93,6 +93,7 @@ module Paperclip ...@@ -93,6 +93,7 @@ module Paperclip
# S3 (strictly speaking) does not support directories, you can still use a / to # S3 (strictly speaking) does not support directories, you can still use a / to
# separate parts of your file name. # separate parts of your file name.
# * +s3_host_name+: If you are using your bucket in Tokyo region etc, write host_name. # * +s3_host_name+: If you are using your bucket in Tokyo region etc, write host_name.
# * +s3_region+: For aws-sdk v2, s3_region is required.
# * +s3_metadata+: These key/value pairs will be stored with the # * +s3_metadata+: These key/value pairs will be stored with the
# object. This option works by prefixing each key with # object. This option works by prefixing each key with
# "x-amz-meta-" before sending it as a header on the object # "x-amz-meta-" before sending it as a header on the object
...@@ -114,20 +115,22 @@ module Paperclip ...@@ -114,20 +115,22 @@ module Paperclip
def self.extended base def self.extended base
begin begin
require 'aws-sdk' require 'aws-sdk'
const_set('AWS_CLASS', defined?(::AWS) ? ::AWS : ::Aws)
const_set('DEFAULT_PERMISSION', defined?(::AWS) ? :public_read : :'public-read')
rescue LoadError => e rescue LoadError => e
e.message << " (You may need to install the aws-sdk gem)" e.message << " (You may need to install the aws-sdk gem)"
raise e raise e
end unless defined?(AWS::Core) end unless defined?(AWS_CLASS)
# Overriding log formatter to make sure it return a UTF-8 string # Overriding log formatter to make sure it return a UTF-8 string
if defined?(AWS::Core::LogFormatter) if defined?(AWS_CLASS::Core::LogFormatter)
AWS::Core::LogFormatter.class_eval do AWS_CLASS::Core::LogFormatter.class_eval do
def summarize_hash(hash) def summarize_hash(hash)
hash.map { |key, value| ":#{key}=>#{summarize_value(value)}".force_encoding('UTF-8') }.sort.join(',') hash.map { |key, value| ":#{key}=>#{summarize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
end end
end end
elsif defined?(AWS::Core::ClientLogging) elsif defined?(AWS_CLASS::Core::ClientLogging)
AWS::Core::ClientLogging.class_eval do AWS_CLASS::Core::ClientLogging.class_eval do
def sanitize_hash(hash) def sanitize_hash(hash)
hash.map { |key, value| "#{sanitize_value(key)}=>#{sanitize_value(value)}".force_encoding('UTF-8') }.sort.join(',') hash.map { |key, value| "#{sanitize_value(key)}=>#{sanitize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
end end
...@@ -141,7 +144,7 @@ module Paperclip ...@@ -141,7 +144,7 @@ module Paperclip
Proc.new do |style, attachment| Proc.new do |style, attachment|
permission = (@s3_permissions[style.to_s.to_sym] || @s3_permissions[:default]) permission = (@s3_permissions[style.to_s.to_sym] || @s3_permissions[:default])
permission = permission.call(attachment, style) if permission.respond_to?(:call) permission = permission.call(attachment, style) if permission.respond_to?(:call)
(permission == :public_read) ? 'http' : 'https' (permission == DEFAULT_PERMISSION) ? 'http' : 'https'
end end
@s3_metadata = @options[:s3_metadata] || {} @s3_metadata = @options[:s3_metadata] || {}
@s3_headers = {} @s3_headers = {}
...@@ -149,7 +152,7 @@ module Paperclip ...@@ -149,7 +152,7 @@ module Paperclip
@s3_storage_class = set_storage_class(@options[:s3_storage_class]) @s3_storage_class = set_storage_class(@options[:s3_storage_class])
@s3_server_side_encryption = :aes256 @s3_server_side_encryption = aws_v1? ? :aes256 : "AES256"
if @options[:s3_server_side_encryption].blank? if @options[:s3_server_side_encryption].blank?
@s3_server_side_encryption = false @s3_server_side_encryption = false
end end
...@@ -200,6 +203,13 @@ module Paperclip ...@@ -200,6 +203,13 @@ module Paperclip
host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com" host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com"
end end
def s3_region
region = @options[:s3_region]
region = region.call(self) if region.is_a?(Proc)
region || s3_credentials[:s3_region]
end
def s3_host_alias def s3_host_alias
@s3_host_alias = @options[:s3_host_alias] @s3_host_alias = @options[:s3_host_alias]
@s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.respond_to?(:call) @s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.respond_to?(:call)
...@@ -220,7 +230,11 @@ module Paperclip ...@@ -220,7 +230,11 @@ module Paperclip
def s3_interface def s3_interface
@s3_interface ||= begin @s3_interface ||= begin
config = { :s3_endpoint => s3_host_name } config = if aws_v1?
{ :s3_endpoint => s3_host_name }
else
{ :region => s3_region }
end
if using_http_proxy? if using_http_proxy?
...@@ -244,15 +258,31 @@ module Paperclip ...@@ -244,15 +258,31 @@ module Paperclip
def obtain_s3_instance_for(options) def obtain_s3_instance_for(options)
instances = (Thread.current[:paperclip_s3_instances] ||= {}) instances = (Thread.current[:paperclip_s3_instances] ||= {})
instances[options] ||= AWS::S3.new(options) instances[options] ||= if aws_v1?
AWS_CLASS::S3.new(options)
else
AWS_CLASS::S3::Resource.new(options)
end
end end
def s3_bucket def s3_bucket
@s3_bucket ||= s3_interface.buckets[bucket_name] @s3_bucket ||= if aws_v1?
s3_interface.buckets[bucket_name]
else
s3_interface.bucket(bucket_name)
end
end
def style_name_as_path(style_name)
path(style_name).sub(%r{\A/},'')
end end
def s3_object style_name = default_style def s3_object style_name = default_style
s3_bucket.objects[path(style_name).sub(%r{\A/},'')] if aws_v1?
s3_bucket.objects[style_name_as_path(style_name)]
else
s3_bucket.object(style_name_as_path(style_name))
end
end end
def using_http_proxy? def using_http_proxy?
...@@ -277,7 +307,7 @@ module Paperclip ...@@ -277,7 +307,7 @@ module Paperclip
def set_permissions permissions def set_permissions permissions
permissions = { :default => permissions } unless permissions.respond_to?(:merge) permissions = { :default => permissions } unless permissions.respond_to?(:merge)
permissions.merge :default => (permissions[:default] || :public_read) permissions.merge :default => (permissions[:default] || DEFAULT_PERMISSION)
end end
def set_storage_class(storage_class) def set_storage_class(storage_class)
...@@ -297,7 +327,7 @@ module Paperclip ...@@ -297,7 +327,7 @@ module Paperclip
else else
false false
end end
rescue AWS::Errors::Base => e rescue AWS_CLASS::Errors::Base => e
false false
end end
...@@ -356,11 +386,11 @@ module Paperclip ...@@ -356,11 +386,11 @@ module Paperclip
write_options[:metadata] = @s3_metadata unless @s3_metadata.empty? write_options[:metadata] = @s3_metadata unless @s3_metadata.empty?
write_options.merge!(@s3_headers) write_options.merge!(@s3_headers)
s3_object(style).write(file, write_options) s3_object(style).send((aws_v1? ? :write : :put), file, write_options)
rescue AWS::S3::Errors::NoSuchBucket rescue AWS_CLASS::S3::Errors::NoSuchBucket
create_bucket create_bucket
retry retry
rescue AWS::S3::Errors::SlowDown rescue AWS_CLASS::S3::Errors::SlowDown
retries += 1 retries += 1
if retries <= 5 if retries <= 5
sleep((2 ** retries) * 0.5) sleep((2 ** retries) * 0.5)
...@@ -382,8 +412,12 @@ module Paperclip ...@@ -382,8 +412,12 @@ module Paperclip
@queued_for_delete.each do |path| @queued_for_delete.each do |path|
begin begin
log("deleting #{path}") log("deleting #{path}")
s3_bucket.objects[path.sub(%r{\A/},'')].delete if aws_v1?
rescue AWS::Errors::Base => e s3_bucket.objects[path.sub(%r{\A/},'')]
else
s3_bucket.object(path.sub(%r{\A/},''))
end.delete
rescue AWS_CLASS::Errors::Base => e
# Ignore this. # Ignore this.
end end
end end
...@@ -393,17 +427,21 @@ module Paperclip ...@@ -393,17 +427,21 @@ module Paperclip
def copy_to_local_file(style, local_dest_path) def copy_to_local_file(style, local_dest_path)
log("copying #{path(style)} to local file #{local_dest_path}") log("copying #{path(style)} to local file #{local_dest_path}")
::File.open(local_dest_path, 'wb') do |local_file| ::File.open(local_dest_path, 'wb') do |local_file|
s3_object(style).read do |chunk| s3_object(style).send(aws_v1? ? :read : :get) do |chunk|
local_file.write(chunk) local_file.write(chunk)
end end
end end
rescue AWS::Errors::Base => e rescue AWS_CLASS::Errors::Base => e
warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}") warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
false false
end end
private private
def aws_v1?
Gem::Version.new(AWS_CLASS::VERSION) < Gem::Version.new(2)
end
def find_credentials creds def find_credentials creds
case creds case creds
when File when File
......
...@@ -31,7 +31,7 @@ Gem::Specification.new do |s| ...@@ -31,7 +31,7 @@ Gem::Specification.new do |s|
s.add_development_dependency('rspec', '~> 3.0') s.add_development_dependency('rspec', '~> 3.0')
s.add_development_dependency('appraisal') s.add_development_dependency('appraisal')
s.add_development_dependency('mocha') s.add_development_dependency('mocha')
s.add_development_dependency('aws-sdk', '>= 1.5.7', "<= 2.0") s.add_development_dependency('aws-sdk', '>= 1.5.7', "< 3.0")
s.add_development_dependency('bourne') s.add_development_dependency('bourne')
s.add_development_dependency('cucumber', '~> 1.3.18') s.add_development_dependency('cucumber', '~> 1.3.18')
s.add_development_dependency('aruba') s.add_development_dependency('aruba')
......
...@@ -8,6 +8,7 @@ unless ENV["S3_BUCKET"].blank? ...@@ -8,6 +8,7 @@ unless ENV["S3_BUCKET"].blank?
storage: :s3, storage: :s3,
bucket: ENV["S3_BUCKET"], bucket: ENV["S3_BUCKET"],
path: ":class/:attachment/:id/:style.:extension", path: ":class/:attachment/:id/:style.:extension",
s3_region: ENV["S3_REGION"],
s3_credentials: { s3_credentials: {
aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'], aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
aws_secre_access_key: ENV['AWS_SECRET_ACCESS_KEY'] aws_secre_access_key: ENV['AWS_SECRET_ACCESS_KEY']
...@@ -45,6 +46,7 @@ unless ENV["S3_BUCKET"].blank? ...@@ -45,6 +46,7 @@ unless ENV["S3_BUCKET"].blank?
storage: :s3, storage: :s3,
bucket: ENV["S3_BUCKET"], bucket: ENV["S3_BUCKET"],
path: ":class/:attachment/:id/:style.:extension", path: ":class/:attachment/:id/:style.:extension",
s3_region: ENV["S3_REGION"],
s3_credentials: { s3_credentials: {
aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'], aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
aws_secre_access_key: ENV['AWS_SECRET_ACCESS_KEY'] aws_secre_access_key: ENV['AWS_SECRET_ACCESS_KEY']
...@@ -64,6 +66,7 @@ unless ENV["S3_BUCKET"].blank? ...@@ -64,6 +66,7 @@ unless ENV["S3_BUCKET"].blank?
storage: :s3, storage: :s3,
bucket: ENV["S3_BUCKET"], bucket: ENV["S3_BUCKET"],
path: ":class/:attachment/:id/:style.:extension", path: ":class/:attachment/:id/:style.:extension",
s3_region: ENV["S3_REGION"],
s3_credentials: { s3_credentials: {
aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'], aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
aws_secre_access_key: ENV['AWS_SECRET_ACCESS_KEY'] aws_secre_access_key: ENV['AWS_SECRET_ACCESS_KEY']
...@@ -105,6 +108,7 @@ unless ENV["S3_BUCKET"].blank? ...@@ -105,6 +108,7 @@ unless ENV["S3_BUCKET"].blank?
rebuild_model styles: { thumb: "100x100", square: "32x32#" }, rebuild_model styles: { thumb: "100x100", square: "32x32#" },
storage: :s3, storage: :s3,
bucket: ENV["S3_BUCKET"], bucket: ENV["S3_BUCKET"],
s3_region: ENV["S3_REGION"],
s3_credentials: { s3_credentials: {
aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'], aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
aws_secre_access_key: ENV['AWS_SECRET_ACCESS_KEY'] aws_secre_access_key: ENV['AWS_SECRET_ACCESS_KEY']
...@@ -141,17 +145,18 @@ unless ENV["S3_BUCKET"].blank? ...@@ -141,17 +145,18 @@ unless ENV["S3_BUCKET"].blank?
end end
context "An attachment that uses S3 for storage and uses AES256 encryption" do context "An attachment that uses S3 for storage and uses AES256 encryption" do
let(:encryption_method) { defined?(::AWS) ? :aes356 : "AES256" }
before do before do
rebuild_model styles: { thumb: "100x100", square: "32x32#" }, rebuild_model styles: { thumb: "100x100", square: "32x32#" },
storage: :s3, storage: :s3,
bucket: ENV["S3_BUCKET"], bucket: ENV["S3_BUCKET"],
path: ":class/:attachment/:id/:style.:extension", path: ":class/:attachment/:id/:style.:extension",
s3_region: ENV["S3_REGION"],
s3_credentials: { s3_credentials: {
aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'], aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
aws_secre_access_key: ENV['AWS_SECRET_ACCESS_KEY'] aws_secre_access_key: ENV['AWS_SECRET_ACCESS_KEY']
}, },
s3_server_side_encryption: :aes256 s3_server_side_encryption: encryption_method
Dummy.delete_all Dummy.delete_all
@dummy = Dummy.new @dummy = Dummy.new
end end
...@@ -173,7 +178,7 @@ unless ENV["S3_BUCKET"].blank? ...@@ -173,7 +178,7 @@ unless ENV["S3_BUCKET"].blank?
end end
it "is encrypted on S3" do it "is encrypted on S3" do
assert @dummy.avatar.s3_object.server_side_encryption == :aes256 assert @dummy.avatar.s3_object.server_side_encryption == encryption_method
end end
end end
end 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