Module: Paperclip::Storage::Azure

Defined in:
lib/paperclip/storage/azure.rb,
lib/paperclip/storage/azure/environment.rb

Overview

Azure's container file hosting service is a scalable, easy place to store files for distribution. You can find out more about it at http://azure.microsoft.com/en-us/services/storage/

To use Paperclip with Azure, include the azure-storage-blob gem in your Gemfile:

gem 'azure-storage-blob'

There are a few Azure-specific options for has_attached_file:

  • azure_credentials: Takes a path, a File, a Hash or a Proc. The path (or File) must point to a YAML file containing the storage_access_key and storage_account_name that azure gives you. You can 'environment-space' this just like you do to your database.yml file, so different environments can use different accounts:

    development:
    storage_account_name: foo
    storage_access_key: 123...
    test:
    storage_account_name: foo
    storage_access_key: abc...
    production:
    storage_account_name: foo
    storage_access_key: 456...
    

    This is not required, however, and the file may simply look like this:

    storage_account_name: foo
    storage_access_key: 456...
    

    In which case, those access keys will be used in all environments. You can also put your container name in this file, instead of adding it to the code directly. This is useful when you want the same account but a different container for development versus production. When using a Proc it provides a single parameter which is the attachment itself. A method #instance is available on the attachment which will take you back to your code. eg. class User has_attached_file :download, :storage => :azure, :azure_credentials => Proc.new{|a| a.instance.azure_credentials }

    def azure_credentials
      { :container => "xxx", :storage_account_name => "xxx", :storage_access_key => "xxx" }
    end
    end
    
  • container: This is the name of the Azure container that will store your files. Remember that the container must be unique across the storage account. If the container does not exist Paperclip will attempt to create it. The container name will not be interpolated. You can define the container as a Proc if you want to determine it's name at runtime. Paperclip will call that Proc with attachment as the only argument.

  • path: This is the key under the container in which the file will be stored. The URL will be constructed from the container and the path. This is what you will want to interpolate. Keys should be unique, like filenames, and despite the fact that Azure (strictly speaking) does not support directories, you can still use a / to separate parts of your file name.

  • region: Depending on the region, different base urls are used. Supported values :global, :de

Defined Under Namespace

Classes: Environment

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(base) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/paperclip/storage/azure.rb', line 61

def self.extended base
  begin
    require 'azure/storage/blob/blob_service'
  rescue LoadError => e
    e.message << " (You may need to install the azure-storage-blob gem)"
    raise e
  end unless defined?(::Azure::Core)

  base.instance_eval do
    @azure_options     = @options[:azure_options]     || {}

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

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

  unless Paperclip::Interpolations.respond_to? :azure_alias_url
    Paperclip.interpolates(:azure_alias_url) do |attachment, style|
      protocol = attachment.azure_protocol(style, true)
      host = attachment.azure_host_alias
      path = attachment.path(style).
             split("/")[attachment.azure_prefixes_in_alias..-1].
             join("/").
             sub(%r{\A/}, "")
      "#{protocol}//#{host}/#{path}"
    end
  end
  Paperclip.interpolates(:azure_path_url) do |attachment, style|
    attachment.azure_uri(style)
  end unless Paperclip::Interpolations.respond_to? :azure_path_url
  Paperclip.interpolates(:asset_host) do |attachment, style|
    "#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
  end unless Paperclip::Interpolations.respond_to? :asset_host
end

Instance Method Details

#auto_connect_durationObject



134
135
136
137
# File 'lib/paperclip/storage/azure.rb', line 134

def auto_connect_duration
  @auto_connect_duration ||= @options[:auto_connect_duration] || azure_credentials[:auto_connect_duration] || 10
  @auto_connect_duration
end

#azure_account_nameObject



143
144
145
146
147
148
# File 'lib/paperclip/storage/azure.rb', line 143

def 
   = @options[:azure_storage_account_name] || azure_credentials[:storage_account_name]
   = .call(self) if .is_a?(Proc)

  
end

#azure_base_urlObject



183
184
185
# File 'lib/paperclip/storage/azure.rb', line 183

def azure_base_url
  Environment.url_for , azure_credentials[:region]
end

#azure_containerObject



187
188
189
# File 'lib/paperclip/storage/azure.rb', line 187

def azure_container
  @azure_container ||= azure_interface.get_container_properties container_name
end

#azure_credentialsObject



139
140
141
# File 'lib/paperclip/storage/azure.rb', line 139

def azure_credentials
  @azure_credentials ||= parse_credentials(@options[:azure_credentials])
end

#azure_host_aliasObject



113
114
115
116
117
# File 'lib/paperclip/storage/azure.rb', line 113

def azure_host_alias
  @azure_host_alias = @options[:azure_host_alias]
  @azure_host_alias = @azure_host_alias.call(self) if @azure_host_alias.respond_to?(:call)
  @azure_host_alias
end

#azure_interfaceObject



156
157
158
159
160
161
162
163
164
165
166
# File 'lib/paperclip/storage/azure.rb', line 156

def azure_interface
  @azure_interface ||= begin
    config = {}

    [:storage_account_name, :storage_access_key, :container].each do |opt|
      config[opt] = azure_credentials[opt] if azure_credentials[opt]
    end

    obtain_azure_instance_for(config.merge(@azure_options))
  end
end

#azure_object(style_name = default_style) ⇒ Object



191
192
193
# File 'lib/paperclip/storage/azure.rb', line 191

def azure_object(style_name = default_style)
  azure_interface.get_blob_properties container_name, path(style_name).sub(%r{\A/},'')
end

#azure_prefixes_in_aliasObject



119
120
121
# File 'lib/paperclip/storage/azure.rb', line 119

def azure_prefixes_in_alias
  @azure_prefixes_in_alias ||= @options[:azure_prefixes_in_alias].to_i
end

#azure_protocol(style = default_style, with_colon = false) ⇒ Object



123
124
125
126
127
128
129
130
131
132
# File 'lib/paperclip/storage/azure.rb', line 123

def azure_protocol(style = default_style, with_colon = false)
  protocol = @azure_options[:protocol]
  protocol = protocol.call(style, self) if protocol.respond_to?(:call)

  if with_colon && !protocol.empty?
    "#{protocol}:"
  else
    protocol.to_s
  end
end

#azure_uri(style_name = default_style) ⇒ Object



179
180
181
# File 'lib/paperclip/storage/azure.rb', line 179

def azure_uri(style_name = default_style)
  "https://#{azure_base_url}/#{container_name}/#{path(style_name).gsub(%r{\A/}, '')}"
end

#container_nameObject



150
151
152
153
154
# File 'lib/paperclip/storage/azure.rb', line 150

def container_name
  @container ||= @options[:container] || azure_credentials[:container]
  @container = @container.call(self) if @container.respond_to?(:call)
  @container or raise ArgumentError, "missing required :container option"
end

#copy_to_local_file(style, local_dest_path) ⇒ Object



279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/paperclip/storage/azure.rb', line 279

def copy_to_local_file(style, local_dest_path)
  log("copying #{path(style)} to local file #{local_dest_path}")

  blob, content = azure_interface.get_blob(container_name, path(style).sub(%r{\A/},''))

  ::File.open(local_dest_path, 'wb') do |local_file|
    local_file.write(content)
  end
rescue ::Azure::Core::Http::HTTPError => e
  raise unless e.status_code == 404

  warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
  false
end

#create_containerObject



214
215
216
# File 'lib/paperclip/storage/azure.rb', line 214

def create_container
  azure_interface.create_container container_name
end

#exists?(style = default_style) ⇒ Boolean

Returns:

  • (Boolean)


202
203
204
205
206
207
208
209
210
211
212
# File 'lib/paperclip/storage/azure.rb', line 202

def exists?(style = default_style)
  if original_filename
    !azure_object(style).nil?
  else
    false
  end
rescue ::Azure::Core::Http::HTTPError => e
  raise unless e.status_code == 404

  false
end

#expiring_url(time = 3600, style_name = default_style) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/paperclip/storage/azure.rb', line 100

def expiring_url(time = 3600, style_name = default_style)
  if path(style_name)
    signer = ::Azure::Core::Auth::SharedAccessSignature.new(
      ,
      azure_credentials[:storage_access_key]
    )
    obj_path = path(style_name).gsub(%r{\A/}, '')
    "#{azure_uri}?#{signer.generate_token(container_name, obj_path, 'r', time)}"
  else
    url(style_name)
  end
end

#flush_deletesObject

:nodoc:



266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/paperclip/storage/azure.rb', line 266

def flush_deletes #:nodoc:
  @queued_for_delete.each do |path|
    begin
      log("deleting #{path}")

      azure_interface.delete_blob container_name, path
    rescue ::Azure::Core::Http::HTTPError => e
      raise unless e.status_code == 404
    end
  end
  @queued_for_delete = []
end

#flush_writesObject

:nodoc:



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/paperclip/storage/azure.rb', line 218

def flush_writes #:nodoc:
  @queued_for_write.each do |style, file|
    retries = 0
    begin
      log("saving #{path(style)}")

      write_options = {
        content_type: file.content_type,
      }

      if azure_container
        save_blob container_name, path(style).sub(%r{\A/},''), file, write_options
      end
    rescue ::Azure::Core::Http::HTTPError => e
      if e.status_code == 404
        create_container
        retry
      else
        raise
      end
    ensure
      file.rewind
    end
  end

  after_flush_writes # allows attachment to clean up temp files

  @queued_for_write = {}
end

#obtain_azure_instance_for(options) ⇒ Object



168
169
170
171
172
173
174
175
176
177
# File 'lib/paperclip/storage/azure.rb', line 168

def obtain_azure_instance_for(options)
  if options[:use_development_storage]
    service = ::Azure::Storage::Blob::BlobService.create(use_development_storage: true)
  else
    service = ::Azure::Storage::Blob::BlobService.create(storage_account_name: options[:storage_account_name],
                                                         storage_access_key: options[:storage_access_key])
  end
  
  service
end

#parse_credentials(creds) ⇒ Object



195
196
197
198
199
200
# File 'lib/paperclip/storage/azure.rb', line 195

def parse_credentials(creds)
  creds = creds.respond_to?('call') ? creds.call(self) : creds
  creds = find_credentials(creds).stringify_keys
  env = Object.const_defined?(:Rails) ? Rails.env : nil
  (creds[env] || creds).symbolize_keys
end

#save_blob(container_name, storage_path, file, write_options) ⇒ Object



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/paperclip/storage/azure.rb', line 248

def save_blob(container_name, storage_path, file, write_options)

  if file.size < 64.megabytes
    azure_interface.create_block_blob container_name, storage_path, file.read, write_options
  else
    blocks = []; count = 0
    while data = file.read(4.megabytes)
      block_id = "block_#{(count += 1).to_s.rjust(5, '0')}"

      azure_interface.put_blob_block container_name, storage_path, block_id, data

      blocks << [block_id]
    end

    azure_interface.commit_blob_blocks container_name, storage_path, blocks
  end
end