mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 13:03:45 +08:00
158 lines
4.0 KiB
Ruby
158 lines
4.0 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
class FakeS3
|
||
|
attr_reader :s3_client
|
||
|
|
||
|
def self.create
|
||
|
s3 = self.new
|
||
|
s3.stub_bucket(SiteSetting.s3_upload_bucket) if SiteSetting.s3_upload_bucket.present?
|
||
|
s3.stub_bucket(File.join(SiteSetting.s3_backup_bucket, RailsMultisite::ConnectionManagement.current_db)) if SiteSetting.s3_backup_bucket.present?
|
||
|
s3.stub_s3_helper
|
||
|
s3
|
||
|
end
|
||
|
|
||
|
def initialize
|
||
|
@buckets = {}
|
||
|
@operations = []
|
||
|
@s3_client = Aws::S3::Client.new(stub_responses: true, region: SiteSetting.s3_region)
|
||
|
|
||
|
stub_methods
|
||
|
end
|
||
|
|
||
|
def bucket(bucket_name)
|
||
|
bucket_name, _prefix = bucket_name.split("/", 2)
|
||
|
@buckets[bucket_name]
|
||
|
end
|
||
|
|
||
|
def stub_bucket(full_bucket_name)
|
||
|
bucket_name, _prefix = full_bucket_name.split("/", 2)
|
||
|
|
||
|
s3_helper = S3Helper.new(
|
||
|
full_bucket_name,
|
||
|
Rails.configuration.multisite ? FileStore::S3Store.new.multisite_tombstone_prefix : FileStore::S3Store::TOMBSTONE_PREFIX,
|
||
|
client: @s3_client
|
||
|
)
|
||
|
@buckets[bucket_name] = FakeS3Bucket.new(full_bucket_name, s3_helper)
|
||
|
end
|
||
|
|
||
|
def stub_s3_helper
|
||
|
@buckets.each do |bucket_name, bucket|
|
||
|
S3Helper.stubs(:new)
|
||
|
.with { |b| b == bucket_name || b == bucket.name }
|
||
|
.returns(bucket.s3_helper)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def operation_called?(name)
|
||
|
@operations.any? do |operation|
|
||
|
operation[:name] == name && (block_given? ? yield(operation) : true)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def find_bucket(params)
|
||
|
bucket(params[:bucket])
|
||
|
end
|
||
|
|
||
|
def find_object(params)
|
||
|
bucket = find_bucket(params)
|
||
|
bucket&.find_object(params[:key])
|
||
|
end
|
||
|
|
||
|
def log_operation(context)
|
||
|
@operations << {
|
||
|
name: context.operation_name,
|
||
|
params: context.params.dup
|
||
|
}
|
||
|
end
|
||
|
|
||
|
def calculate_etag(context)
|
||
|
# simple, reproducible ETag calculation
|
||
|
Digest::MD5.hexdigest(context.params.to_json)
|
||
|
end
|
||
|
|
||
|
def stub_methods
|
||
|
@s3_client.stub_responses(:head_object, -> (context) do
|
||
|
log_operation(context)
|
||
|
|
||
|
if object = find_object(context.params)
|
||
|
{ content_length: object[:size], last_modified: object[:last_modified], metadata: object[:metadata] }
|
||
|
else
|
||
|
{ status_code: 404, headers: {}, body: "" }
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
@s3_client.stub_responses(:get_object, -> (context) do
|
||
|
log_operation(context)
|
||
|
|
||
|
if object = find_object(context.params)
|
||
|
{ content_length: object[:size], body: "" }
|
||
|
else
|
||
|
{ status_code: 404, headers: {}, body: "" }
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
@s3_client.stub_responses(:delete_object, -> (context) do
|
||
|
log_operation(context)
|
||
|
|
||
|
find_bucket(context.params)&.delete_object(context.params[:key])
|
||
|
nil
|
||
|
end)
|
||
|
|
||
|
@s3_client.stub_responses(:copy_object, -> (context) do
|
||
|
log_operation(context)
|
||
|
|
||
|
source_bucket_name, source_key = context.params[:copy_source].split("/", 2)
|
||
|
copy_source = { bucket: source_bucket_name, key: source_key }
|
||
|
|
||
|
if context.params[:metadata_directive] == "REPLACE"
|
||
|
attribute_overrides = context.params.except(:copy_source, :metadata_directive)
|
||
|
else
|
||
|
attribute_overrides = context.params.slice(:key, :bucket)
|
||
|
end
|
||
|
|
||
|
new_object = find_object(copy_source).dup.merge(attribute_overrides)
|
||
|
find_bucket(new_object).put_object(new_object)
|
||
|
|
||
|
{ copy_object_result: { etag: calculate_etag(context) } }
|
||
|
end)
|
||
|
|
||
|
@s3_client.stub_responses(:create_multipart_upload, -> (context) do
|
||
|
log_operation(context)
|
||
|
|
||
|
find_bucket(context.params).put_object(context.params)
|
||
|
{ upload_id: SecureRandom.hex }
|
||
|
end)
|
||
|
|
||
|
@s3_client.stub_responses(:put_object, -> (context) do
|
||
|
log_operation(context)
|
||
|
|
||
|
find_bucket(context.params).put_object(context.params)
|
||
|
{ etag: calculate_etag(context) }
|
||
|
end)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class FakeS3Bucket
|
||
|
attr_reader :name, :s3_helper
|
||
|
|
||
|
def initialize(bucket_name, s3_helper)
|
||
|
@name = bucket_name
|
||
|
@s3_helper = s3_helper
|
||
|
@objects = {}
|
||
|
end
|
||
|
|
||
|
def put_object(obj)
|
||
|
@objects[obj[:key]] = obj
|
||
|
end
|
||
|
|
||
|
def delete_object(key)
|
||
|
@objects.delete(key)
|
||
|
end
|
||
|
|
||
|
def find_object(key)
|
||
|
@objects[key]
|
||
|
end
|
||
|
end
|