lamby-no-rails 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.python-version +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +17 -0
- data/Brewfile +5 -0
- data/CHANGELOG.md +109 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +168 -0
- data/LICENSE.txt +21 -0
- data/Pipfile +13 -0
- data/Pipfile.lock +370 -0
- data/README.md +37 -0
- data/Rakefile +12 -0
- data/bin/bootstrap +20 -0
- data/bin/console +14 -0
- data/bin/setup +6 -0
- data/bin/test +5 -0
- data/bin/update +7 -0
- data/lamby.gemspec +29 -0
- data/lib/lamby.rb +25 -0
- data/lib/lamby/debug.rb +46 -0
- data/lib/lamby/handler.rb +71 -0
- data/lib/lamby/logger.rb +23 -0
- data/lib/lamby/rack.rb +85 -0
- data/lib/lamby/rack_alb.rb +73 -0
- data/lib/lamby/rack_api.rb +36 -0
- data/lib/lamby/railtie.rb +12 -0
- data/lib/lamby/sam_helpers.rb +9 -0
- data/lib/lamby/ssm_parameter_store.rb +154 -0
- data/lib/lamby/tasks.rake +15 -0
- data/lib/lamby/templates.rake +36 -0
- data/lib/lamby/templates/api_gateway.rb +25 -0
- data/lib/lamby/templates/api_gateway/app.rb +10 -0
- data/lib/lamby/templates/api_gateway/build +23 -0
- data/lib/lamby/templates/api_gateway/deploy +22 -0
- data/lib/lamby/templates/api_gateway/template.yaml +84 -0
- data/lib/lamby/templates/application_load_balancer.rb +25 -0
- data/lib/lamby/templates/application_load_balancer/app.rb +10 -0
- data/lib/lamby/templates/application_load_balancer/build +23 -0
- data/lib/lamby/templates/application_load_balancer/deploy +39 -0
- data/lib/lamby/templates/application_load_balancer/template.yaml +90 -0
- data/lib/lamby/templates/shared.rb +15 -0
- data/lib/lamby/version.rb +3 -0
- metadata +199 -0
data/bin/setup
ADDED
data/bin/test
ADDED
data/bin/update
ADDED
data/lamby.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'lamby/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "lamby-no-rails"
|
8
|
+
spec.version = Lamby::VERSION
|
9
|
+
spec.authors = ["Ken Collins"]
|
10
|
+
spec.email = ["kcollins@customink.com"]
|
11
|
+
spec.summary = %q{Simple Rails & AWS Lambda Integration using Rack}
|
12
|
+
spec.description = %q{Simple Rails & AWS Lambda Integration using Rack and various utilities.}
|
13
|
+
spec.homepage = "https://github.com/customink/lamby"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
16
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
end
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
spec.add_dependency 'rack'
|
22
|
+
spec.add_development_dependency 'aws-sdk-ssm'
|
23
|
+
spec.add_development_dependency 'bundler'
|
24
|
+
spec.add_development_dependency 'rake'
|
25
|
+
spec.add_development_dependency 'minitest'
|
26
|
+
spec.add_development_dependency 'mocha'
|
27
|
+
spec.add_development_dependency 'pry'
|
28
|
+
spec.add_development_dependency 'sqlite3'
|
29
|
+
end
|
data/lib/lamby.rb
ADDED
@@ -0,0 +1,25 @@
|
|
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/rack_alb'
|
9
|
+
require 'lamby/rack_api'
|
10
|
+
require 'lamby/debug'
|
11
|
+
require 'lamby/handler'
|
12
|
+
require 'rails/railtie'
|
13
|
+
require 'lamby/railtie'
|
14
|
+
|
15
|
+
module Lamby
|
16
|
+
|
17
|
+
extend self
|
18
|
+
|
19
|
+
def handler(app, event, context, options = {})
|
20
|
+
Handler.call(app, event, context, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
autoload :SsmParameterStore, 'lamby/ssm_parameter_store'
|
24
|
+
|
25
|
+
end
|
data/lib/lamby/debug.rb
ADDED
@@ -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['multiValueQueryStringParameters'] || 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,71 @@
|
|
1
|
+
module Lamby
|
2
|
+
class Handler
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def call(app, event, context, options = {})
|
7
|
+
new(app, event, context, options).call.response
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(app, event, context, options = {})
|
13
|
+
@app = app
|
14
|
+
@event = event
|
15
|
+
@context = context
|
16
|
+
@options = options
|
17
|
+
@called = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def response
|
21
|
+
{ statusCode: status,
|
22
|
+
headers: headers,
|
23
|
+
body: body }.merge(rack_response)
|
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 rack
|
50
|
+
@rack ||= case @options[:rack]
|
51
|
+
when :api
|
52
|
+
Lamby::RackApi.new @event, @context
|
53
|
+
else
|
54
|
+
Lamby::RackAlb.new @event, @context
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def rack_response
|
59
|
+
rack.response(self)
|
60
|
+
end
|
61
|
+
|
62
|
+
def call_app
|
63
|
+
if Debug.on?(@event)
|
64
|
+
Debug.call @event, @context, rack.env
|
65
|
+
else
|
66
|
+
@app.call rack.env
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
data/lib/lamby/logger.rb
ADDED
@@ -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
|
+
# )
|
data/lib/lamby/rack.rb
ADDED
@@ -0,0 +1,85 @@
|
|
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_REQUESTID = 'HTTP_X_REQUEST_ID'.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
|
+
def response(_handler)
|
22
|
+
{}
|
23
|
+
end
|
24
|
+
|
25
|
+
def api?
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
def alb?
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
def multi_value?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def env_base
|
40
|
+
raise NotImplementedError
|
41
|
+
end
|
42
|
+
|
43
|
+
def env_headers
|
44
|
+
headers.transform_keys do |key|
|
45
|
+
"HTTP_#{key.to_s.upcase.tr '-', '_'}"
|
46
|
+
end.tap do |hdrs|
|
47
|
+
hdrs[HTTP_X_REQUESTID] = request_id
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def content_type
|
52
|
+
headers.delete('Content-Type') || headers.delete('content-type') || headers.delete('CONTENT_TYPE')
|
53
|
+
end
|
54
|
+
|
55
|
+
def content_length
|
56
|
+
bytesize = body.bytesize.to_s if body
|
57
|
+
headers.delete('Content-Length') || headers.delete('content-length') || headers.delete('CONTENT_LENGTH') || bytesize
|
58
|
+
end
|
59
|
+
|
60
|
+
def body
|
61
|
+
@body ||= if event['body'] && base64_encoded?
|
62
|
+
Base64.decode64 event['body']
|
63
|
+
else
|
64
|
+
event['body']
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def headers
|
69
|
+
@headers ||= event['headers'] || {}
|
70
|
+
end
|
71
|
+
|
72
|
+
def query_string
|
73
|
+
@query_string ||= event['queryStringParameters'].try(:to_query)
|
74
|
+
end
|
75
|
+
|
76
|
+
def base64_encoded?
|
77
|
+
event['isBase64Encoded']
|
78
|
+
end
|
79
|
+
|
80
|
+
def request_id
|
81
|
+
context.aws_request_id
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Lamby
|
2
|
+
class RackAlb < Lamby::Rack
|
3
|
+
|
4
|
+
def alb?
|
5
|
+
true
|
6
|
+
end
|
7
|
+
|
8
|
+
def multi_value?
|
9
|
+
event.key? 'multiValueHeaders'
|
10
|
+
end
|
11
|
+
|
12
|
+
def response(handler)
|
13
|
+
hhdrs = handler.headers
|
14
|
+
multivalue_headers = hhdrs.transform_values { |v| Array.wrap(v) } if multi_value?
|
15
|
+
status_description = "#{handler.status} #{::Rack::Utils::HTTP_STATUS_CODES[handler.status]}"
|
16
|
+
base64_encode = hhdrs['Content-Transfer-Encoding'] == 'binary' || hhdrs['X-Lamby-Base64'] == '1'
|
17
|
+
body = Base64.strict_encode64(handler.body) if base64_encode
|
18
|
+
{ multiValueHeaders: multivalue_headers,
|
19
|
+
statusDescription: status_description,
|
20
|
+
isBase64Encoded: base64_encode,
|
21
|
+
body: body }.compact
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def env_base
|
27
|
+
{ ::Rack::REQUEST_METHOD => event['httpMethod'],
|
28
|
+
::Rack::SCRIPT_NAME => '',
|
29
|
+
::Rack::PATH_INFO => event['path'] || '',
|
30
|
+
::Rack::QUERY_STRING => query_string,
|
31
|
+
::Rack::SERVER_NAME => headers['host'],
|
32
|
+
::Rack::SERVER_PORT => headers['x-forwarded-port'],
|
33
|
+
::Rack::SERVER_PROTOCOL => 'HTTP/1.1',
|
34
|
+
::Rack::RACK_VERSION => ::Rack::VERSION,
|
35
|
+
::Rack::RACK_URL_SCHEME => headers['x-forwarded-proto'],
|
36
|
+
::Rack::RACK_INPUT => StringIO.new(body || ''),
|
37
|
+
::Rack::RACK_ERRORS => $stderr,
|
38
|
+
::Rack::RACK_MULTITHREAD => false,
|
39
|
+
::Rack::RACK_MULTIPROCESS => false,
|
40
|
+
::Rack::RACK_RUNONCE => false,
|
41
|
+
LAMBDA_EVENT => event,
|
42
|
+
LAMBDA_CONTEXT => context
|
43
|
+
}.tap do |env|
|
44
|
+
ct = content_type
|
45
|
+
cl = content_length
|
46
|
+
env['CONTENT_TYPE'] = ct if ct
|
47
|
+
env['CONTENT_LENGTH'] = cl if cl
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def headers
|
52
|
+
@headers ||= multi_value? ? headers_multi : super
|
53
|
+
end
|
54
|
+
|
55
|
+
def headers_multi
|
56
|
+
(event['multiValueHeaders'] || {}).transform_values do |v|
|
57
|
+
v.is_a?(Array) ? v.first : v
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def query_string
|
62
|
+
@query_string ||= multi_value? ? query_string_multi : super
|
63
|
+
end
|
64
|
+
|
65
|
+
def query_string_multi
|
66
|
+
query = event['multiValueQueryStringParameters'] || {}
|
67
|
+
string = query.map do |key, value|
|
68
|
+
value.map{ |v| "#{key}=#{v}" }.join('&')
|
69
|
+
end.flatten.join('&')
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Lamby
|
2
|
+
class RackApi < Lamby::Rack
|
3
|
+
|
4
|
+
def api?
|
5
|
+
true
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def env_base
|
11
|
+
{ ::Rack::REQUEST_METHOD => event['httpMethod'],
|
12
|
+
::Rack::SCRIPT_NAME => '',
|
13
|
+
::Rack::PATH_INFO => event['path'] || '',
|
14
|
+
::Rack::QUERY_STRING => query_string,
|
15
|
+
::Rack::SERVER_NAME => headers['Host'],
|
16
|
+
::Rack::SERVER_PORT => headers['X-Forwarded-Port'],
|
17
|
+
::Rack::SERVER_PROTOCOL => event.dig('requestContext', 'protocol') || 'HTTP/1.1',
|
18
|
+
::Rack::RACK_VERSION => ::Rack::VERSION,
|
19
|
+
::Rack::RACK_URL_SCHEME => 'https',
|
20
|
+
::Rack::RACK_INPUT => StringIO.new(body || ''),
|
21
|
+
::Rack::RACK_ERRORS => $stderr,
|
22
|
+
::Rack::RACK_MULTITHREAD => false,
|
23
|
+
::Rack::RACK_MULTIPROCESS => false,
|
24
|
+
::Rack::RACK_RUNONCE => false,
|
25
|
+
LAMBDA_EVENT => event,
|
26
|
+
LAMBDA_CONTEXT => context
|
27
|
+
}.tap do |env|
|
28
|
+
ct = content_type
|
29
|
+
cl = content_length
|
30
|
+
env['CONTENT_TYPE'] = ct if ct
|
31
|
+
env['CONTENT_LENGTH'] = cl if cl
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|