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
5857Add to the [ Shrine] 's initializer file the Shrine-AWS-Lambda plugin registration with the ` :callback_url ` parameter,
5958and 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
7170By default, Shrine-AWS-Lambda is using the S3 bucket named ` :cache ` for retrieving the original file, and the ` :store `
7271named 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?
9493else
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
135122end
136123
137124Shrine .plugin :activerecord
138125Shrine .plugin :backgrounding
139126Shrine .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+
141141Shrine .plugin :rack_file # for non-Rails apps
142142Shrine .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) } ` .
150154This 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
155160catching 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
160165the information about how the file should be processed. A random generated string is appended to the assembly, stored
161166into 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
164169Processing 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.
168173The 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
184189class 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
221230end
222-
223231```
224232
225233The 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
253260Any 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
296303end
297-
298304```
299305
300306#### Backgrounding
301307
302308Even though submitting a Lambda assembly doesn't require any uploading, it still does a HTTP request, so it is better
303309to 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
307319Then 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
318332end
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
364378the 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