class ActiveStorage::Service::OpenStackService

ActiveStorage provider for OpenStack

Attributes

client[R]
container[R]

Public Class Methods

new(container:, credentials:, public: false, connection_options: {}) click to toggle source
Calls superclass method
# File lib/active_storage/service/open_stack_service.rb, line 11
def initialize(container:, credentials:, public: false, connection_options: {})
  super()
  settings = credentials.reverse_merge(connection_options: connection_options)

  @public = public
  @client = Fog::OpenStack::Storage.new(settings)
  @container = Fog::OpenStack.escape(container)
end

Public Instance Methods

delete(key) click to toggle source
# File lib/active_storage/service/open_stack_service.rb, line 65
def delete(key)
  instrument :delete, key: key do
    client.delete_object(container, key)
  rescue Fog::OpenStack::Storage::NotFound
    false
  end
end
delete_prefixed(prefix) click to toggle source
# File lib/active_storage/service/open_stack_service.rb, line 73
def delete_prefixed(prefix)
  instrument :delete, prefix: prefix do
    directory = client.directories.get(container)
    filtered_files = client.files(directory: directory, prefix: prefix)
    filtered_files = filtered_files.map(&:key)

    client.delete_multiple_objects(container, filtered_files)
  end
end
download(key, &block) click to toggle source
# File lib/active_storage/service/open_stack_service.rb, line 37
def download(key, &block)
  if block_given?
    instrument :streaming_download, key: key do
      object_for(key, &block)
    end
  else
    instrument :download, key: key do
      object_for(key).body
    end
  end
rescue Fog::OpenStack::Storage::NotFound
  raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
end
download_chunk(key, range) click to toggle source
# File lib/active_storage/service/open_stack_service.rb, line 51
def download_chunk(key, range)
  instrument :download_chunk, key: key, range: range do
    chunk_buffer = []

    object_for(key) do |chunk|
      chunk_buffer << chunk
    end

    chunk_buffer.join[range]
  rescue Fog::OpenStack::Storage::NotFound
    raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
  end
end
exist?(key) click to toggle source
# File lib/active_storage/service/open_stack_service.rb, line 83
def exist?(key)
  instrument :exist, key: key do |payload|
    answer = head_for(key)
    payload[:exist] = answer.present?
  rescue Fog::OpenStack::Storage::NotFound
    payload[:exist] = false
  end
end
headers_for_direct_upload(_key, content_type:, checksum:, **) click to toggle source
# File lib/active_storage/service/open_stack_service.rb, line 123
def headers_for_direct_upload(_key, content_type:, checksum:, **)
  {
    'Content-Type' => content_type,
    'ETag' => convert_base64digest_to_hexdigest(checksum)
  }
end
update_metadata(key, content_type:, disposition: nil, filename: nil, **) click to toggle source
# File lib/active_storage/service/open_stack_service.rb, line 130
def update_metadata(key, content_type:, disposition: nil, filename: nil, **)
  instrument :update_metadata, key: key, content_type: content_type, disposition: disposition do
    params = { 'Content-Type' => content_type }
    if disposition && filename
      params['Content-Disposition'] =
        content_disposition_with(
          type: disposition,
          filename: ActiveStorage::Filename.wrap(filename)
        )
    end
    client.post_object(container, key, params)
    true
  rescue Fog::OpenStack::Storage::NotFound
    raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
  end
end
upload(key, io, checksum: nil, disposition: nil, content_type: nil, filename: nil, **) click to toggle source
# File lib/active_storage/service/open_stack_service.rb, line 20
def upload(key, io, checksum: nil, disposition: nil, content_type: nil, filename: nil, **)
  instrument :upload, key: key, checksum: checksum do
    params = { 'Content-Type' => content_type || guess_content_type(io) }
    params['ETag'] = convert_base64digest_to_hexdigest(checksum) if checksum
    if disposition && filename
      params['Content-Disposition'] =
        content_disposition_with(type: disposition, filename: filename)
    end

    begin
      client.put_object(container, key, io, params)
    rescue Excon::Error::UnprocessableEntity
      raise ActiveStorage::IntegrityError
    end
  end
end
url(key, **options) click to toggle source
Calls superclass method
# File lib/active_storage/service/open_stack_service.rb, line 92
def url(key, **options)
  if ActiveStorage.version < Gem::Version.new('6.1-alpha')
    instrument :url, key: key do |payload|
      private_url(key, **options).tap do |generated_url|
        payload[:url] = generated_url
      end
    end
  else
    super
  end
end
url_for_direct_upload(key, expires_in:, filename: nil, **) click to toggle source
# File lib/active_storage/service/open_stack_service.rb, line 104
def url_for_direct_upload(key, expires_in:, filename: nil, **)
  instrument :url, key: key do |payload|
    expire_at = unix_timestamp_expires_at(expires_in)
    generated_url = client.create_temp_url(
      container,
      key,
      expire_at,
      'PUT',
      port: 443,
      **(filename ? { filename: ActiveStorage::Filename.wrap(filename).to_s } : {}),
      scheme: 'https'
    )

    payload[:url] = generated_url

    generated_url
  end
end

Private Instance Methods

convert_base64digest_to_hexdigest(base64digest) click to toggle source

ActiveStorage sends a `Digest::MD5.base64digest` checksum OpenStack expects a `Digest::MD5.hexdigest` ETag

# File lib/active_storage/service/open_stack_service.rb, line 159
def convert_base64digest_to_hexdigest(base64digest)
  base64digest.unpack1('m0').unpack1('H*')
end
guess_content_type(io) click to toggle source
# File lib/active_storage/service/open_stack_service.rb, line 167
def guess_content_type(io)
  Marcel::MimeType.for io,
                       name: io.try(:original_filename),
                       declared_type: io.try(:content_type)
end
head_for(key) click to toggle source
# File lib/active_storage/service/open_stack_service.rb, line 149
def head_for(key)
  client.head_object(container, key)
end
object_for(key, &block) click to toggle source
# File lib/active_storage/service/open_stack_service.rb, line 153
def object_for(key, &block)
  client.get_object(container, key, &block)
end
private_url(key, expires_in:, filename:, disposition:, **) click to toggle source
# File lib/active_storage/service/open_stack_service.rb, line 173
def private_url(key, expires_in:, filename:, disposition:, **)
  expire_at = unix_timestamp_expires_at(expires_in)
  generated_url = client.get_object_https_url(container, key, expire_at, filename: filename.sanitized)
  generated_url += '&inline' if disposition.to_s != 'attachment'
  # unfortunately OpenStack Swift cannot overwrite the content type of an object via a temp url
  # so we just ignore the content_type argument here

  generated_url
end
public_url(key, **) click to toggle source
# File lib/active_storage/service/open_stack_service.rb, line 183
def public_url(key, **)
  client.public_url(container, key)
end
unix_timestamp_expires_at(seconds_from_now) click to toggle source
# File lib/active_storage/service/open_stack_service.rb, line 163
def unix_timestamp_expires_at(seconds_from_now)
  Time.current.advance(seconds: seconds_from_now).to_i
end