Skip to content

Commit 1be7e38

Browse files
authored
Merge pull request #3 from texpert/shrine-3
Upgrade to work with Shrine version 3
2 parents 4cd1ced + e611bca commit 1be7e38

8 files changed

Lines changed: 147 additions & 106 deletions

File tree

.github_changelog_generator

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
unreleased=true
2-
future-release=0.1.2
2+
future-release=0.2.0
33
exclude-labels=duplicate,question,invalid,wontfix,release

.rubocop.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ RSpec/ExampleLength:
3737
RSpec/MultipleExpectations:
3838
Description: Checks if examples contain too many `expect` calls.
3939
Enabled: true
40-
Max: 5
40+
Max: 10
4141

4242
RSpec/MultipleMemoizedHelpers:
4343
Description: Checks if example groups contain too many `let` and `subject` calls.
@@ -67,6 +67,7 @@ Style/StringLiterals:
6767
Metrics/AbcSize:
6868
# The ABC size is a calculated magnitude, so this number can be a Fixnum or
6969
# a Float.
70+
CountRepeatedAttributes: false
7071
Max: 30
7172

7273
Metrics/ClassLength:

README.md

Lines changed: 75 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# Shrine::Plugins::AwsLambda
2-
Provides [AWS Lambda] integration for [Shrine] File Attachment toolkit for Ruby applications
2+
Provides [AWS Lambda] integration for the [Shrine] File Attachment toolkit for Ruby applications
33

4-
This is a gem, renamed from initial [shrine-lambda](https://github.com/texpert/shrine-lambda) to [shrine-aws-lambda](https://github.com/texpert/shrine-aws-lambda)
5-
for clarity
4+
This is a gem, renamed from the initial [shrine-lambda](https://github.com/texpert/shrine-lambda) to [shrine-aws-lambda](https://github.com/texpert/shrine-aws-lambda) for clarity
65

76
## Description
87

@@ -53,7 +52,7 @@ either in the [Shrine] initializer, or in [default profile][AWS profiles] in the
5352
region: 'your AWS bucket region' }
5453
```
5554

56-
Also, for Lamda functions to work, various [AWS Lamda permissions] should be managed on the [Amazon Web Services] side.
55+
Also, for Lambda functions to work, various [AWS Lambda permissions] should be managed on the [Amazon Web Services] side.
5756

5857
Add to the [Shrine]'s initializer file the Shrine-AWS-Lambda plugin registration with the `:callback_url` parameter,
5958
and the [AWS Lambda] functions list retrieval call (which will retrieve the functions list on application initialization
@@ -71,11 +70,11 @@ and will store the list into the `Shrine.opts[:lambda_function_list]` for furthe
7170
By default, Shrine-AWS-Lambda is using the S3 bucket named `:cache` for retrieving the original file, and the `:store`
7271
named S3 bucket for storing the resulting files.
7372

74-
Srine-Lamda uses the [Shrine backgrounding plugin] for asynchronous operation, so this plugin should be also included
75-
into the Shrine's initializer.
73+
Shrine-AWS-Lambda uses the [Shrine backgrounding plugin] for asynchronous operation, so this plugin should be also
74+
included into the Shrine's initializer.
7675

77-
Here is a full example of a Shrine initializer of a [Rails] application using [Roda] endpoints for presigned_url's
78-
(used for direct file uploads to [AWS S3]) and [AWS Lambda] callbacks:
76+
Here is a full example of a Shrine initializer of a [Rails] application using a [Roda] endpoint for presigned_url
77+
(used for direct file uploads to [AWS S3]) and [AWS Lambda] callbacks):
7978

8079
```ruby
8180
# config/initializers/shrine.rb:
@@ -94,72 +93,78 @@ if Rails.env.test?
9493
else
9594
require 'shrine/storage/s3'
9695

97-
secrets = Rails.application.secrets
96+
aws_credentials = Rails.application.credentials.aws
9897

99-
s3_options = { access_key_id: secrets.aws_access_key_id,
100-
secret_access_key: secrets.aws_secret_access_key,
98+
s3_options = { access_key_id: aws_credentials[:access_key_id],
99+
secret_access_key: aws_credentials[:secret_access_key],
101100
region: 'us-east-2' }
102101

103102
if Rails.env.production?
104-
cache_bucket = store_bucket = secrets.aws_s3_bucket
103+
cache_bucket = store_bucket = aws_credentials.aws_s3_bucket
105104
else
106105
cache_bucket = 'texpert-test-cache'
107106
store_bucket = 'texpert-test-store'
108107
end
109108

110-
Shrine.storages = {
111-
cache: Shrine::Storage::S3.new(prefix: 'cache', **s3_options.merge(bucket: cache_bucket)),
112-
store: Shrine::Storage::S3.new(prefix: 'store', **s3_options.merge(bucket: store_bucket))
113-
}
114-
115-
lambda_callback_url = if Rails.env.development?
116-
"http://#{ENV['USER']}.localtunnel.me/rapi/lambda"
117-
else
118-
"https://#{ENV.fetch('APP_HOST')}/rapi/lambda"
119-
end
109+
Shrine.storages = { cache: Shrine::Storage::S3.new(prefix: 'cache', **s3_options.merge!(bucket: cache_bucket)),
110+
store: Shrine::Storage::S3.new(prefix: 'store', **s3_options.merge!(bucket: store_bucket)) }
120111

121-
shrine.plugin :aws_lambda, s3_options.merge(callback_url: lambda_callback_url)
122-
Shrine.lambda_function_list
123-
124-
Shrine.plugin :presign_endpoint, presign_options: ->(request) do
125-
filename = request.params['filename']
126-
extension = File.extname(filename)
127-
content_type = Rack::Mime.mime_type(extension)
112+
ActiveSupport::Reloader.to_prepare do
113+
lambda_callback_url = if Rails.env.development? && NGROK_ENABLED
114+
"#{NGROK_URL}/rapi/lambda"
115+
else
116+
"https://#{ENV['APP_HOST'] || 'localhost'}/rapi/lambda"
117+
end
128118

129-
{
130-
content_length_range: 0..1.gigabyte, # limit filesize to 1 GB
131-
content_disposition: "attachment; filename=\"#{filename}\"", # download with original filename
132-
content_type: content_type, # set correct content type
133-
}
119+
Shrine.plugin :aws_lambda, s3_options.merge!(callback_url: lambda_callback_url)
120+
Shrine.lambda_function_list
134121
end
135122
end
136123

137124
Shrine.plugin :activerecord
138125
Shrine.plugin :backgrounding
139126
Shrine.plugin :cached_attachment_data # for forms
140-
Shrine.plugin :logging, logger: Rails.logger
127+
128+
Shrine.logger = Rails.logger
129+
Shrine.plugin :instrumentation
130+
131+
Shrine.plugin :presign_endpoint, presign_options: lambda { |request|
132+
filename = request.params['filename']
133+
extension = File.extname(filename)
134+
content_type = Rack::Mime.mime_type(extension)
135+
136+
{ content_length_range: 0..1.gigabyte, # limit filesize to 1 GB
137+
content_disposition: "attachment; filename=\"#{filename}\"", # download with original filename
138+
content_type: content_type } # set correct content type
139+
}
140+
141141
Shrine.plugin :rack_file # for non-Rails apps
142142
Shrine.plugin :remote_url, max_size: 1.gigabyte
143143

144-
Shrine::Attacher.promote { |data| PromoteJob.perform_later(data) }
145-
Shrine::Attacher.delete { |data| DeleteJob.perform_later(data) }
144+
Shrine::Attacher.promote_block { PromoteJob.enqueue(self.class.name, record.class.name, record.id, name, file_data) }
145+
Shrine::Attacher.destroy_block { DeleteJob.enqueue(self.class.name, data) }
146+
```
147+
148+
Take notice that the promote job is a default, not AWS Lambda job:
146149

150+
```
151+
Shrine::Attacher.promote_block { PromoteJob.enqueue(self.class.name, record.class.name, record.id, name, file_data) }
147152
```
148153

149-
Take notice that the promote job is a default `Shrine::Attacher.promote { |data| PromoteJob.perform_later(data) }`.
150154
This is made to be able to use other than AWS storages in the test environment (like Shrine's `FileSystem` storage)
151-
and, also, other uploaders which are not using [AWS Lambda]. This job better to be overrided to a `LambdaPromoteJob`
152-
directly in the uploaders' classes which will use [AWS Lambda].
155+
and, also, other uploaders which are not using [AWS Lambda]. To use an AWS Lambda job, this job must be overridden
156+
to a `LambdaPromoteJob` directly in the uploaders' classes which will use [AWS Lambda] - see below in the **Usage**
157+
chapter.
153158

154-
Another thing used in this initializer is the [localtunnel] application for exposing the localhost to the world for
159+
Another thing used in this initializer is the [ngrok-wrapper] gem for exposing the localhost to the world for
155160
catching the Lambda callback requests.
156161

157162
#### How it works
158163

159-
Shrine-Lamnda works in such a way that an "assembly" should be created in the `LambdaUploader`, which contains all
164+
Shrine-AWS-Lambda works in such a way that an "assembly" should be created in the `LambdaUploader`, which contains all
160165
the information about how the file should be processed. A random generated string is appended to the assembly, stored
161166
into the cached file metadata, and used by the Lambda function to sign the requests to the `:lambda_callback_url`,
162-
along with the `:access_key_id` from the temporary credentials Lambda function is running with.
167+
along with the AWS `:access_key_id` from the AWS credentials Lambda function is running with.
163168

164169
Processing itself happens asynchronously - the invoked Lambda function will issue a PUT HTTP request to the
165170
`:lambda_callback_url`, specified in the Shrine's initializer, with the request's payload containing the processing
@@ -168,9 +173,9 @@ results.
168173
The request should be intercepted by a endpoint at the `:lambda_callback_url`, and its payload transferred to the
169174
`lambda_save` method on successful request authorization.
170175

171-
The authorization is calculatating the HTTP request signature using the random string stored in the cached file and
172-
the Lambda function's `:access_key_id` received in the request authorization header. Then, the calculated signature is
173-
compared to the received in the same authorization header Lambda signature.
176+
The authorization is calculating the HTTP request signature using the random string stored in the cached file and
177+
the AWS Lambda function's `:access_key_id` received in the request authorization header. Then, the calculated
178+
signature is compared to the received in the same authorization header AWS Lambda signature.
174179

175180
#### Usage
176181

@@ -182,7 +187,11 @@ Shrine-AWS-Lambda assemblies are built inside the `#lambda_process_versions` met
182187
# frozen_string_literal: true
183188

184189
class LambdaUploader < Uploader
185-
Attacher.promote { |data| LambdaPromoteJob.perform_later(data) } unless Rails.env.test?
190+
unless Rails.env.test?
191+
Attacher.promote_block do
192+
LambdaPromoteJob.enqueue(self.class.name, record.class.name, record.id, name, file_data)
193+
end
194+
end
186195

187196
plugin :upload_options, store: ->(_io, context) do
188197
if %i[avatar logo].include?(context[:name])
@@ -219,7 +228,6 @@ class LambdaUploader < Uploader
219228
assembly
220229
end
221230
end
222-
223231
```
224232

225233
The above example is built to interact with the [lambda-image-resize] function, which is using the [Sharp] Javascript
@@ -240,14 +248,13 @@ The default options used by Shrine-AWS-Lambda plugin are the following:
240248
target_storage: :store }
241249
```
242250

243-
These options could be overrided in the `LambdaUploader` specifying them as the `assembly` keys:
251+
These options could be overridden in the `LambdaUploader` specifying them as the `assembly` keys:
244252

245253
```ruby
246-
assembly[:callbackURL] = some_callback_url]
247-
assembly[:copy_original = false # If this is `false`, only the processed file versions will be stored
254+
assembly[:callbackURL] = some_callback_url
255+
assembly[:copy_original] = false # If this is `false`, only the processed file versions will be stored
248256
assembly[:storages] = Shrine.buckets_to_use(%i[cache store other_store])
249257
assembly[:target_storage] = :other_store
250-
251258
```
252259

253260
Any S3 buckets could be specified, as long as the buckets are defined in the Shrine's initializer file.
@@ -294,15 +301,20 @@ module RAPI
294301
end
295302
end
296303
end
297-
298304
```
299305

300306
#### Backgrounding
301307

302308
Even though submitting a Lambda assembly doesn't require any uploading, it still does a HTTP request, so it is better
303309
to put it into a background job. This is configured in the `LambdaUploader` class:
304310

305-
`Attacher.promote { |data| LambdaPromoteJob.perform_later(data) } unless Rails.env.test?`
311+
```ruby
312+
unless Rails.env.test?
313+
Attacher.promote_block do
314+
LambdaPromoteJob.enqueue(self.class.name, record.class.name, record.id, name, file_data)
315+
end
316+
end
317+
```
306318

307319
Then the job file should be implemented:
308320

@@ -311,9 +323,11 @@ Then the job file should be implemented:
311323

312324
# frozen_string_literal: true
313325

314-
class LambdaPromoteJob < ApplicationJob
315-
def perform(data)
316-
Timeout.timeout(30) { Shrine::Attacher.lambda_process(data) }
326+
class LambdaPromoteJob < Que::Job
327+
def run(...)
328+
ActiveRecord::Base.transaction do
329+
Shrine::Attacher.lambda_process(...)
330+
end
317331
end
318332
end
319333
```
@@ -360,15 +374,15 @@ by the following command:
360374
$ gem build shrine-aws-lambda.gemspec
361375
```
362376

363-
Assuming the version was set to `0.1.2`, a `shrine-aws-lambda-0.1.2.gem` binary file will be generated at the root of
377+
Assuming the version was set to `0.2.0`, a `shrine-aws-lambda-0.2.0.gem` binary file will be generated at the root of
364378
the app (repo).
365379

366380
- The binary file shouldn't be added into the `git` tree, it will be pushed into the RubyGems and to the GitHub releases
367381

368382
#### Pushing a new gem release to RubyGems
369383

370384
```bash
371-
$ gem push shrine-aws-lambda-0.1.2.gem # don't forget to specify the correct version number
385+
$ gem push shrine-aws-lambda-0.2.0.gem # don't forget to specify the correct version number
372386
```
373387

374388
#### Crafting the new release on GitHub
@@ -403,12 +417,12 @@ article], which pointed me to use the [Sharp] library for image resizing.
403417
[Amazon Web Services]: https://aws.amazon.com
404418
[AWS blog article]: https://aws.amazon.com/blogs/compute/resize-images-on-the-fly-with-amazon-s3-aws-lambda-and-amazon-api-gateway/
405419
[AWS Lambda]: https://aws.amazon.com/lambda
406-
[AWS Lamda permissions]: https://docs.aws.amazon.com/lambda/latest/dg/intro-permission-model.html
420+
[AWS Lambda permissions]: https://docs.aws.amazon.com/lambda/latest/dg/intro-permission-model.html
407421
[AWS profiles]: https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html
408422
[AWS S3]: https://aws.amazon.com/s3/
409423
[Janko]: https://github.com/janko-m
410424
[lambda-image-resize]: https://github.com/texpert/lambda-image-resize.js
411-
[localtunnel]: https://github.com/localtunnel/localtunnel
425+
[ngrok-wrapper]: https://github.com/texpert/ngrok-wrapper
412426
[Rails]: http://rubyonrails.org
413427
[Roda]: http://roda.jeremyevans.net
414428
[Sharp]: https://github.com/lovell/sharp

0 commit comments

Comments
 (0)