1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
module Fog
module AWS
class Storage
class Real
require 'fog/aws/parsers/storage/delete_multiple_objects'
# Delete multiple objects from S3
# @note For versioned deletes, options should include a version_ids hash, which
# maps from filename to an array of versions.
# The semantics are that for each (object_name, version) tuple, the
# caller must insert the object_name and an associated version (if
# desired), so for n versions, the object must be inserted n times.
#
# @param bucket_name [String] Name of bucket containing object to delete
# @param object_names [Array] Array of object names to delete
#
# @return [Excon::Response] response:
# * body [Hash]:
# * DeleteResult [Array]:
# * Deleted [Hash]:
# * Key [String] - Name of the object that was deleted
# * VersionId [String] - ID for the versioned onject in case of a versioned delete
# * DeleteMarker [Boolean] - Indicates if the request accessed a delete marker
# * DeleteMarkerVersionId [String] - Version ID of the delete marker accessed
# * Error [Hash]:
# * Key [String] - Name of the object that failed to be deleted
# * VersionId [String] - ID of the versioned object that was attempted to be deleted
# * Code [String] - Status code for the result of the failed delete
# * Message [String] - Error description
#
# @see http://docs.amazonwebservices.com/AmazonS3/latest/API/multiobjectdeleteapi.html
def delete_multiple_objects(bucket_name, object_names, options = {})
headers = options.dup
data = "<Delete>"
data << "<Quiet>true</Quiet>" if headers.delete(:quiet)
version_ids = headers.delete('versionId')
object_names.each do |object_name|
object_version = version_ids.nil? ? nil : version_ids[object_name]
if object_version
object_version = object_version.is_a?(String) ? [object_version] : object_version
object_version.each do |version_id|
data << "<Object>"
data << "<Key>#{CGI.escapeHTML(object_name)}</Key>"
data << "<VersionId>#{CGI.escapeHTML(version_id)}</VersionId>"
data << "</Object>"
end
else
data << "<Object>"
data << "<Key>#{CGI.escapeHTML(object_name)}</Key>"
data << "</Object>"
end
end
data << "</Delete>"
headers['Content-Length'] = data.bytesize
headers['Content-MD5'] = Base64.encode64(OpenSSL::Digest::MD5.digest(data)).
gsub("\n", '')
request({
:body => data,
:expects => 200,
:headers => headers,
:bucket_name => bucket_name,
:method => 'POST',
:parser => Fog::Parsers::AWS::Storage::DeleteMultipleObjects.new,
:query => {'delete' => nil}
})
end
end
class Mock # :nodoc:all
def delete_multiple_objects(bucket_name, object_names, options = {})
headers = options.dup
headers.delete(:quiet)
response = Excon::Response.new
if bucket = self.data[:buckets][bucket_name]
response.status = 200
response.body = { 'DeleteResult' => [] }
version_ids = headers.delete('versionId')
object_names.each do |object_name|
object_version = version_ids.nil? ? [nil] : version_ids[object_name]
object_version = object_version.is_a?(String) ? [object_version] : object_version
object_version.each do |version_id|
response.body['DeleteResult'] << delete_object_helper(bucket,
object_name,
version_id)
end
end
else
response.status = 404
raise(Excon::Errors.status_error({:expects => 200}, response))
end
response
end
private
def delete_object_helper(bucket, object_name, version_id)
response = { 'Deleted' => {} }
if bucket[:versioning]
bucket[:objects][object_name] ||= []
if version_id
version = bucket[:objects][object_name].find { |object| object['VersionId'] == version_id}
# S3 special cases the 'null' value to not error out if no such version exists.
if version || (version_id == 'null')
bucket[:objects][object_name].delete(version)
bucket[:objects].delete(object_name) if bucket[:objects][object_name].empty?
response['Deleted'] = { 'Key' => object_name,
'VersionId' => version_id,
'DeleteMarker' => 'true',
'DeleteMarkerVersionId' => version_id
}
else
response = delete_error_body(object_name,
version_id,
'InvalidVersion',
'Invalid version ID specified')
end
else
delete_marker = {
:delete_marker => true,
'Key' => object_name,
'VersionId' => bucket[:versioning] == 'Enabled' ? Fog::Mock.random_base64(32) : 'null',
'Last-Modified' => Fog::Time.now.to_date_header
}
# When versioning is suspended, a delete marker is placed if the last object ID is not the value 'null',
# otherwise the last object is replaced.
if bucket[:versioning] == 'Suspended' && bucket[:objects][object_name].first['VersionId'] == 'null'
bucket[:objects][object_name].shift
end
bucket[:objects][object_name].unshift(delete_marker)
response['Deleted'] = { 'Key' => object_name,
'VersionId' => delete_marker['VersionId'],
'DeleteMarkerVersionId' =>
delete_marker['VersionId'],
'DeleteMarker' => 'true',
}
end
else
if version_id && version_id != 'null'
response = delete_error_body(object_name,
version_id,
'InvalidVersion',
'Invalid version ID specified')
response = invalid_version_id_payload(version_id)
else
bucket[:objects].delete(object_name)
response['Deleted'] = { 'Key' => object_name }
end
end
response
end
def delete_error_body(key, version_id, message, code)
{
'Error' => {
'Code' => code,
'Message' => message,
'VersionId' => version_id,
'Key' => key,
}
}
end
end
end
end
end
|