module Paperclip::Storage::S3

Amazon's S3 file hosting service is a scalable, easy place to store files for distribution. You can find out more about it at aws.amazon.com/s3

To use Paperclip with S3, include the aws-sdk gem in your Gemfile:

gem 'aws-sdk', '~> 1.6'

There are a few S3-specific options for has_attached_file:

Public Class Methods

extended(base) click to toggle source
# File lib/paperclip/storage/s3.rb, line 114
def self.extended base
  begin
    require 'aws-sdk'
  rescue LoadError => e
    e.message << " (You may need to install the aws-sdk gem)"
    raise e
  end unless defined?(AWS::Core)

  # Overriding log formatter to make sure it return a UTF-8 string
  if defined?(AWS::Core::LogFormatter)
    AWS::Core::LogFormatter.class_eval do
      def summarize_hash(hash)
        hash.map { |key, value| ":#{key}=>#{summarize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
      end
    end
  elsif defined?(AWS::Core::ClientLogging)
    AWS::Core::ClientLogging.class_eval do
      def sanitize_hash(hash)
        hash.map { |key, value| "#{sanitize_value(key)}=>#{sanitize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
      end
    end
  end

  base.instance_eval do
    @s3_options     = @options[:s3_options]     || {}
    @s3_permissions = set_permissions(@options[:s3_permissions])
    @s3_protocol    = @options[:s3_protocol]    ||
      Proc.new do |style, attachment|
        permission  = (@s3_permissions[style.to_s.to_sym] || @s3_permissions[:default])
        permission  = permission.call(attachment, style) if permission.respond_to?(:call)
        (permission == :public_read) ? 'http'.freeze : 'https'.freeze
      end
    @s3_metadata = @options[:s3_metadata] || {}
    @s3_headers = {}
    merge_s3_headers(@options[:s3_headers], @s3_headers, @s3_metadata)

    @s3_storage_class = set_storage_class(@options[:s3_storage_class])

    @s3_server_side_encryption = :aes256
    if @options[:s3_server_side_encryption].blank?
      @s3_server_side_encryption = false
    end
    if @s3_server_side_encryption
      @s3_server_side_encryption = @options[:s3_server_side_encryption]
    end

    unless @options[:url].to_s.match(/\A:s3.*url\Z/) || @options[:url] == ":asset_host".freeze
      @options[:path] = path_option.gsub(/:url/, @options[:url]).sub(/\A:rails_root\/public\/system/, "".freeze)
      @options[:url]  = ":s3_path_url".freeze
    end
    @options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol)

    @http_proxy = @options[:http_proxy] || nil
  end

  Paperclip.interpolates(:s3_alias_url) do |attachment, style|
    "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
  end unless Paperclip::Interpolations.respond_to? :s3_alias_url
  Paperclip.interpolates(:s3_path_url) do |attachment, style|
    "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
  end unless Paperclip::Interpolations.respond_to? :s3_path_url
  Paperclip.interpolates(:s3_domain_url) do |attachment, style|
    "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
  end unless Paperclip::Interpolations.respond_to? :s3_domain_url
  Paperclip.interpolates(:asset_host) do |attachment, style|
    "#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
  end unless Paperclip::Interpolations.respond_to? :asset_host
end

Public Instance Methods

bucket_name() click to toggle source
# File lib/paperclip/storage/s3.rb, line 215
def bucket_name
  @bucket = @options[:bucket] || s3_credentials[:bucket]
  @bucket = @bucket.call(self) if @bucket.respond_to?(:call)
  @bucket or raise ArgumentError, "missing required :bucket option"
end
copy_to_local_file(style, local_dest_path) click to toggle source
# File lib/paperclip/storage/s3.rb, line 393
def copy_to_local_file(style, local_dest_path)
  log("copying #{path(style)} to local file #{local_dest_path}")
  ::File.open(local_dest_path, 'wb') do |local_file|
    s3_object(style).read do |chunk|
      local_file.write(chunk)
    end
  end
rescue AWS::Errors::Base => e
  warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
  false
end
create_bucket() click to toggle source
# File lib/paperclip/storage/s3.rb, line 325
def create_bucket
  s3_interface.buckets.create(bucket_name)
end
exists?(style = default_style) click to toggle source
# File lib/paperclip/storage/s3.rb, line 294
def exists?(style = default_style)
  if original_filename
    s3_object(style).exists?
  else
    false
  end
rescue AWS::Errors::Base => e
  false
end
expiring_url(time = 3600, style_name = default_style) click to toggle source
# File lib/paperclip/storage/s3.rb, line 183
def expiring_url(time = 3600, style_name = default_style)
  if path(style_name)
    base_options = { :expires => time, :secure => use_secure_protocol?(style_name) }
    s3_object(style_name).url_for(:read, base_options.merge(s3_url_options)).to_s
  else
    url(style_name)
  end
end
http_proxy_host() click to toggle source
# File lib/paperclip/storage/s3.rb, line 262
def http_proxy_host
  using_http_proxy? ? @http_proxy[:host] : nil
end
http_proxy_password() click to toggle source
# File lib/paperclip/storage/s3.rb, line 274
def http_proxy_password
  using_http_proxy? ? @http_proxy[:password] : nil
end
http_proxy_port() click to toggle source
# File lib/paperclip/storage/s3.rb, line 266
def http_proxy_port
  using_http_proxy? ? @http_proxy[:port] : nil
end
http_proxy_user() click to toggle source
# File lib/paperclip/storage/s3.rb, line 270
def http_proxy_user
  using_http_proxy? ? @http_proxy[:user] : nil
end
obtain_s3_instance_for(options) click to toggle source
# File lib/paperclip/storage/s3.rb, line 245
def obtain_s3_instance_for(options)
  instances = (Thread.current[:paperclip_s3_instances] ||= {})
  instances[options] ||= AWS::S3.new(options)
end
parse_credentials(creds) click to toggle source
# File lib/paperclip/storage/s3.rb, line 288
def parse_credentials creds
  creds = creds.respond_to?(:call) ? creds.call(self) : creds
  creds = find_credentials(creds).stringify_keys
  (creds[RailsEnvironment.get] || creds).symbolize_keys
end
s3_bucket() click to toggle source
# File lib/paperclip/storage/s3.rb, line 250
def s3_bucket
  @s3_bucket ||= s3_interface.buckets[bucket_name]
end
s3_credentials() click to toggle source
# File lib/paperclip/storage/s3.rb, line 192
def s3_credentials
  @s3_credentials ||= parse_credentials(@options[:s3_credentials])
end
s3_host_alias() click to toggle source
# File lib/paperclip/storage/s3.rb, line 203
def 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
end
s3_host_name() click to toggle source
# File lib/paperclip/storage/s3.rb, line 196
def s3_host_name
  host_name = @options[:s3_host_name]
  host_name = host_name.call(self) if host_name.is_a?(Proc)

  host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com".freeze
end
s3_interface() click to toggle source
# File lib/paperclip/storage/s3.rb, line 221
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, :credential_provider].each do |opt|
      config[opt] = s3_credentials[opt] if s3_credentials[opt]
    end

    obtain_s3_instance_for(config.merge(@s3_options))
  end
end
s3_object(style_name = default_style) click to toggle source
# File lib/paperclip/storage/s3.rb, line 254
def s3_object style_name = default_style
  s3_bucket.objects[path(style_name).sub(%r{\A/},'')]
end
s3_permissions(style = default_style) click to toggle source
# File lib/paperclip/storage/s3.rb, line 304
def s3_permissions(style = default_style)
  s3_permissions = @s3_permissions[style] || @s3_permissions[:default]
  s3_permissions = s3_permissions.call(self, style) if s3_permissions.respond_to?(:call)
  s3_permissions
end
s3_protocol(style = default_style, with_colon = false) click to toggle source
# File lib/paperclip/storage/s3.rb, line 314
def s3_protocol(style = default_style, with_colon = false)
  protocol = @s3_protocol
  protocol = protocol.call(style, self) if protocol.respond_to?(:call)

  if with_colon && !protocol.empty?
    "#{protocol}:"
  else
    protocol.to_s
  end
end
s3_storage_class(style = default_style) click to toggle source
# File lib/paperclip/storage/s3.rb, line 310
def s3_storage_class(style = default_style)
  @s3_storage_class[style] || @s3_storage_class[:default]
end
s3_url_options() click to toggle source
# File lib/paperclip/storage/s3.rb, line 209
def s3_url_options
  s3_url_options = @options[:s3_url_options] || {}
  s3_url_options = s3_url_options.call(instance) if s3_url_options.respond_to?(:call)
  s3_url_options
end
sanitize_hash(hash) click to toggle source
# File lib/paperclip/storage/s3.rb, line 131
def sanitize_hash(hash)
  hash.map { |key, value| "#{sanitize_value(key)}=>#{sanitize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
end
set_permissions(permissions) click to toggle source
# File lib/paperclip/storage/s3.rb, line 278
def set_permissions permissions
  permissions = { :default => permissions } unless permissions.respond_to?(:merge)
  permissions.merge :default => (permissions[:default] || :public_read)
end
set_storage_class(storage_class) click to toggle source
# File lib/paperclip/storage/s3.rb, line 283
def set_storage_class(storage_class)
  storage_class = {:default => storage_class} unless storage_class.respond_to?(:merge)
  storage_class
end
summarize_hash(hash) click to toggle source
# File lib/paperclip/storage/s3.rb, line 125
def summarize_hash(hash)
  hash.map { |key, value| ":#{key}=>#{summarize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
end
using_http_proxy?() click to toggle source
# File lib/paperclip/storage/s3.rb, line 258
def using_http_proxy?
  !!@http_proxy
end

Private Instance Methods

find_credentials(creds) click to toggle source
# File lib/paperclip/storage/s3.rb, line 407
def find_credentials creds
  case creds
  when File
    YAML::load(ERB.new(File.read(creds.path)).result)
  when String, Pathname
    YAML::load(ERB.new(File.read(creds)).result)
  when Hash
    creds
  when NilClass
    {}
  else
    raise ArgumentError, "Credentials given are not a path, file, proc, or hash."
  end
end
merge_s3_headers(http_headers, s3_headers, s3_metadata) click to toggle source
# File lib/paperclip/storage/s3.rb, line 426
def merge_s3_headers(http_headers, s3_headers, s3_metadata)
  return if http_headers.nil?
  http_headers = http_headers.call(instance) if http_headers.respond_to?(:call)
  http_headers.inject({}) do |headers,(name,value)|
    case name.to_s
    when /\Ax-amz-meta-(.*)/i
      s3_metadata[$1.downcase] = value
    else
      s3_headers[name.to_s.downcase.sub(/\Ax-amz-/,'').tr("-","_").to_sym] = value
    end
  end
end
use_secure_protocol?(style_name) click to toggle source
# File lib/paperclip/storage/s3.rb, line 422
def use_secure_protocol?(style_name)
  s3_protocol(style_name) == "https"
end