modulator 0.2.2 → 0.3.0
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 +4 -4
- data/.gitignore +6 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile +0 -4
- data/Gemfile.lock +26 -22
- data/README.md +243 -7
- data/lib/modulator.rb +82 -46
- data/lib/modulator/gateway/gateway.rb +4 -6
- data/lib/modulator/gateway/routes/console.rb +32 -34
- data/lib/modulator/gateway_event.json +60 -0
- data/lib/modulator/{lambda/aws_lambda_handler.rb → lambda_handler.rb} +2 -15
- data/lib/modulator/stack/builder.rb +256 -0
- data/lib/modulator/stack/policies.rb +99 -0
- data/lib/modulator/{lambda/aws_stack_uploader.rb → stack/uploader.rb} +59 -38
- data/lib/modulator/version.rb +1 -1
- data/lib/utils.rb +7 -1
- data/modulator.gemspec +1 -4
- metadata +9 -20
- data/lib/modulator/lambda/aws_stack_builder.rb +0 -225
@@ -0,0 +1,99 @@
|
|
1
|
+
module StackBuilder
|
2
|
+
module LambdaPolicy
|
3
|
+
module_function
|
4
|
+
|
5
|
+
# add inline iam role to lambda, NOTE: use the same role for all lambdas for now
|
6
|
+
def add_lambda_iam_role(function_name: nil)
|
7
|
+
StackBuilder.stack.add('LambdaRole', Humidifier::IAM::Role.new(
|
8
|
+
assume_role_policy_document: {
|
9
|
+
"Version" => "2012-10-17",
|
10
|
+
'Statement' => [
|
11
|
+
{
|
12
|
+
"Action" => ["sts:AssumeRole"],
|
13
|
+
"Effect" => "Allow",
|
14
|
+
'Principal' => {
|
15
|
+
'Service' => ["lambda.amazonaws.com"]
|
16
|
+
}
|
17
|
+
}
|
18
|
+
]
|
19
|
+
},
|
20
|
+
policies: []
|
21
|
+
)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_policy(policy, **opts)
|
26
|
+
StackBuilder.stack.resources['LambdaRole'].properties['policies'] << send(policy, opts)
|
27
|
+
end
|
28
|
+
|
29
|
+
# policy to access cloudwatch
|
30
|
+
def cloudwatch(**opts)
|
31
|
+
{
|
32
|
+
"policy_document" => {
|
33
|
+
"Version" => "2012-10-17",
|
34
|
+
'Statement' => [
|
35
|
+
{
|
36
|
+
"Sid" => "AllowLogCreation",
|
37
|
+
"Action" => [
|
38
|
+
"logs:CreateLogStream",
|
39
|
+
"logs:PutLogEvents",
|
40
|
+
],
|
41
|
+
"Effect" => "Allow",
|
42
|
+
"Resource" => Humidifier.fn.sub("arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*")
|
43
|
+
},
|
44
|
+
{
|
45
|
+
"Sid" => "AllowLogGroupCreation",
|
46
|
+
"Action" => [
|
47
|
+
"logs:CreateLogGroup",
|
48
|
+
],
|
49
|
+
"Effect" => "Allow",
|
50
|
+
"Resource" => "*"
|
51
|
+
}
|
52
|
+
]
|
53
|
+
},
|
54
|
+
"policy_name" => "cloud-watch-access"
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
# policy to access prefixed dynamo tables
|
59
|
+
def dynamo_db(**opts)
|
60
|
+
prefixes = opts[:prefixes] || []
|
61
|
+
prefix_separator = opts[:prefix_separator] || '-'
|
62
|
+
wildcard = '*'
|
63
|
+
if prefixes.any?
|
64
|
+
prefixes.map!{|prefix| prefix == :app_name ? StackBuilder.stack.app_name.dasherize.split('-') : prefix}
|
65
|
+
wildcard = "#{(prefixes << '*').join(prefix_separator)}"
|
66
|
+
end
|
67
|
+
{
|
68
|
+
"policy_document" => {
|
69
|
+
"Version" => "2012-10-17",
|
70
|
+
'Statement' => [
|
71
|
+
{
|
72
|
+
"Sid" => "AllowAllActionsOnPrefixedTable",
|
73
|
+
"Effect" => "Allow",
|
74
|
+
"Action" => [
|
75
|
+
"dynamodb:*"
|
76
|
+
],
|
77
|
+
"Resource" => Humidifier.fn.sub("arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/#{wildcard}")
|
78
|
+
},
|
79
|
+
{
|
80
|
+
"Sid" => "AdditionalPrivileges",
|
81
|
+
"Effect" => "Allow",
|
82
|
+
"Action" => [
|
83
|
+
"dynamodb:ListTables",
|
84
|
+
"dynamodb:DescribeTable"
|
85
|
+
],
|
86
|
+
"Resource" => "*"
|
87
|
+
}
|
88
|
+
]
|
89
|
+
},
|
90
|
+
"policy_name" => "dynamo-db-access"
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
# TODO: add access to named secrets
|
95
|
+
def secret_manager(**opts)
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -2,28 +2,49 @@ require 'aws-sdk-s3'
|
|
2
2
|
require 'digest'
|
3
3
|
require 'bundler'
|
4
4
|
|
5
|
-
module
|
5
|
+
module StackBuilder
|
6
6
|
module_function
|
7
7
|
|
8
8
|
S3Client = Aws::S3::Client.new
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
# bundle gems and upload all for simple lambda apps
|
11
|
+
def upload_lambda_files
|
12
|
+
puts '- bundling dependencies'
|
13
|
+
Bundler.with_clean_env do
|
14
|
+
Dir.chdir(app_path) do
|
15
|
+
`bundle install`
|
16
|
+
`bundle install --deployment --without development`
|
17
|
+
end
|
18
|
+
end
|
19
|
+
FileUtils.remove_dir(app_path.join("vendor/bundle/ruby/#{GEM_PATH_RUBY_VERSION}/cache")) # remove cache dir
|
20
|
+
upload_app_layer(sub_dirs: '', add_layer_to_stack: false) # reuse layer upload
|
21
|
+
FileUtils.remove_dir(app_path.join('.bundle'))
|
22
|
+
FileUtils.remove_dir(app_path.join('vendor'))
|
23
|
+
end
|
24
|
+
|
25
|
+
# generic handler for all lambda
|
26
|
+
def upload_generic_lambda_handler
|
12
27
|
lambda_handler_key = LAMBDA_HANDLER_FILE_NAME + '.rb.zip'
|
28
|
+
modulator_handler_source = Pathname.new(__FILE__).dirname.parent.join('lambda_handler.rb').read
|
13
29
|
source = <<~SOURCE
|
14
|
-
|
30
|
+
# DO NOT EDIT THIS FILE
|
31
|
+
|
32
|
+
#{modulator_handler_source}
|
15
33
|
Dir.chdir('/opt/ruby/lib')
|
16
34
|
SOURCE
|
17
35
|
|
18
36
|
existing_handler = S3Client.get_object(
|
19
|
-
bucket:
|
37
|
+
bucket: s3_bucket,
|
20
38
|
key: lambda_handler_key
|
21
39
|
) rescue false # not found
|
22
40
|
|
23
|
-
|
24
|
-
zip_file
|
25
|
-
|
26
|
-
|
41
|
+
if existing_handler
|
42
|
+
existing_source = Zip::InputStream.open(existing_handler.body) do |zip_file|
|
43
|
+
zip_file.get_next_entry
|
44
|
+
zip_file.read
|
45
|
+
end
|
46
|
+
self.lambda_handler_s3_object_version = existing_handler.version_id
|
47
|
+
end
|
27
48
|
|
28
49
|
if existing_source != source
|
29
50
|
puts '- uploading generic lambda handler'
|
@@ -31,11 +52,12 @@ module AwsStackBuilder
|
|
31
52
|
zip.put_next_entry LAMBDA_HANDLER_FILE_NAME + '.rb'
|
32
53
|
zip.print source
|
33
54
|
end
|
34
|
-
S3Client.put_object(
|
35
|
-
bucket:
|
55
|
+
new_handler = S3Client.put_object(
|
56
|
+
bucket: s3_bucket,
|
36
57
|
key: lambda_handler_key,
|
37
58
|
body: source_zip_file.tap(&:rewind).read
|
38
59
|
)
|
60
|
+
self.lambda_handler_s3_object_version = new_handler.version_id
|
39
61
|
end
|
40
62
|
end
|
41
63
|
|
@@ -51,7 +73,7 @@ module AwsStackBuilder
|
|
51
73
|
new_checksum = Digest::MD5.hexdigest(File.read(app_path.join('Gemfile.lock')))
|
52
74
|
|
53
75
|
zip_file_name = app_dir + '_gems.zip'
|
54
|
-
gems_path
|
76
|
+
gems_path = app_path.join(hidden_dir, 'gems')
|
55
77
|
gems_zip_path = app_path.join(hidden_dir, zip_file_name)
|
56
78
|
|
57
79
|
if old_checksum != new_checksum
|
@@ -61,14 +83,14 @@ module AwsStackBuilder
|
|
61
83
|
# bundle gems
|
62
84
|
Bundler.with_clean_env do
|
63
85
|
Dir.chdir(app_path) do
|
64
|
-
`bundle install --path=./#{hidden_dir}/gems --clean`
|
86
|
+
`bundle install --path=./#{hidden_dir}/gems --clean --without development`
|
65
87
|
end
|
66
88
|
end
|
67
89
|
ZipFileGenerator.new(gems_path, gems_zip_path).write
|
68
90
|
|
69
91
|
# upload zipped file
|
70
92
|
gem_layer = S3Client.put_object(
|
71
|
-
bucket:
|
93
|
+
bucket: s3_bucket,
|
72
94
|
key: zip_file_name,
|
73
95
|
body: gems_zip_path.read
|
74
96
|
)
|
@@ -77,7 +99,7 @@ module AwsStackBuilder
|
|
77
99
|
gems_zip_path.delete
|
78
100
|
else
|
79
101
|
puts '- using existing gems layer'
|
80
|
-
gem_layer = S3Client.get_object(bucket:
|
102
|
+
gem_layer = S3Client.get_object(bucket: s3_bucket, key: zip_file_name)
|
81
103
|
end
|
82
104
|
|
83
105
|
add_layer(
|
@@ -88,48 +110,53 @@ module AwsStackBuilder
|
|
88
110
|
)
|
89
111
|
end
|
90
112
|
|
91
|
-
def upload_app_layer
|
113
|
+
def upload_app_layer(sub_dirs: 'ruby/lib', add_layer_to_stack: true)
|
92
114
|
zip_file_name = app_dir + '.zip'
|
93
115
|
app_zip_path = app_path.join(hidden_dir, zip_file_name)
|
94
116
|
|
95
|
-
# copy app code to ruby/lib in
|
117
|
+
# copy app code to ruby/lib in outside temp dir
|
96
118
|
temp_dir_name = '.modulator_temp'
|
97
|
-
|
119
|
+
temp_sub_dirs = sub_dirs
|
98
120
|
temp_path = app_path.parent.join(temp_dir_name)
|
99
|
-
temp_path.join(
|
100
|
-
FileUtils.copy_entry app_path, temp_path.join(
|
121
|
+
temp_path.join(temp_sub_dirs).mkpath
|
122
|
+
FileUtils.copy_entry app_path, temp_path.join(temp_sub_dirs)
|
101
123
|
|
102
124
|
# calculate checksum for app folder
|
103
125
|
checksum_path = app_path.join(hidden_dir, 'app_checksum')
|
104
126
|
old_checksum = (checksum_path.read rescue nil)
|
105
|
-
new_checksum = checksum(app_path)
|
127
|
+
new_checksum = Utils.checksum(app_path)
|
106
128
|
|
107
129
|
if old_checksum != new_checksum
|
108
|
-
puts '- uploading app
|
130
|
+
puts '- uploading app files'
|
109
131
|
checksum_path.write(new_checksum)
|
110
132
|
ZipFileGenerator.new(temp_path, app_zip_path).write
|
111
133
|
# upload zipped file
|
112
134
|
app_layer = S3Client.put_object(
|
113
|
-
bucket:
|
135
|
+
bucket: s3_bucket,
|
114
136
|
key: zip_file_name,
|
115
137
|
body: app_zip_path.read
|
116
138
|
)
|
117
139
|
# delete zipped file
|
118
140
|
app_zip_path.delete
|
119
141
|
else
|
120
|
-
puts '- using existing app
|
121
|
-
app_layer = S3Client.get_object(bucket:
|
142
|
+
puts '- using existing app files'
|
143
|
+
app_layer = S3Client.get_object(bucket: s3_bucket, key: zip_file_name)
|
122
144
|
end
|
123
145
|
|
124
146
|
# delete temp dir
|
125
147
|
FileUtils.remove_dir(temp_path)
|
126
148
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
149
|
+
if add_layer_to_stack
|
150
|
+
add_layer(
|
151
|
+
name: app_name,
|
152
|
+
description: "App source. MD5: #{new_checksum}",
|
153
|
+
s3_key: zip_file_name,
|
154
|
+
s3_object_version: app_layer.version_id
|
155
|
+
)
|
156
|
+
else # for simple lambda apps
|
157
|
+
self.lambda_handler_s3_key = zip_file_name
|
158
|
+
self.lambda_handler_s3_object_version = app_layer.version_id
|
159
|
+
end
|
133
160
|
end
|
134
161
|
|
135
162
|
# add layer
|
@@ -139,17 +166,11 @@ module AwsStackBuilder
|
|
139
166
|
layer_name: name,
|
140
167
|
description: description,
|
141
168
|
content: {
|
142
|
-
s3_bucket:
|
169
|
+
s3_bucket: s3_bucket,
|
143
170
|
s3_key: s3_key,
|
144
171
|
s3_object_version: s3_object_version
|
145
172
|
}
|
146
173
|
)
|
147
174
|
)
|
148
175
|
end
|
149
|
-
|
150
|
-
def checksum(dir)
|
151
|
-
files = Dir["#{dir}/**/*"].reject{|f| File.directory?(f)}
|
152
|
-
content = files.map{|f| File.read(f)}.join
|
153
|
-
Digest::MD5.hexdigest(content)
|
154
|
-
end
|
155
176
|
end
|
data/lib/modulator/version.rb
CHANGED
data/lib/utils.rb
CHANGED
@@ -4,7 +4,7 @@ require 'zip'
|
|
4
4
|
# the patched classes are used only in tests and tools
|
5
5
|
class String
|
6
6
|
def camelize
|
7
|
-
split('_').collect do |word|
|
7
|
+
gsub('-', '_').split('_').collect do |word|
|
8
8
|
word[0] = word[0].upcase
|
9
9
|
word
|
10
10
|
end.join
|
@@ -56,6 +56,12 @@ module Utils
|
|
56
56
|
def load_json(path)
|
57
57
|
JSON.parse(File.read(path))
|
58
58
|
end
|
59
|
+
|
60
|
+
def checksum(dir)
|
61
|
+
files = Dir["#{dir}/**/*"].reject{|f| File.directory?(f)}
|
62
|
+
content = files.map{|f| File.read(f)}.join
|
63
|
+
Digest::MD5.hexdigest(content)
|
64
|
+
end
|
59
65
|
end
|
60
66
|
|
61
67
|
# NOTE: this code is taken from https://github.com/rubyzip/rubyzip examples
|
data/modulator.gemspec
CHANGED
@@ -29,10 +29,7 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.add_development_dependency "rack-test", "~> 1.1"
|
30
30
|
|
31
31
|
# stack builder
|
32
|
-
|
33
|
-
# spec.add_dependency "humidifier", github: 'damir/humidifier'
|
34
|
-
spec.add_dependency "aws-sdk-s3"
|
35
|
-
spec.add_dependency "aws-sdk-cloudformation"
|
32
|
+
spec.add_dependency "humidifier"
|
36
33
|
spec.add_dependency "rubyzip"
|
37
34
|
|
38
35
|
# local gateway
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: modulator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Damir Roso
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -67,21 +67,7 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.1'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: aws-sdk-cloudformation
|
70
|
+
name: humidifier
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
86
72
|
requirements:
|
87
73
|
- - ">="
|
@@ -160,6 +146,7 @@ files:
|
|
160
146
|
- ".gitignore"
|
161
147
|
- ".rspec"
|
162
148
|
- ".travis.yml"
|
149
|
+
- CHANGELOG.md
|
163
150
|
- CODE_OF_CONDUCT.md
|
164
151
|
- Gemfile
|
165
152
|
- Gemfile.lock
|
@@ -171,9 +158,11 @@ files:
|
|
171
158
|
- lib/modulator.rb
|
172
159
|
- lib/modulator/gateway/gateway.rb
|
173
160
|
- lib/modulator/gateway/routes/console.rb
|
174
|
-
- lib/modulator/
|
175
|
-
- lib/modulator/
|
176
|
-
- lib/modulator/
|
161
|
+
- lib/modulator/gateway_event.json
|
162
|
+
- lib/modulator/lambda_handler.rb
|
163
|
+
- lib/modulator/stack/builder.rb
|
164
|
+
- lib/modulator/stack/policies.rb
|
165
|
+
- lib/modulator/stack/uploader.rb
|
177
166
|
- lib/modulator/version.rb
|
178
167
|
- lib/utils.rb
|
179
168
|
- modulator.gemspec
|
@@ -1,225 +0,0 @@
|
|
1
|
-
require 'humidifier'
|
2
|
-
require_relative 'aws_stack_uploader'
|
3
|
-
|
4
|
-
module AwsStackBuilder
|
5
|
-
module_function
|
6
|
-
|
7
|
-
RUBY_VERSION = 'ruby2.5'
|
8
|
-
GEM_PATH = '/opt/ruby/2.5.0'
|
9
|
-
LAMBDA_HANDLER_FILE_NAME = 'lambda-handler'
|
10
|
-
|
11
|
-
class << self
|
12
|
-
attr_accessor :app_name, :stack, :api_gateway_deployment, :gateway_id
|
13
|
-
attr_accessor :app_path, :app_dir, :hidden_dir, :bucket, :stack_opts
|
14
|
-
end
|
15
|
-
|
16
|
-
def init(app_name:, bucket:, **stack_opts)
|
17
|
-
puts 'Initializing stack'
|
18
|
-
@app_name = app_name.camelize
|
19
|
-
@bucket = bucket
|
20
|
-
@app_path = Pathname.getwd
|
21
|
-
@app_dir = app_path.basename.to_s
|
22
|
-
@hidden_dir = '.modulator'
|
23
|
-
@stack_opts = stack_opts
|
24
|
-
@stack = Humidifier::Stack.new(name: @app_name, aws_template_format_version: '2010-09-09')
|
25
|
-
|
26
|
-
# api stage
|
27
|
-
@stack.add_parameter('ApiGatewayStageName', description: 'Gateway deployment stage', type: 'String', default: 'v1')
|
28
|
-
|
29
|
-
add_api_gateway
|
30
|
-
add_api_gateway_deployment
|
31
|
-
add_lambda_iam_role
|
32
|
-
upload_files
|
33
|
-
extend_stack_instance(@stack)
|
34
|
-
@stack
|
35
|
-
end
|
36
|
-
|
37
|
-
def upload_files
|
38
|
-
upload_lambda_handler
|
39
|
-
puts 'Generating layers'
|
40
|
-
app_path.join(hidden_dir).mkpath
|
41
|
-
upload_gems_layer
|
42
|
-
upload_app_layer
|
43
|
-
end
|
44
|
-
|
45
|
-
# helpers
|
46
|
-
def extend_stack_instance(stack)
|
47
|
-
stack.instance_eval do
|
48
|
-
def add_lambda_endpoint(**opts) # gateway:, mod:, wrapper: {}, env: {}, settings: {}
|
49
|
-
# add lambda
|
50
|
-
lambda = AwsStackBuilder.add_lambda(opts)
|
51
|
-
# add api resources
|
52
|
-
AwsStackBuilder.add_api_gateway_resources(gateway: opts[:gateway], lambda: lambda)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
# gateway
|
58
|
-
def add_api_gateway
|
59
|
-
@gateway_id = 'ApiGateway'
|
60
|
-
@stack.add(gateway_id, Humidifier::ApiGateway::RestApi.new(name: app_name, description: app_name + ' API'))
|
61
|
-
end
|
62
|
-
|
63
|
-
# gateway deployment
|
64
|
-
def add_api_gateway_deployment
|
65
|
-
@api_gateway_deployment = Humidifier::ApiGateway::Deployment.new(
|
66
|
-
rest_api_id: Humidifier.ref(gateway_id),
|
67
|
-
stage_name: Humidifier.ref("ApiGatewayStageName")
|
68
|
-
)
|
69
|
-
@stack.add('ApiGatewayDeployment', @api_gateway_deployment)
|
70
|
-
@stack.add_output('ApiGatewayInvokeURL',
|
71
|
-
value: Humidifier.fn.sub("https://${#{gateway_id}}.execute-api.${AWS::Region}.amazonaws.com/${ApiGatewayStageName}"),
|
72
|
-
description: 'API root url',
|
73
|
-
export_name: app_name + 'RootUrl'
|
74
|
-
)
|
75
|
-
@api_gateway_deployment.depends_on = []
|
76
|
-
end
|
77
|
-
|
78
|
-
# lambda function
|
79
|
-
def add_lambda(gateway:, mod:, wrapper: {}, env: {}, settings: {})
|
80
|
-
lambda_config = {}
|
81
|
-
name_parts = mod[:name].split('::')
|
82
|
-
{gateway: gateway, module: mod, wrapper: wrapper}.each do |env_group_prefix, env_group|
|
83
|
-
env_group.each{|env_key, env_value| lambda_config["#{env_group_prefix}_#{env_key}"] = env_value}
|
84
|
-
end
|
85
|
-
|
86
|
-
lambda_function = Humidifier::Lambda::Function.new(
|
87
|
-
description: "Lambda for #{mod[:name]}.#{mod[:method]}",
|
88
|
-
function_name: [app_name.dasherize, name_parts, mod[:method]].flatten.map(&:downcase).join('-'),
|
89
|
-
handler: "#{LAMBDA_HANDLER_FILE_NAME}.AwsLambdaHandler.call",
|
90
|
-
environment: {
|
91
|
-
variables: env
|
92
|
-
.reduce({}){|env_as_string, (k, v)| env_as_string.update(k.to_s => v.to_s)}
|
93
|
-
.merge(lambda_config)
|
94
|
-
.merge('GEM_PATH' => GEM_PATH, 'app_dir' => app_dir)
|
95
|
-
},
|
96
|
-
role: Humidifier.fn.get_att(['LambdaRole', 'Arn']),
|
97
|
-
timeout: settings[:timeout] || stack_opts[:timeout] || 15,
|
98
|
-
memory_size: settings[:memory_size] || stack_opts[:memory_size] || 128,
|
99
|
-
runtime: RUBY_VERSION,
|
100
|
-
code: {
|
101
|
-
s3_bucket: bucket,
|
102
|
-
s3_key: LAMBDA_HANDLER_FILE_NAME + '.rb.zip'
|
103
|
-
},
|
104
|
-
layers: [
|
105
|
-
Humidifier.ref(app_name + 'Layer'),
|
106
|
-
Humidifier.ref(app_name + 'GemsLayer')
|
107
|
-
]
|
108
|
-
)
|
109
|
-
id = ['Lambda', name_parts, mod[:method].capitalize].join
|
110
|
-
@stack.add(id, lambda_function)
|
111
|
-
add_lambda_invoke_permission(id: id, gateway: gateway)
|
112
|
-
id
|
113
|
-
end
|
114
|
-
|
115
|
-
# invoke permission
|
116
|
-
def add_lambda_invoke_permission(id:, gateway:)
|
117
|
-
arn_path_matcher = gateway[:path].split('/').each_with_object([]) do |fragment, matcher|
|
118
|
-
fragment = '*' if fragment.start_with?(':')
|
119
|
-
matcher << fragment
|
120
|
-
end.join('/')
|
121
|
-
@stack.add('LambdaPermission', Humidifier::Lambda::Permission.new(
|
122
|
-
action: "lambda:InvokeFunction",
|
123
|
-
function_name: Humidifier.fn.get_att([id, 'Arn']),
|
124
|
-
principal: "apigateway.amazonaws.com",
|
125
|
-
source_arn: Humidifier.fn.sub("arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${#{gateway_id}}/*/#{gateway[:verb]}/#{arn_path_matcher}")
|
126
|
-
)
|
127
|
-
)
|
128
|
-
end
|
129
|
-
|
130
|
-
# gateway method
|
131
|
-
def add_api_gateway_resources(gateway:, lambda:)
|
132
|
-
|
133
|
-
# example: calculator/algebra/:x/:y/sum -> module name, args, method name
|
134
|
-
path = gateway[:path].split('/')
|
135
|
-
|
136
|
-
# root resource
|
137
|
-
root_resource = path.shift
|
138
|
-
@stack.add(root_resource.camelize, Humidifier::ApiGateway::Resource.new(
|
139
|
-
rest_api_id: Humidifier.ref(AwsStackBuilder.gateway_id),
|
140
|
-
parent_id: Humidifier.fn.get_att(["ApiGateway", "RootResourceId"]),
|
141
|
-
path_part: root_resource
|
142
|
-
)
|
143
|
-
)
|
144
|
-
|
145
|
-
# args and method name are nested resources
|
146
|
-
parent_resource = root_resource.camelize
|
147
|
-
path.each do |fragment|
|
148
|
-
if fragment.start_with?(':')
|
149
|
-
fragment = fragment[1..-1]
|
150
|
-
dynamic_fragment = "{#{fragment}}"
|
151
|
-
end
|
152
|
-
@stack.add(parent_resource + fragment.camelize, Humidifier::ApiGateway::Resource.new(
|
153
|
-
rest_api_id: Humidifier.ref(AwsStackBuilder.gateway_id),
|
154
|
-
parent_id: Humidifier.ref(parent_resource),
|
155
|
-
path_part: dynamic_fragment || fragment
|
156
|
-
)
|
157
|
-
)
|
158
|
-
parent_resource = parent_resource + fragment.camelize
|
159
|
-
end
|
160
|
-
|
161
|
-
# attach lambda to last resource
|
162
|
-
id = 'EndpointFor' + (gateway[:path].gsub(':', '').gsub('/', '_')).camelize
|
163
|
-
@stack.add(id, Humidifier::ApiGateway::Method.new(
|
164
|
-
authorization_type: 'NONE',
|
165
|
-
http_method: gateway[:verb].to_s.upcase,
|
166
|
-
integration: {
|
167
|
-
integration_http_method: 'POST',
|
168
|
-
type: "AWS_PROXY",
|
169
|
-
uri: Humidifier.fn.sub([
|
170
|
-
"arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations",
|
171
|
-
'lambdaArn' => Humidifier.fn.get_att([lambda, 'Arn'])
|
172
|
-
])
|
173
|
-
},
|
174
|
-
rest_api_id: Humidifier.ref(gateway_id),
|
175
|
-
resource_id: Humidifier.ref(parent_resource) # last evaluated resource
|
176
|
-
)
|
177
|
-
)
|
178
|
-
|
179
|
-
# deployment depends on the method
|
180
|
-
@api_gateway_deployment.depends_on << id
|
181
|
-
end
|
182
|
-
|
183
|
-
def add_lambda_iam_role(function_name: nil)
|
184
|
-
@stack.add('LambdaRole', Humidifier::IAM::Role.new(
|
185
|
-
assume_role_policy_document: {
|
186
|
-
'Version' => "2012-10-17",
|
187
|
-
'Statement' => [
|
188
|
-
{
|
189
|
-
'Action' => ["sts:AssumeRole"],
|
190
|
-
'Effect' => "Allow",
|
191
|
-
'Principal' => {
|
192
|
-
'Service' => ["lambda.amazonaws.com"]
|
193
|
-
}
|
194
|
-
}
|
195
|
-
]
|
196
|
-
},
|
197
|
-
policies: [
|
198
|
-
{
|
199
|
-
'policy_document' => {
|
200
|
-
'Version' => "2012-10-17",
|
201
|
-
'Statement' => [
|
202
|
-
{
|
203
|
-
'Action' => [
|
204
|
-
"logs:CreateLogStream",
|
205
|
-
"logs:PutLogEvents",
|
206
|
-
],
|
207
|
-
'Effect' => "Allow",
|
208
|
-
'Resource' => Humidifier.fn.sub("arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*")
|
209
|
-
},
|
210
|
-
{
|
211
|
-
'Action' => [
|
212
|
-
"logs:CreateLogGroup",
|
213
|
-
],
|
214
|
-
'Effect' => "Allow",
|
215
|
-
'Resource' => "*"
|
216
|
-
}
|
217
|
-
]
|
218
|
-
},
|
219
|
-
'policy_name' => "cloud-watch-access"
|
220
|
-
}
|
221
|
-
]
|
222
|
-
)
|
223
|
-
)
|
224
|
-
end
|
225
|
-
end
|