class Canistor::Storage::Bucket

Holds information about a bucket and implements interaction with it.

Attributes

name[RW]
objects[R]
region[RW]
settings[R]
uploads[R]

Public Class Methods

new(**attributes) click to toggle source
# File lib/canistor/storage/bucket.rb, line 22
def initialize(**attributes)
  @settings = Settings.new
  clear
  attributes.each do |name, value|
    public_send("#{name}=", value)
  end
end

Public Instance Methods

[](name) click to toggle source
# File lib/canistor/storage/bucket.rb, line 36
def [](name)
  @objects[name]
end
[]=(name, value) click to toggle source
# File lib/canistor/storage/bucket.rb, line 40
def []=(name, value)
  @objects[name] = value
end
allow_access_to(access_key_ids) click to toggle source
# File lib/canistor/storage/bucket.rb, line 142
def allow_access_to(access_key_ids)
  settings.allow_access_keys(access_key_ids)
end
clear() click to toggle source
# File lib/canistor/storage/bucket.rb, line 126
def clear
  @objects = Canistor::Storage::Objects.new(
    versioned: settings.versioned?
  )
  @uploads = {}
end
delete(context, access_key_id, subject) click to toggle source
# File lib/canistor/storage/bucket.rb, line 115
def delete(context, access_key_id, subject)
  if !settings.access_keys.include?(access_key_id)
    Canistor::ErrorHandler.serve_access_denied(context, subject)
  elsif objects[subject.key]
    object = @objects.delete(subject.key)
    object.delete(context, subject)
  else
    Canistor::ErrorHandler.serve_no_such_key(context, subject)
  end
end
dig(*segments) click to toggle source
# File lib/canistor/storage/bucket.rb, line 44
def dig(*segments)
  @objects.dig(*segments)
end
get(context, access_key_id, subject) click to toggle source
# File lib/canistor/storage/bucket.rb, line 58
def get(context, access_key_id, subject)
  params = CGI::parse(context.http_request.endpoint.query.to_s)
  catch(:rendered_error) do
    if !settings.access_keys.include?(access_key_id)
      Canistor::ErrorHandler.serve_access_denied(context, subject)
    elsif params.has_key?('uploads')
      list_bucket_uploads(context)
    elsif params.has_key?('uploadId')
      list_bucket_upload_parts(context, subject, params)
    elsif subject.key.nil? || subject.key == ''
      list_bucket(context)
    elsif object = objects[subject.key]
      object.get(context, subject)
    else
      Canistor::ErrorHandler.serve_no_such_key(context, subject)
    end
  end
end
head(context, access_key_id, subject) click to toggle source
# File lib/canistor/storage/bucket.rb, line 48
def head(context, access_key_id, subject)
  if !settings.access_keys.include?(access_key_id)
    Canistor::ErrorHandler.serve_access_denied(context, subject)
  elsif object = objects[subject.key]
    object.head(context, subject)
  else
    Canistor::ErrorHandler.serve_no_such_key(context, subject)
  end
end
post(context, access_key_id, subject) click to toggle source
# File lib/canistor/storage/bucket.rb, line 95
def post(context, access_key_id, subject)
  if settings.access_keys.include?(access_key_id)
    Canistor.take_fail(:store) { return }
    params = CGI::parse(context.http_request.endpoint.query.to_s)
    catch(:rendered_error) do
      if params.has_key?('uploads')
        # Client wants to create a new upload when uploads is present in
        # the query.
        post_upload(context, subject)
      elsif params.has_key?('uploadId')
        # Client wants to complete the upload when uploadId is present in
        # the query.
        complete_upload(context, subject, params)
      end
    end
  else
    Canistor::ErrorHandler.serve_access_denied(context, subject)
  end
end
put(context, access_key_id, subject) click to toggle source
# File lib/canistor/storage/bucket.rb, line 77
def put(context, access_key_id, subject)
  if settings.access_keys.include?(access_key_id)
    Canistor.take_fail(:store) { return }
    params = CGI::parse(context.http_request.endpoint.query.to_s)
    catch(:rendered_error) do
      if params.has_key?('uploadId')
        # Client wants to create a new upload part when uploadId is
        # present in the query.
        put_upload_part(context, subject, params)
      else
        put_object(context, subject)
      end
    end
  else
    Canistor::ErrorHandler.serve_access_denied(context, subject)
  end
end
store_replica(object) click to toggle source
# File lib/canistor/storage/bucket.rb, line 146
def store_replica(object)
  replica = object.copy
  replica.versioned = settings.versioned?
  self[object.key] = replica
end
to_s() click to toggle source
# File lib/canistor/storage/bucket.rb, line 133
def to_s
  @objects.values.map do |object|
    ' * ' + object.label
  end.join("\n") +
  @uploads.keys.each do |upload_id|
    ' - ' + upload_id
  end.join("\n")
end
update_settings(settings) click to toggle source

Update bucket settings, see Canistor::Storage::Bucket::Settings for supported configuration.

# File lib/canistor/storage/bucket.rb, line 32
def update_settings(settings)
  @settings.update(settings)
end

Private Instance Methods

build_object(subject) click to toggle source
# File lib/canistor/storage/bucket.rb, line 154
def build_object(subject)
  Canistor::Storage::Object.new(
    region: subject.region,
    bucket: subject.bucket,
    key: subject.key,
    versioned: settings.versioned?
  )
end
build_upload(subject) click to toggle source
# File lib/canistor/storage/bucket.rb, line 179
def build_upload(subject)
  Canistor::Storage::Upload.new(
    region: subject.region,
    bucket: subject.bucket,
    key: subject.key
  )
end
complete_upload(context, subject, params) click to toggle source
# File lib/canistor/storage/bucket.rb, line 202
def complete_upload(context, subject, params)
  if upload = uploads.dig(params['uploadId'][0])
    object = upload.post(context, subject)
    self[subject.key] = object
  else
    Canistor::ErrorHandler.serve_no_such_upload(context, subject)
    throw :rendered_error
  end
end
each(prefix:, marker:, max_keys:) { |object| ... } click to toggle source

Iterate over all objects in the bucket using the filter and pagination options which exist in S3.

# File lib/canistor/storage/bucket.rb, line 214
def each(prefix:, marker:, max_keys:, &block)
  passed_marker = marker.nil? ? false : true
  has_prefix = (prefix.to_s.strip == '') ? false : true
  objects.each do |path, object|
    if !passed_marker && (!has_prefix || object.key.start_with?(prefix))
      yield object
      max_keys -= 1 unless max_keys.nil?
    end
    break if max_keys && max_keys < 1
    passed_marker = true if (!passed_marker && object.key == marker)
  end
end
each_upload(upload_id_marker:, key_marker:) { |upload| ... } click to toggle source
# File lib/canistor/storage/bucket.rb, line 237
def each_upload(upload_id_marker:, key_marker:, &block)
  uploads.each do |upload_id, upload|
    if upload_matches?(
      upload,
      upload_id_marker: upload_id_marker,
      key_marker: key_marker
    )
      yield upload
    end
  end
end
list_bucket(context) click to toggle source
# File lib/canistor/storage/bucket.rb, line 249
def list_bucket(context)
  context.http_response.signal_headers(
    200,
    'date' => Time.now.httpdate,
    'x-amz-request-id' => SecureRandom.hex(8).upcase
  )
  unless context.http_request.http_method == 'HEAD'
    context.http_response.signal_data(to_xml(context))
  end
end
list_bucket_upload_parts(context, subject, params) click to toggle source
# File lib/canistor/storage/bucket.rb, line 269
def list_bucket_upload_parts(context, subject, params)
  upload = uploads.dig(params['uploadId'][0])
  if upload && upload.key == subject.key
    upload.get(context)
  else
    Canistor::ErrorHandler.serve_no_such_upload(context, subject)
    throw :rendered_error
  end
end
list_bucket_uploads(context) click to toggle source
# File lib/canistor/storage/bucket.rb, line 260
def list_bucket_uploads(context)
  context.http_response.signal_headers(
    200,
    'date' => Time.now.httpdate,
    'x-amz-request-id' => SecureRandom.hex(8).upcase
  )
  context.http_response.signal_data(to_uploads_xml(context))
end
post_upload(context, subject) click to toggle source
# File lib/canistor/storage/bucket.rb, line 187
def post_upload(context, subject)
  upload = build_upload(subject)
  @uploads[upload.id] = upload
  upload.put(context, subject)
end
put_object(context, subject) click to toggle source
# File lib/canistor/storage/bucket.rb, line 163
def put_object(context, subject)
  object = build_object(subject)
  object.versioned = settings.versioned?
  object.put(context, subject)
  self[subject.key] = object
  replicate(object)
end
put_upload_part(context, subject, params) click to toggle source
# File lib/canistor/storage/bucket.rb, line 193
def put_upload_part(context, subject, params)
  if upload = uploads.dig(params['uploadId'][0])
    upload.put(context, subject)
  else
    Canistor::ErrorHandler.serve_no_such_upload(context, subject)
    throw :rendered_error
  end
end
replicate(object) click to toggle source
# File lib/canistor/storage/bucket.rb, line 171
def replicate(object)
  if settings.replicated?
    settings.replicate_to_buckets.each do |bucket|
      bucket.store_replica(object)
    end
  end
end
to_uploads_xml(context) click to toggle source
# File lib/canistor/storage/bucket.rb, line 305
def to_uploads_xml(context)
  # Only return uploads with ID's that start with id marker
  upload_id_marker = context.params[:upload_id_marker]
  # Only return uploads with keys that start with key marker
  key_marker = context.params[:key_marker]
  Nokogiri::XML::Builder.new do |xml|
    xml.ListMultipartUploadsResult(
      xmlns: 'http://s3.amazonaws.com/doc/2006-03-01/'
    ) do
      xml.Bucket name
      xml.KeyMarker key_marker
      xml.UploadIdMarker upload_id_marker
      each_upload(
        upload_id_marker: upload_id_marker,
        key_marker: key_marker
      ) do |upload|
        xml.Upload do
          xml.Key upload.key
          xml.UploadId upload.id
        end
      end
    end
  end.to_xml
end
to_xml(context) click to toggle source
# File lib/canistor/storage/bucket.rb, line 279
def to_xml(context)
  # Only return objects with keys that start with the prefix.
  prefix = context.params[:prefix]
  # Return objects until we find a key that matches the marker. Can
  # be used to group objects.
  marker = context.params[:marker]
  # Stop after returning a number of objects.
  max_keys = context.params[:max_keys]
  max_keys = max_keys ? max_keys.to_i : nil
  Nokogiri::XML::Builder.new do |xml|
    xml.ListBucketResult(xmlns: 'http://s3.amazonaws.com/doc/2006-03-01/') do
      xml.Name name
      xml.Prefix
      xml.Marker
      xml.MaxKeys max_keys
      each(prefix: prefix, marker: marker, max_keys: max_keys) do |object|
        xml.Contents do
          xml.Key object.key
          xml.Size object.size
          xml.StorageClass 'STANDARD'
        end
      end
    end
  end.to_xml
end
upload_matches?(upload, upload_id_marker:, key_marker:) click to toggle source
# File lib/canistor/storage/bucket.rb, line 227
def upload_matches?(upload, upload_id_marker:, key_marker:)
  if upload_id_marker && !upload.id.start_with?(upload_id_marker)
    return false
  end
  if key_marker && !upload.key.start_with?(key_marker)
    return false
  end
  true
end