Browse Source

Add a storage service backed by the AWS S3 SDK (#263)

pull/265/head
Stéphane Meny 3 years ago committed by GitHub
parent
commit
79d00cdc01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Gemfile
  2. 18
      Gemfile.lock
  3. 2
      app/services/paas_configuration_service.rb
  4. 68
      app/services/storage_service.rb
  5. 2
      spec/services/paas_configuration_service_spec.rb
  6. 98
      spec/services/storage_service_spec.rb

2
Gemfile

@ -43,6 +43,8 @@ gem "uk_postcode"
gem "postcodes_io"
# Use Ruby objects to build reusable markup. A React inspired evolution of the presenter pattern
gem "view_component"
# Use the AWS S3 SDK as storage mechanism
gem "aws-sdk-s3"
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console

18
Gemfile.lock

@ -104,6 +104,22 @@ GEM
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
ast (2.4.2)
aws-eventstream (1.2.0)
aws-partitions (1.550.0)
aws-sdk-core (3.125.5)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.53.0)
aws-sdk-core (~> 3, >= 3.125.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.111.3)
aws-sdk-core (~> 3, >= 3.125.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.4.0)
aws-eventstream (~> 1, >= 1.0.2)
bcrypt (3.1.16)
bindex (0.8.1)
bootsnap (1.10.2)
@ -180,6 +196,7 @@ GEM
responders (>= 2, < 4)
iniparse (1.5.0)
io-wait (0.2.1)
jmespath (1.5.0)
jquery-rails (4.4.0)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
@ -424,6 +441,7 @@ PLATFORMS
DEPENDENCIES
activeadmin!
arbre!
aws-sdk-s3
bootsnap (>= 1.4.4)
byebug
capybara

2
app/services/paas_configuration_service.rb

@ -31,7 +31,7 @@ private
end
def read_s3_buckets
return [] unless s3_config_present?
return {} unless s3_config_present?
s3_buckets = {}
@paas_config[:"aws-s3-bucket"].each do |bucket_config|

68
app/services/storage_service.rb

@ -0,0 +1,68 @@
class StorageService
attr_reader :configuration
def initialize(paas_config_service, paas_instance_name)
@paas_config_service = paas_config_service
@paas_instance_name = paas_instance_name.to_sym
@configuration = create_configuration
@client = create_client
end
def get_file_io(file_name)
file_response =
@client.get_object(bucket: @configuration.bucket_name, key: file_name)
file_response.body
end
def write_file(file_name, data)
@client.put_object(
body: data,
bucket: @configuration.bucket_name,
key: file_name,
)
end
private
def create_configuration
unless @paas_config_service.config_present?
raise "No PaaS configuration present"
end
unless @paas_config_service.s3_buckets.key?(@paas_instance_name)
raise "#{@paas_instance_name} instance name could not be found"
end
bucket_config = @paas_config_service.s3_buckets[@paas_instance_name]
StorageConfiguration.new(bucket_config[:credentials])
end
def create_client
credentials =
Aws::Credentials.new(
@configuration.access_key_id,
@configuration.secret_access_key,
)
Aws::S3::Client.new(
region: @configuration.region,
credentials: credentials,
)
end
end
class StorageConfiguration
attr_reader :access_key_id, :secret_access_key, :bucket_name, :region
def initialize(credentials)
@access_key_id = credentials[:aws_access_key_id]
@secret_access_key = credentials[:aws_secret_access_key]
@bucket_name = credentials[:bucket_name]
@region = credentials[:aws_region]
end
def ==(other)
@access_key_id == other.access_key_id &&
@secret_access_key == other.secret_access_key &&
@bucket_name == other.bucket_name &&
@region == other.region
end
end

2
spec/services/paas_configuration_service_spec.rb

@ -17,6 +17,7 @@ RSpec.describe PaasConfigurationService do
end
it "does not retrieve any S3 bucket configuration" do
expect(config_service.s3_buckets).to be_a(Hash)
expect(config_service.s3_buckets).to be_empty
end
end
@ -64,6 +65,7 @@ RSpec.describe PaasConfigurationService do
end
it "does not retrieve any S3 bucket configuration" do
expect(config_service.s3_buckets).to be_a(Hash)
expect(config_service.s3_buckets).to be_empty
end
end

98
spec/services/storage_service_spec.rb

@ -0,0 +1,98 @@
require "rails_helper"
RSpec.describe StorageService do
let(:instance_name) { "instance_1" }
let(:bucket_name) { "bucket_1" }
let(:vcap_services) do
<<-JSON
{"aws-s3-bucket": [
{
"instance_name": "#{instance_name}",
"credentials": {
"aws_access_key_id": "key_id",
"aws_region": "eu-west-2",
"aws_secret_access_key": "secret",
"bucket_name": "#{bucket_name}"
}
}
]}
JSON
end
context "when we create an S3 Service with no PaaS Configuration present" do
subject { described_class.new(PaasConfigurationService.new, "random_instance") }
it "raises an exception" do
expect { subject }.to raise_error(RuntimeError, /No PaaS configuration present/)
end
end
context "when we create an S3 Service with an unknown instance name" do
subject { described_class.new(PaasConfigurationService.new, "random_instance") }
before do
allow(ENV).to receive(:[]).with("VCAP_SERVICES").and_return("{}")
end
it "raises an exception" do
expect { subject }.to raise_error(RuntimeError, /instance name could not be found/)
end
end
context "when we create an storage service with a valid instance name" do
subject { described_class.new(PaasConfigurationService.new, instance_name) }
before do
allow(ENV).to receive(:[])
allow(ENV).to receive(:[]).with("VCAP_SERVICES").and_return(vcap_services)
end
it "creates a Storage Configuration" do
expect(subject.configuration).to be_an(StorageConfiguration)
end
it "sets the expected parameters in the configuration" do
expected_configuration = StorageConfiguration.new(
{
aws_access_key_id: "key_id",
aws_region: "eu-west-2",
aws_secret_access_key: "secret",
bucket_name: bucket_name,
},
)
expect(subject.configuration).to eq(expected_configuration)
end
end
context "when we create an storage service and write a stubbed object" do
subject { described_class.new(PaasConfigurationService.new, instance_name) }
let(:filename) { "my_file" }
let(:content) { "content" }
let(:s3_client_stub) { Aws::S3::Client.new(stub_responses: true) }
before do
allow(ENV).to receive(:[])
allow(ENV).to receive(:[]).with("VCAP_SERVICES").and_return(vcap_services)
allow(Aws::S3::Client).to receive(:new).and_return(s3_client_stub)
end
it "retrieves the previously written object successfully if it exists" do
s3_client_stub.stub_responses(:get_object, { body: content })
data = subject.get_file_io(filename)
expect(data.string).to eq(content)
end
it "fails when the object does not exist" do
s3_client_stub.stub_responses(:get_object, "NoSuchKey")
expect { subject.get_file_io("fake_filename") }
.to raise_error(Aws::S3::Errors::NoSuchKey)
end
it "writes to the storage with the expected parameters" do
expect(s3_client_stub).to receive(:put_object).with(body: content,
bucket: bucket_name,
key: filename)
subject.write_file(filename, content)
end
end
end
Loading…
Cancel
Save