Skip to content

Commit 838b41c

Browse files
committed
Load files from the s3 API
1 parent 4629c23 commit 838b41c

File tree

11 files changed

+118
-11
lines changed

11 files changed

+118
-11
lines changed

.github/workflows/ruby.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,16 @@ jobs:
1919
with:
2020
ruby-version: ${{ matrix.ruby }}
2121
bundler-cache: true
22+
- name: Start MinIO
23+
run: docker compose up -d minio
24+
- name: Wait for MinIO to be ready
25+
run: |
26+
timeout 60 bash -c 'until curl -f http://localhost:9000/minio/health/ready; do sleep 2; done'
2227
- name: Run tests
2328
run: bin/rake test:prepare spec
29+
- name: Stop MinIO
30+
if: always()
31+
run: docker compose down
2432
lint:
2533
runs-on: ubuntu-latest
2634
steps:

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,5 @@ gem 'connection_pool'
8282
group :production do
8383
gem 'newrelic_rpm'
8484
end
85+
86+
gem "aws-sdk-s3", "~> 1.199"

Gemfile.lock

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,25 @@ GEM
7777
airbrussh (1.5.3)
7878
sshkit (>= 1.6.1, != 1.7.0)
7979
ast (2.4.3)
80+
aws-eventstream (1.4.0)
81+
aws-partitions (1.1172.0)
82+
aws-sdk-core (3.233.0)
83+
aws-eventstream (~> 1, >= 1.3.0)
84+
aws-partitions (~> 1, >= 1.992.0)
85+
aws-sigv4 (~> 1.9)
86+
base64
87+
bigdecimal
88+
jmespath (~> 1, >= 1.6.1)
89+
logger
90+
aws-sdk-kms (1.113.0)
91+
aws-sdk-core (~> 3, >= 3.231.0)
92+
aws-sigv4 (~> 1.5)
93+
aws-sdk-s3 (1.199.1)
94+
aws-sdk-core (~> 3, >= 3.231.0)
95+
aws-sdk-kms (~> 1)
96+
aws-sigv4 (~> 1.5)
97+
aws-sigv4 (1.12.1)
98+
aws-eventstream (~> 1, >= 1.0.2)
8099
base64 (0.3.0)
81100
bcrypt_pbkdf (1.1.1)
82101
bcrypt_pbkdf (1.1.1-arm64-darwin)
@@ -186,6 +205,7 @@ GEM
186205
jbuilder (2.14.1)
187206
actionview (>= 7.0.0)
188207
activesupport (>= 7.0.0)
208+
jmespath (1.6.2)
189209
json (2.15.1)
190210
jwt (3.1.2)
191211
base64
@@ -422,6 +442,7 @@ PLATFORMS
422442
x86_64-linux
423443

424444
DEPENDENCIES
445+
aws-sdk-s3 (~> 1.199)
425446
bootsnap (>= 1.4.2)
426447
cancancan
427448
capistrano (~> 3.0)

app/controllers/file_controller.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
##
44
# API for delivering files from stacks
55
class FileController < ApplicationController
6+
include ActionController::Live
7+
68
rescue_from ActionController::MissingFile do
79
render plain: 'File not found', status: :not_found
810
end
@@ -24,7 +26,15 @@ def show
2426
ip: request.remote_ip
2527
)
2628

27-
send_file current_file.path, filename: current_file.file_name, disposition:
29+
send_stream(
30+
filename: current_file.file_name, # Sets the filename for the download
31+
type: current_file.content_type, # Sets the content type
32+
disposition:
33+
) do |stream|
34+
current_file.s3_object do |chunk|
35+
stream.write(chunk)
36+
end
37+
end
2838
end
2939
# rubocop:enable Metrics/AbcSize
3040

app/models/s3_client_factory.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# frozen_string_literal: true
2+
3+
# Creates an S3 client instance.
4+
class S3ClientFactory
5+
def self.create_client
6+
Aws::S3::Client.new(
7+
region: 'us-east-1',
8+
endpoint: Settings.s3.endpoint,
9+
force_path_style: true, # Often required for custom endpoints
10+
access_key_id: Settings.s3.access_key_id,
11+
secret_access_key: Settings.s3.secret_access_key
12+
)
13+
end
14+
end

app/models/stacks_file.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ def readable?
2727
path && File.world_readable?(path)
2828
end
2929

30+
def s3_object(&)
31+
S3ClientFactory.create_client.get_object(bucket: Settings.s3.bucket, key: storage_root.relative_path, &)
32+
rescue Aws::S3::Errors::NoSuchKey
33+
raise "Unable to find file at #{path}"
34+
end
35+
3036
def mtime
3137
@mtime ||= File.mtime(path) if readable?
3238
end
@@ -39,6 +45,10 @@ def content_length
3945
@content_length ||= File.size(path) if readable?
4046
end
4147

48+
def content_type
49+
cocina.find_file(file_name)['hasMimeType']
50+
end
51+
4252
def path
4353
@path ||= storage_root.absolute_path
4454
end

compose.yaml

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
1-
version: '3.6'
2-
31
services:
42
web:
53
build: .
64
ports:
75
- 3001:3000
86
environment:
9-
SECRET_KEY_BASE: 'e55fb68eb0f1f4f82fbb56eafeebf1c134518fd466d1c3d01b2bfd3d6b5cd9347b4c15eecb3bdc0e4c5bc226dba626a23bb7b9ddb14f709638571f910002f639'
10-
RAILS_LOG_TO_STDOUT: 'true'
7+
SECRET_KEY_BASE: "e55fb68eb0f1f4f82fbb56eafeebf1c134518fd466d1c3d01b2bfd3d6b5cd9347b4c15eecb3bdc0e4c5bc226dba626a23bb7b9ddb14f709638571f910002f639"
8+
RAILS_LOG_TO_STDOUT: "true"
119
REMOTE_USER: blalbrit@stanford.edu
12-
SETTINGS__CORS__ALLOW_ORIGIN_URL: 'http://localhost:3000'
13-
tty: true
10+
SETTINGS__CORS__ALLOW_ORIGIN_URL: "http://localhost:3000"
11+
tty: true
12+
minio:
13+
image: minio/minio
14+
environment:
15+
MINIO_ROOT_USER: minio-user
16+
MINIO_ROOT_PASSWORD: minio-password
17+
MINIO_UPDATE: off
18+
command: server /data --json --console-address ':9090'
19+
ports: ["9000:9000/tcp", "9090:9090/tcp"] # open http://127.0.0.1:9090 (9000 is the API port)
20+
volumes:
21+
- minio_data:/data
22+
volumes:
23+
minio_data:

config/settings.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ features:
44
stacks:
55
storage_root: /stacks
66

7+
s3:
8+
access_key_id: ~
9+
secret_access_key: ~
10+
bucket: stacks-test
11+
endpoint: https://sul-weka-s3.stanford.edu
12+
713
imageserver:
814
base_uri: "http://imageserver-prod.stanford.edu/iiif/2/"
915

config/settings/test.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,9 @@ metrics_api_url: "https://example.com"
1515

1616
stacks:
1717
storage_root: spec/fixtures
18+
19+
s3:
20+
access_key_id: minio-user
21+
secret_access_key: minio-password
22+
bucket: stacks-test
23+
endpoint: http://localhost:9000

spec/rails_helper.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,26 @@
6060
# The different available types are documented in the features, such as in
6161
# https://relishapp.com/rspec/rspec-rails/docs
6262
config.infer_spec_type_from_file_location!
63+
64+
config.before(:suite) do
65+
s3_client = S3ClientFactory.create_client
66+
bucket_name = Settings.s3.bucket
67+
begin
68+
s3_client.create_bucket(bucket: bucket_name)
69+
rescue Aws::S3::Errors::BucketAlreadyOwnedByYou
70+
# Bucket already exists, do nothing
71+
end
72+
73+
# Add test files into MinIO
74+
object_key = 'bb/000/cr/7262/bb000cr7262/content/8ff299eda08d7c506273840d52a03bf3'
75+
file_path = "spec/fixtures/#{object_key}"
76+
File.open(file_path, 'rb') do |file|
77+
s3_client.put_object(
78+
bucket: bucket_name,
79+
key: object_key,
80+
body: file
81+
)
82+
end
83+
end
6384
end
85+
WebMock.disable_net_connect!(allow_localhost: true)

0 commit comments

Comments
 (0)