lamby 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c73918d04cbb3595e6894ba9d15a3c15199fb01aa4eab940d4985b057e234d59
4
- data.tar.gz: 25e3a650683c3d3f5a5a30364370b60c29c40e868dc4e93aad1766e94d3aaefb
3
+ metadata.gz: 2da2381e04438228fa63901ffd863bc2d1f97bf84e834cb57d40fc8d2353fc4e
4
+ data.tar.gz: 8bd9e4a546114e937fc3da4efa0fddd1e5d0800e5b757145662ac0d7e3c603e1
5
5
  SHA512:
6
- metadata.gz: f712aaaba4fbe4bc44cdd238084d2049c98f09a3ccfe7df97f66f272b4e5bc7dcd4babe93f22beff16cc0a43c1b4f53d92bc2090ff8460f55eab3bc52465291b
7
- data.tar.gz: 597c37b84b04dcd00be778f339fac9f0709afb94d14cb7111308f56de6a892e55bf3a022c34dec2900b4ce9f4f53f3b83ba71f365408bc6493e846b5009ea496
6
+ metadata.gz: ff7c934023c0237523b96cf5ebb374d3866c4eecadc3790615fc783e8f93db32ebf99b7c3c96b309debab0eaff54f8e5e1680d8e58bdbc67c3a96d954fe9fdb5
7
+ data.tar.gz: c9ea5922cd3e329b0b4665d0a7a45c2d0f8155a7cb25928ba35ea719d81511465263b94ee195b8b378761460c2fdba84d03fc42444f2ed92a573b04b8387a81f
data/.gitignore CHANGED
@@ -2,7 +2,6 @@
2
2
  /.yardoc
3
3
  /_yardoc/
4
4
  /coverage/
5
- /doc/
6
5
  /pkg/
7
6
  /spec/reports/
8
7
  /tmp/
@@ -1,13 +1,27 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lamby (0.1.0)
4
+ lamby (0.2.0)
5
+ activesupport
6
+ rack
5
7
 
6
8
  GEM
7
9
  remote: https://rubygems.org/
8
10
  specs:
11
+ activesupport (5.2.2)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 0.7, < 2)
14
+ minitest (~> 5.1)
15
+ tzinfo (~> 1.1)
16
+ concurrent-ruby (1.1.4)
17
+ i18n (1.6.0)
18
+ concurrent-ruby (~> 1.0)
9
19
  minitest (5.11.3)
20
+ rack (2.0.6)
10
21
  rake (12.3.2)
22
+ thread_safe (0.3.6)
23
+ tzinfo (1.2.5)
24
+ thread_safe (~> 0.1)
11
25
 
12
26
  PLATFORMS
13
27
  ruby
data/README.md CHANGED
@@ -1,42 +1,373 @@
1
+
1
2
  # Lamby
2
3
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/lamby`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+ <img src="https://git.io/fhjSv" alt="Lamby: Simple Rails & AWS Lambda Integration using Rack." align="right" /><h3>Simple Rails & AWS Lambda Integration using Rack</h3>
5
+
6
+ The goal of this project is to provide minimal code along with comprehensive documentation to get your Rails application running under AWS Lambda.
7
+
8
+ This gem's code will focus mainly on converting API Gateway `event` and `context` objects into a Rack `env` to send to your Rails application. Most everything else is documentation. Please use the table of contents below to quickly navigate to any guides that interests you the most.
9
+
10
+ ### Table of Contents
11
+
12
+ * [How Does Lamby Work?](#how-does-lamby-work)
13
+ * [Getting Started](#getting-started)
14
+ * [Installing AWS CLI and AWS SAM](#installing-aws-cli-and-aws-sam)
15
+ * [Bin Script Conventions](#bin-script-conventions)
16
+ * [SAM Bugs and Patches](#sam-bugs-and-patches)
17
+ * [About AWS SAM and CloudFormation](#about-aws-sam-and-cloudformation)
18
+ * [Performance](#performance)
19
+ * [Database Connections](#database-connections)
20
+ * [Basic Ruby and Lambda](#basic-ruby-and-lambda)
21
+ * [Contributing](#contributing)
22
+ * [Code of Conduct](#code-of-conduct)
23
+
24
+ The following are code and/or documentation items we are currently working on. Please open an issue and suggest a new one if you do not see it here. Thanks!
25
+
26
+ * [ ] Ensure Rack integration is solid and high quality.
27
+ - Cookies, Sessions, Query Params, Forms, etc.
28
+ * [ ] Encrypted Session Secret via AWS System Manager Parameter Store.
29
+ * [ ] Better Gemfile usage for development and/or test groups.
30
+ * [ ] Hooking up Rails asset precompile to a S3 bucket and using an asset host.
31
+ * [ ] Ensure Rails tagged UUID logging matches APIGateway/Lambda IDs in CloudWatch.
32
+ * [ ] Documentation around using ImageMagick and Lambda Layers.
33
+ - https://twitter.com/clare_liguori/status/1087861037712400385
34
+ - https://github.com/mthenw/awesome-layers
35
+ * [ ] Explore how New Relic could be used if at all.
36
+ * [ ] Add `isBase64Encoded` to response as needed.
37
+ * [ ] Better Railtie hooks, and gem structure. Tests.
38
+
39
+
40
+ ## How Does Lamby Work?
41
+
42
+ Since [Rails is on Rack](https://guides.rubyonrails.org/rails_on_rack.html), the Lamby gem relies on converting API Gateway events sent to your `handler` and converting them to a [Rack](https://rack.github.io) `env` object. We then send that object to your application and pass the result back to the Lambda handler which expects a simple object/hash containing the `statusCode`, `body`, and `headers`. It is that simple.
43
+
44
+ Thanks to the projects and people below which inspired our code and implementation strategies.
45
+
46
+ * [AWS Sinatra Example](https://github.com/aws-samples/serverless-sinatra-sample)
47
+ * [Rack Lambda Handler Pull Request](https://github.com/rack/rack/pull/1337)
48
+ * [Serverless Rack Plugin](https://github.com/logandk/serverless-rack)
49
+ * [Jets' Rack Implementation](https://github.com/tongueroo/jets/blob/master/lib/jets/controller/rack/env.rb)
50
+
51
+ Other small details which Lamby helps with.
52
+
53
+ * Ensure all `Logger` objects use `STDOUT`.
54
+ * Sets the `RAILS_LOG_TO_STDOUT` environment variable.
55
+ * Provides a debug response in development (or when `LAMBY_DEBUG` env set) using `?debug=1` query param.
56
+
4
57
 
5
- TODO: Delete this and the text above, and describe your gem
58
+ ## Getting Started
6
59
 
7
- ## Installation
60
+ ℹ️ Some of these steps may be automated once we learn they are worth automating.
8
61
 
9
- Add this line to your application's Gemfile:
62
+ #### New Basic Rails Application Example
63
+
64
+ Assuming you have a basic Rails application, or maybe you want to make a new one from scratch.
65
+
66
+ ```shell
67
+ $ gem install rails
68
+ $ rails new my_app \
69
+ --skip-active-record \
70
+ --skip-action-mailer \
71
+ --skip-active-storage \
72
+ --skip-action-cable \
73
+ --skip-spring \
74
+ --skip-coffee \
75
+ --skip-turbolinks \
76
+ --skip-bootsnap
77
+ $ cd my_app
78
+ $ rbenv local 2.5.3
79
+ ```
80
+
81
+ #### Add The Gem
82
+
83
+ Add the Lamby gem to your Rails project's `Gemfile`.
10
84
 
11
85
  ```ruby
12
86
  gem 'lamby'
13
87
  ```
14
88
 
15
- And then execute:
89
+ #### Create Handler
90
+
91
+ Create an `app.rb` file that will be the source for the Lambda's handler. Example:
92
+
93
+ ```ruby
94
+ ENV['SECRET_KEY_BASE'] = '...' # Temporary hack, see TODO list above.
95
+ require_relative 'config/boot'
96
+ require 'lamby'
97
+ require_relative 'config/application'
98
+ require_relative 'config/environment'
99
+
100
+ $app = Rack::Builder.new { run Rails.application }.to_app
101
+
102
+ def handler(event:, context:)
103
+ Lamby.handler $app, event, context
104
+ end
105
+ ```
106
+
107
+ #### Create SAM Template
108
+
109
+ Create an [AWS SAM](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md) `template.yaml` file that expresses your function's resources and how it receives events from API Gateway. Please use this full [doc/template.yaml](doc/template.yaml) file hosted here as a starting point. The basic template accomplishes the following.
16
110
 
17
- $ bundle
111
+ * Defines a `RailsEnv` input parameter.
112
+ - Applies this to the `RAILS_ENV` environment variable.
113
+ * Creates a API Gateway resource named `RailsApi`.
114
+ - Ensures that the stage name is set to the Rails env.
115
+ - Using inline Swagger, define a root `/` via GET and greedy proxy `/{resource+}` via ANY.
116
+ - Allow any binary media type to be a valid response.
117
+ * Creates a Lambda function resource named `RailsFunction`.
118
+ - Sets the handler to `app.handler`. File above.
119
+ - Defines events from API above for both root and greedy proxy.
120
+ * Simple `Outputs` that echos the resources you created.
18
121
 
19
- Or install it yourself as:
122
+ #### Git Ignores
20
123
 
21
- $ gem install lamby
124
+ Ensure both `/.aws-sam` and `/vendor/bundle` are added to `.gitignore`. AWS SAM uses the `.aws-sam` directory to build your project. No file there should be committed to source control.
22
125
 
23
- ## Usage
126
+ ```shell
127
+ $ echo '/.aws-sam' >> .gitignore
128
+ $ echo '/vendor/bundle' >> .gitignore
129
+ ```
24
130
 
25
- TODO: Write usage instructions here
131
+ #### Get Running
26
132
 
27
- ## Development
133
+ To run your Lambda locally or deploy it, please read the following sections:
134
+
135
+ * [Installing AWS CLI and AWS SAM](#installing-aws-cli-and-aws-sam)
136
+ * [Bin Script Conventions](#bin-script-conventions)
137
+
138
+
139
+ ## Installing AWS CLI and AWS SAM
140
+
141
+ You will need both of these tools to run SAM locally and/or deploy your Lambda application to AWS. SAM also requires the usage of Docker.
142
+
143
+ * [Install SAM CLI](https://aws.amazon.com/serverless/sam/)
144
+ * [Installing the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html)
145
+ * [Install Docker](https://docs.docker.com/install/)
146
+
147
+
148
+ ## Bin Script Conventions
149
+
150
+ Below is a list of bin scripts we recommend creating for your AWS SAM & Rails projects. Each section contains a short description on what the script does. Remember, think of these scripts as starting points! You should add to or adjust each to meet your needs while following [Strap](https://github.com/MikeMcQuaid/strap) and [Scripts to Rule Them All](https://githubengineering.com/scripts-to-rule-them-all/) conventions.
151
+
152
+ #### bin/bootstrap
153
+
154
+ The current Docker image for Lambda's Ruby runtime is only compatible with Bundler prior to version 2.0.0. So this bootstrap makes sure the latest 1.x bundler is installed and used.
155
+
156
+ ```shell
157
+ #!/bin/bash
158
+ set -e
159
+
160
+ gem uninstall -aIx bundler
161
+ gem install bundler -v 1.17.3
162
+ ```
163
+
164
+ #### bin/setup
165
+
166
+ Nothing fancy here, just calling build where most of the work is done.
167
+
168
+ ```shell
169
+ #!/bin/bash
170
+ set -e
171
+
172
+ ./bin/build
173
+ ```
174
+
175
+ #### bin/build
176
+
177
+ This makes use of the [sam build](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-build.html) command along with the `--use-container` option to ensure gems with native extensions are built for both the AWS' Linux platform and your native host platform. It also:
178
+
179
+ * Sets up a pristine build environment.
180
+ * Removes unnecessary project files copied over during the build.
181
+ * Cleans build directory's vendor gems of unwanted cache files. Saves ~40% [package size](https://docs.aws.amazon.com/lambda/latest/dg/limits.html).
182
+ * Builds local platform gems on top of copied Linux platform gems.
183
+
184
+ ```shell
185
+ #!/bin/bash
186
+ set -e
187
+
188
+ # Clean any previous bundle dir.
189
+ rm -rf ./.bundle \
190
+ ./vendor/bundle \
191
+ ./.aws-sam/build \
192
+ node_modules
193
+
194
+ # Clean up Rails
195
+ ./bin/rails log:clear tmp:clear
196
+
197
+ # Ensure native extensions built for platform.
198
+ sam build --use-container
199
+
200
+ # Clean build dir of unneeded artifacts.
201
+ pushd ./.aws-sam/build/RailsFunction/
202
+ rm -rf .aws-sam \
203
+ .git \
204
+ node_modules \
205
+ test \
206
+ template.yaml \
207
+ package.json \
208
+ yarn.lock \
209
+ tmp
210
+ rm -rf vendor/bundle/ruby/2.5.0/cache
211
+ popd
212
+
213
+ # Avoid doing local bundle work if building for deploy.
214
+ if [ -z ${SKIP_LOCAL_BUNDLE+x} ]; then
215
+ # Copy the build bundle to allow server native extensions to work.
216
+ cp -R ./.aws-sam/build/RailsFunction/vendor/bundle ./vendor/bundle
217
+ # Make a comingled bundle dir for build and this platform.
218
+ rm -rf ./.bundle
219
+ bundle install --path vendor/bundle
220
+ fi
221
+ ```
28
222
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
223
+ #### bin/deploy
224
+
225
+ Uses both the [sam package](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-package.html) and [sam deploy](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-deploy.html) commands to ship your Lambda stack to AWS. It also:
226
+
227
+ * Uses or sets the `RAILS_ENV` variable. Defaults to `development`.
228
+ * Uses or sets the `CF_BUCKET_NAME` variable. This is the name of the S3 bucket that holds your deploy artifacts.
229
+ * Sets the `SKIP_LOCAL_BUNDLE` environment variable used by build.
230
+
231
+ ```shell
232
+ #!/bin/bash
233
+ set -e
234
+
235
+ export RAILS_ENV=${RAILS_ENV:="development"}
236
+ export CF_BUCKET_NAME=${CF_BUCKET_NAME:="mycloudformationbucket.example.org"}
237
+ export SKIP_LOCAL_BUNDLE="1"
238
+
239
+ ./bin/build
240
+
241
+ sam package \
242
+ --template-file ./.aws-sam/build/template.yaml \
243
+ --output-template-file ./.aws-sam/build/packaged.yaml \
244
+ --s3-bucket $CF_BUCKET_NAME \
245
+ --s3-prefix "my-app-${RAILS_ENV}"
246
+
247
+ sam deploy \
248
+ --template-file ./.aws-sam/build/packaged.yaml \
249
+ --stack-name "my-app-${RAILS_ENV}" \
250
+ --capabilities "CAPABILITY_IAM" \
251
+ --parameter-overrides \
252
+ RailsEnv=${RAILS_ENV}
253
+ ```
254
+
255
+ So an example `bin/deploy-production` script would look like this.
256
+
257
+ ```shell
258
+ #!/bin/bash
259
+ set -e
260
+
261
+ export RAILS_ENV="production"
262
+
263
+ ./bin/deploy
264
+ ```
265
+
266
+ #### bin/server
267
+
268
+ Start the normal Rails development server. Make sure to run setup/build prior.
269
+
270
+ ```shell
271
+ #!/bin/bash
272
+ set -e
273
+
274
+ ./bin/rails server
275
+ ```
276
+
277
+ #### bin/server-sam
278
+
279
+ Uses the [sam local start-api](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-start-api.html) command to
280
+
281
+ ```shell
282
+ #!/bin/bash
283
+ set -e
284
+
285
+ # Delete the static build to avoid local server using it.
286
+ rm -rf ./.aws-sam/build
287
+ sam local start-api
288
+ ```
289
+
290
+
291
+ ## SAM Bugs and Patches
292
+
293
+ Unfortunately, the SAM CLI project has a few needed enhancements and if you decide to do any local development via the [sam local start-api](#binserver-sam) command, it will need to be patched on your local machine. I used the commands below to find the two files needing patching.
294
+
295
+ ```shell
296
+ $ which sam # Use directory info below in find command.
297
+ $ find /usr/local -name container.py | grep samcli
298
+ $ find /usr/local -name local_apigw_service.py | grep samcli
299
+ ```
300
+
301
+ Here are the issues we have created on the SAM CLI project. Details on the patches below.
302
+
303
+ * [Add Docker Delegated Consistency to Volume](https://github.com/awslabs/aws-sam-cli/pull/1046)
304
+ * [Missing Authentication Token for root path '/'](https://github.com/awslabs/aws-sam-cli/issues/437)
305
+
306
+ #### Docker Volume Mount Performance on Mac
307
+
308
+ If you are on a Mac, Docker has a [known performance issue](https://forums.docker.com/t/file-access-in-mounted-volumes-extremely-slow-cpu-bound/8076) when sharing volumes due to the overhead of keeping files in sync. This performance issue means your Rails application could take ~60 seconds to load in development. Thankfully, Docker has [tooling in place](https://docs.docker.com/docker-for-mac/osxfs-caching/) to help.
309
+
310
+ Once you found the correct `container.py` file, make this change below to tell the Docker SDK to mount the volume with both the `ro` (read-only) option and `delegated` consistency mode.
311
+
312
+ ```diff
313
+ @@ -95,7 +95,7 @@
314
+ # https://docs.docker.com/storage/bind-mounts
315
+ # Mount the host directory as "read only" inside container
316
+ "bind": self._working_dir,
317
+ - "mode": "ro"
318
+ + "mode": "ro,delegated"
319
+ }
320
+ },
321
+ # We are not running an interactive shell here.
322
+ ```
323
+
324
+ #### Fixing Root SAM Local Proxy Paths
325
+
326
+ We need our API Gateway to use both a root path and a greedy proxy path to forward to Rails in development. SAM has a feature compatibility bug where it forces static file hosting on the root path. Once you found the correct `local_apigw_service.py` file, make the change below to disable static `public` directory assets on the root path. Don't worry, Rails & Rack will serve your static files automatically instead.
327
+
328
+ ```diff
329
+ @@ -71,7 +71,7 @@
330
+ """
331
+
332
+ self._app = Flask(__name__,
333
+ - static_url_path="", # Mount static files at root '/'
334
+ + static_url_path=None, # Mount static files at root '/'
335
+ static_folder=self.static_dir # Serve static files from this directory
336
+ )
337
+ ```
338
+
339
+
340
+ ## About AWS SAM and CloudFormation
341
+
342
+ AWS SAM is shorthand for the [Serverless Application Model](https://github.com/awslabs/serverless-application-model) and it is a superset of CloudFormation - a language that describes and provisions your infrastructure using code. As your application grows and requires additional AWS resources, learning how to express this in your `template.yml` is critical. We recommend the following links when needing to learn both SAM and CloudFormation.
343
+
344
+ * [AWS Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md)
345
+ * [AWS CloudFormation User Guide](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html)
346
+
347
+
348
+ ## Performance
349
+
350
+ Because your Rails app is initialized outside the `handler` it should be loaded into memory and respond to requests in a very timely manner. Make sure to set the `MemorySize` as needed in your `template.yml` and you should get comparable performance to EC2. Initial tests show that basic view rendering only takes a few milliseconds. Please do share your performance results if you have any!
351
+
352
+
353
+ ## Database Connections
354
+
355
+ If you want to use a database with Rails and Lambda, I would highly suggest using DynamoDB with the excellent [AWS Record](https://github.com/aws/aws-sdk-ruby-record) gem. However, if you want to explore and share how you might use Lamby with PG or MySQL, I'd love to hear about it.
356
+
357
+
358
+ ## Basic Ruby and Lambda
359
+
360
+ Curious about using AWS SAM with Ruby and no Rails? Please see this 757rb (Norfolk Ruby User's Group) repository for an overview on how to kick-start your next Ruby project.
361
+
362
+ * [Using Ruby with AWS Lambda & SAM](https://github.com/757rb/hello-757rb-lambda)
30
363
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
364
 
33
365
  ## Contributing
34
366
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/metaskills/lamby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
367
+ After checking out the repo, run `./bin/setup` to install dependencies. Then, run `./bin/test` to run the tests. **NOTE: There are no tests now but adding them is on our TODO list.**
36
368
 
37
- ## License
369
+ Bug reports and pull requests are welcome on GitHub at https://github.com/metaskills/lamby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
38
370
 
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
40
371
 
41
372
  ## Code of Conduct
42
373
 
data/bin/setup CHANGED
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env bash
2
+
2
3
  set -euo pipefail
3
4
  IFS=$'\n\t'
4
5
  set -vx
5
6
 
6
7
  bundle install
7
-
8
- # Do any other automated setup that you need to do here
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ bundle exec rake test
@@ -0,0 +1,85 @@
1
+ AWSTemplateFormatVersion: '2010-09-09'
2
+ Transform: AWS::Serverless-2016-10-31
3
+ Description: Hello Rails Lambda
4
+
5
+ Parameters:
6
+
7
+ RailsEnv:
8
+ Type: String
9
+ Default: development
10
+ AllowedValues:
11
+ - development
12
+ - test
13
+ - staging
14
+ - production
15
+
16
+ Resources:
17
+
18
+ RailsApi:
19
+ Type: AWS::Serverless::Api
20
+ Properties:
21
+ StageName: !Ref RailsEnv
22
+ EndpointConfiguration: EDGE
23
+ DefinitionBody:
24
+ swagger: 2.0
25
+ info: { title: !Ref 'AWS::StackName' }
26
+ basePath: !Join [ '', [ '/', !Ref RailsEnv ] ]
27
+ schemes: [ 'https' ]
28
+ paths:
29
+ /:
30
+ get:
31
+ x-amazon-apigateway-integration:
32
+ uri:
33
+ Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RailsFunction.Arn}:live/invocations
34
+ httpMethod: POST
35
+ type: aws_proxy
36
+ /{resource+}:
37
+ x-amazon-apigateway-any-method:
38
+ parameters:
39
+ - name: resource
40
+ in: path
41
+ required: true
42
+ type: string
43
+ x-amazon-apigateway-integration:
44
+ uri:
45
+ Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RailsFunction.Arn}:live/invocations
46
+ httpMethod: POST
47
+ type: aws_proxy
48
+ x-amazon-apigateway-binary-media-types:
49
+ - '*/*'
50
+
51
+ RailsFunction:
52
+ Type: AWS::Serverless::Function
53
+ Properties:
54
+ CodeUri: .
55
+ Handler: app.handler
56
+ Runtime: ruby2.5
57
+ MemorySize: 512
58
+ Timeout: 30
59
+ Environment:
60
+ Variables:
61
+ RAILS_ENV: !Ref RailsEnv
62
+ Events:
63
+ Root:
64
+ Type: Api
65
+ Properties:
66
+ Path: /
67
+ Method: GET
68
+ RestApiId: !Ref RailsApi
69
+ RailsAppAll:
70
+ Type: Api
71
+ Properties:
72
+ Path: /{resource+}
73
+ Method: ANY
74
+ RestApiId: !Ref RailsApi
75
+ AutoPublishAlias: live
76
+
77
+ Outputs:
78
+
79
+ RailsApiUrl:
80
+ Description: API Gateway Endpoint
81
+ Value: !Sub "https://${RailsApi}.execute-api.${AWS::Region}.amazonaws.com/${RailsEnv}/"
82
+
83
+ RailsFunctionArn:
84
+ Description: Lambda Function ARN
85
+ Value: !GetAtt RailsFunction.Arn
@@ -1,7 +1,7 @@
1
1
 
2
- lib = File.expand_path("../lib", __FILE__)
2
+ lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "lamby/version"
4
+ require 'lamby/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "lamby"
@@ -18,7 +18,9 @@ Gem::Specification.new do |spec|
18
18
  spec.bindir = "exe"
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
- spec.add_development_dependency "bundler"
22
- spec.add_development_dependency "rake"
23
- spec.add_development_dependency "minitest"
21
+ spec.add_dependency 'rack'
22
+ spec.add_dependency 'activesupport'
23
+ spec.add_development_dependency 'bundler'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'minitest'
24
26
  end
@@ -1,6 +1,19 @@
1
- require "lamby/version"
1
+ require 'lamby/logger'
2
+ require 'rack'
3
+ require 'base64'
4
+ require 'active_support/all'
5
+ require 'lamby/version'
6
+ require 'lamby/sam_helpers'
7
+ require 'lamby/rack'
8
+ require 'lamby/debug'
9
+ require 'lamby/handler'
2
10
 
3
11
  module Lamby
4
- class Error < StandardError; end
5
- # Your code goes here...
12
+
13
+ extend self
14
+
15
+ def handler(app, event, context)
16
+ Handler.call(app, event, context)
17
+ end
18
+
6
19
  end
@@ -0,0 +1,46 @@
1
+ module Lamby
2
+ module Debug
3
+ include Lamby::SamHelpers
4
+
5
+ extend self
6
+
7
+ def on?(event)
8
+ params = event['queryStringParameters']
9
+ (Rails.env.development? || ENV['LAMBY_DEBUG']) && params && params['debug'] == '1'
10
+ end
11
+
12
+ def call(event, context, env)
13
+ [ 200, { 'Content-Type' => 'text/html' }, [body(event, context, env)] ]
14
+ end
15
+
16
+ private
17
+
18
+ def body(event, context, env)
19
+ <<-HTML
20
+ <!DOCTYPE html>
21
+ <html>
22
+ <body>
23
+ <h1>Lamby Debug Response</h1>
24
+ <h2>Event</h2>
25
+ <pre>
26
+ #{JSON.pretty_generate(event)}
27
+ </pre>
28
+ <h2>Rack Env</h2>
29
+ <pre>
30
+ #{JSON.pretty_generate(env)}
31
+ </pre>
32
+ <h2>#{context.class.name}</h2>
33
+ <code>
34
+ #{CGI::escapeHTML(context.inspect)}
35
+ </code>
36
+ <h2>Environment</h2>
37
+ <pre>
38
+ #{sam_local? ? JSON.pretty_generate(ENV.to_h) : 'N/A'}
39
+ </pre>
40
+ </body>
41
+ </html>
42
+ HTML
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,58 @@
1
+ module Lamby
2
+ class Handler
3
+
4
+ class << self
5
+
6
+ def call(app, event, context)
7
+ new(app, event, context).call.response
8
+ end
9
+
10
+ end
11
+
12
+ def initialize(app, event, context)
13
+ @app = app
14
+ @event = event
15
+ @context = context
16
+ @rack = Lamby::Rack.new event, context
17
+ @called = false
18
+ end
19
+
20
+ def response
21
+ { statusCode: status,
22
+ headers: headers,
23
+ body: body }
24
+ end
25
+
26
+ def status
27
+ @status
28
+ end
29
+
30
+ def headers
31
+ @headers
32
+ end
33
+
34
+ def body
35
+ @rbody ||= ''.tap do |rbody|
36
+ @body.each { |part| rbody << part }
37
+ end
38
+ end
39
+
40
+ def call
41
+ return self if @called
42
+ @status, @headers, @body = call_app
43
+ @called = true
44
+ self
45
+ end
46
+
47
+ private
48
+
49
+ def call_app
50
+ if Debug.on?(@event)
51
+ Debug.call @event, @context, @rack.env
52
+ else
53
+ @app.call @rack.env
54
+ end
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,23 @@
1
+ require 'logger'
2
+
3
+ ENV['RAILS_LOG_TO_STDOUT'] = '1'
4
+
5
+ module Lamby
6
+ module Logger
7
+
8
+ def initialize(*args)
9
+ args[0] = STDOUT
10
+ super(*args)
11
+ end
12
+
13
+ end
14
+ end
15
+
16
+ Logger.prepend Lamby::Logger
17
+
18
+ # TODO: Railtie initializer
19
+ # Rails.application.config.logger = ActiveSupport::TaggedLogging.new(
20
+ # ActiveSupport::Logger.new(STDOUT).tap { |logger|
21
+ # logger.formatter = Rails.application.config.log_formatter
22
+ # }
23
+ # )
@@ -0,0 +1,88 @@
1
+ module Lamby
2
+ class Rack
3
+
4
+ include SamHelpers
5
+
6
+ LAMBDA_EVENT = 'lambda.event'.freeze
7
+ LAMBDA_CONTEXT = 'lambda.context'.freeze
8
+ HTTP_X_APIGWSTAGE = 'HTTP_X_APIGWSTAGE'.freeze
9
+
10
+ attr_reader :event, :context
11
+
12
+ def initialize(event, context)
13
+ @event = event
14
+ @context = context
15
+ end
16
+
17
+ def env
18
+ @env ||= env_base.merge!(env_headers)
19
+ end
20
+
21
+ private
22
+
23
+ def env_base
24
+ { ::Rack::REQUEST_METHOD => event['httpMethod'],
25
+ ::Rack::SCRIPT_NAME => '',
26
+ ::Rack::PATH_INFO => event['path'] || '',
27
+ ::Rack::QUERY_STRING => query_string,
28
+ ::Rack::SERVER_NAME => headers['Host'],
29
+ ::Rack::SERVER_PORT => headers['X-Forwarded-Port'],
30
+ ::Rack::SERVER_PROTOCOL => server_protocol,
31
+ ::Rack::RACK_VERSION => ::Rack::VERSION,
32
+ ::Rack::RACK_URL_SCHEME => 'https',
33
+ ::Rack::RACK_INPUT => StringIO.new(body || ''),
34
+ ::Rack::RACK_ERRORS => $stderr,
35
+ ::Rack::RACK_MULTITHREAD => false,
36
+ ::Rack::RACK_MULTIPROCESS => false,
37
+ ::Rack::RACK_RUNONCE => false,
38
+ LAMBDA_EVENT => event,
39
+ LAMBDA_CONTEXT => context
40
+ }.tap do |env|
41
+ ct = content_type
42
+ cl = content_length
43
+ env[::Rack::CONTENT_TYPE] = ct if ct
44
+ env[::Rack::CONTENT_LENGTH] = cl if cl
45
+ end
46
+ end
47
+
48
+ def env_headers
49
+ headers.transform_keys do |key|
50
+ "HTTP_#{key.to_s.upcase.tr '-', '_'}"
51
+ end
52
+ end
53
+
54
+ def content_type
55
+ headers.delete('Content-Type') || headers.delete('content-type')
56
+ end
57
+
58
+ def content_length
59
+ bytesize = body.bytesize.to_s if body
60
+ headers.delete('Content-Length') || headers.delete('content-length') || bytesize
61
+ end
62
+
63
+ def body
64
+ @body ||= if event['body'] && base64_encoded?
65
+ Base64.decode64 event['body']
66
+ else
67
+ event['body']
68
+ end
69
+ end
70
+
71
+ def headers
72
+ event['headers'] || {}
73
+ end
74
+
75
+ def query_string
76
+ @query_string ||= event['queryStringParameters'].try(:to_query)
77
+ end
78
+
79
+ def base64_encoded?
80
+ event['isBase64Encoded']
81
+ end
82
+
83
+ def server_protocol
84
+ event.dig('requestContext', 'protocol') || 'HTTP/1.1'
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,9 @@
1
+ module Lamby
2
+ module SamHelpers
3
+
4
+ def sam_local?
5
+ ENV['AWS_SAM_LOCAL'] == 'true'
6
+ end
7
+
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module Lamby
2
- VERSION = "0.1.0"
2
+ VERSION = '0.2.0'
3
3
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lamby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ken Collins
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-03-10 00:00:00.000000000 Z
11
+ date: 2019-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: bundler
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -70,8 +98,15 @@ files:
70
98
  - Rakefile
71
99
  - bin/console
72
100
  - bin/setup
101
+ - bin/test
102
+ - doc/template.yml
73
103
  - lamby.gemspec
74
104
  - lib/lamby.rb
105
+ - lib/lamby/debug.rb
106
+ - lib/lamby/handler.rb
107
+ - lib/lamby/logger.rb
108
+ - lib/lamby/rack.rb
109
+ - lib/lamby/sam_helpers.rb
75
110
  - lib/lamby/version.rb
76
111
  homepage: https://github.com/customink/lamby
77
112
  licenses: