Commit c65d7f81 authored by Peter Leitzen's avatar Peter Leitzen Committed by Bob Van Landuyt

Implement a status page storage client for AWS S3

The S3 client returns platform-agnostic data to allow
an easier way adding a more generic interface later.
parent 6bb28a4c
# frozen_string_literal: true
module StatusPage
module Storage
class Error < StandardError
def initialize(bucket:, error:, **args)
super(
"Error occured #{error.class.name.inspect} " \
"for bucket #{bucket.inspect}. " \
"Arguments: #{args.inspect}"
)
end
end
end
end
# frozen_string_literal: true
module StatusPage
module Storage
# Represents a platform-agnostic object class.
Object = Struct.new(:key, :content, :modified_at, keyword_init: true)
end
end
# frozen_string_literal: true
module StatusPage
module Storage
# Implements a minimal AWS S3 client.
class S3Client
def initialize(region:, bucket_name:, access_key_id:, secret_access_key:)
@bucket_name = bucket_name
@client = Aws::S3::Client.new(
region: region,
credentials: Aws::Credentials.new(access_key_id, secret_access_key)
)
end
# Stores +content+ as +key+ in storage
#
# Note: We are making sure that
# * we control +content+ (not the user)
# * this upload is done a background job (not in a web request)
def upload_object(key, content)
wrap_errors(key: key) do
client.put_object(bucket: bucket_name, key: key, body: content)
end
true
end
private
attr_reader :client, :bucket_name
def wrap_errors(**args)
yield
rescue Aws::Errors::ServiceError => e
raise Error, bucket: bucket_name, error: e, **args
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe StatusPage::Storage::S3Client, :aws_s3 do
let(:region) { 'eu-west-1' }
let(:bucket_name) { 'bucket_name' }
let(:access_key_id) { 'key_id' }
let(:secret_access_key) { 'secret' }
let(:client) do
described_class.new(
region: region, bucket_name: bucket_name, access_key_id: access_key_id,
secret_access_key: secret_access_key
)
end
describe '#upload_object' do
let(:key) { 'key' }
let(:content) { 'content' }
subject(:result) { client.upload_object(key, content) }
context 'when successful' do
it 'returns true' do
stub_responses(:put_object)
expect(result).to eq(true)
end
end
context 'when failed' do
let(:aws_error) { 'SomeError' }
it 'raises an error' do
stub_responses(:put_object, aws_error)
msg = error_message(aws_error, key: key)
expect { result }.to raise_error(StatusPage::Storage::Error, msg)
end
end
end
private
def stub_responses(*args)
s3_client = client.instance_variable_get(:@client)
s3_client.stub_responses(*args)
end
def error_message(error_class, **args)
%{Error occured "Aws::S3::Errors::#{error_class}" } \
"for bucket #{bucket_name.inspect}. Arguments: #{args.inspect}"
end
end
# frozen_string_literal: true
RSpec.configure do |config|
config.around(:each, :aws_s3) do |example|
(Aws.config[:s3] ||= {})[:stub_responses] = true
example.run
ensure
Aws.config[:s3].delete(:stub_responses)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment