modulator 0.1.1
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 +11 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +87 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/gateway/gateway.rb +123 -0
- data/lib/gateway/routes/console.rb +89 -0
- data/lib/lambda/aws_lambda_handler.rb +140 -0
- data/lib/lambda/aws_stack_builder.rb +223 -0
- data/lib/lambda/aws_stack_uploader.rb +147 -0
- data/lib/modulator.rb +114 -0
- data/lib/utils.rb +101 -0
- data/lib/version.rb +3 -0
- data/modulator.gemspec +42 -0
- metadata +203 -0
@@ -0,0 +1,223 @@
|
|
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, :app_path, :app_dir, :bucket, :stack_opts
|
13
|
+
end
|
14
|
+
|
15
|
+
def init(app_name:, bucket:, **stack_opts)
|
16
|
+
puts 'Initializing stack'
|
17
|
+
@app_name = app_name.camelize
|
18
|
+
@bucket = bucket
|
19
|
+
@app_path = Pathname.getwd
|
20
|
+
@app_dir = app_path.basename.to_s
|
21
|
+
@stack_opts = stack_opts
|
22
|
+
@stack = Humidifier::Stack.new(name: @app_name, aws_template_format_version: '2010-09-09')
|
23
|
+
|
24
|
+
# api stage
|
25
|
+
@stack.add_parameter('ApiGatewayStageName', description: 'Gateway deployment stage', type: 'String', default: 'v1')
|
26
|
+
|
27
|
+
add_api_gateway
|
28
|
+
add_api_gateway_deployment
|
29
|
+
add_lambda_iam_role
|
30
|
+
upload_files
|
31
|
+
extend_stack_instance(@stack)
|
32
|
+
@stack
|
33
|
+
end
|
34
|
+
|
35
|
+
def upload_files
|
36
|
+
upload_lambda_handler
|
37
|
+
puts 'Generating layers'
|
38
|
+
app_path.join('.modulator').mkpath
|
39
|
+
upload_gems_layer
|
40
|
+
upload_app_layer
|
41
|
+
end
|
42
|
+
|
43
|
+
# helpers
|
44
|
+
def extend_stack_instance(stack)
|
45
|
+
stack.instance_eval do
|
46
|
+
def add_lambda_endpoint(**opts) # gateway:, mod:, wrapper: {}, env: {}, settings: {}
|
47
|
+
# add lambda
|
48
|
+
lambda = AwsStackBuilder.add_lambda(opts)
|
49
|
+
# add api resources
|
50
|
+
AwsStackBuilder.add_api_gateway_resources(gateway: opts[:gateway], lambda: lambda)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# gateway
|
56
|
+
def add_api_gateway
|
57
|
+
@gateway_id = 'ApiGateway'
|
58
|
+
@stack.add(gateway_id, Humidifier::ApiGateway::RestApi.new(name: app_name, description: app_name + ' API'))
|
59
|
+
end
|
60
|
+
|
61
|
+
# gateway deployment
|
62
|
+
def add_api_gateway_deployment
|
63
|
+
@api_gateway_deployment = Humidifier::ApiGateway::Deployment.new(
|
64
|
+
rest_api_id: Humidifier.ref(gateway_id),
|
65
|
+
stage_name: Humidifier.ref("ApiGatewayStageName")
|
66
|
+
)
|
67
|
+
@stack.add('ApiGatewayDeployment', @api_gateway_deployment)
|
68
|
+
@stack.add_output('ApiGatewayInvokeURL',
|
69
|
+
value: Humidifier.fn.sub("https://${#{gateway_id}}.execute-api.${AWS::Region}.amazonaws.com/${ApiGatewayStageName}"),
|
70
|
+
description: 'API root url',
|
71
|
+
export_name: app_name + 'RootUrl'
|
72
|
+
)
|
73
|
+
@api_gateway_deployment.depends_on = []
|
74
|
+
end
|
75
|
+
|
76
|
+
# lambda function
|
77
|
+
def add_lambda(gateway:, mod:, wrapper: {}, env: {}, settings: {})
|
78
|
+
lambda_config = {}
|
79
|
+
name_parts = mod[:name].split('::')
|
80
|
+
{gateway: gateway, module: mod, wrapper: wrapper}.each do |env_group_prefix, env_group|
|
81
|
+
env_group.each{|env_key, env_value| lambda_config["#{env_group_prefix}_#{env_key}"] = env_value}
|
82
|
+
end
|
83
|
+
|
84
|
+
lambda_function = Humidifier::Lambda::Function.new(
|
85
|
+
description: "Lambda for #{mod[:name]}.#{mod[:method]}",
|
86
|
+
function_name: [app_name.dasherize, name_parts, mod[:method]].flatten.map(&:downcase).join('-'),
|
87
|
+
handler: "#{LAMBDA_HANDLER_FILE_NAME}.AwsLambdaHandler.call",
|
88
|
+
environment: {
|
89
|
+
variables: env
|
90
|
+
.reduce({}){|env_as_string, (k, v)| env_as_string.update(k.to_s => v.to_s)}
|
91
|
+
.merge(lambda_config)
|
92
|
+
.merge('GEM_PATH' => GEM_PATH, 'app_dir' => app_dir)
|
93
|
+
},
|
94
|
+
role: Humidifier.fn.get_att(['LambdaRole', 'Arn']),
|
95
|
+
timeout: settings[:timeout] || stack_opts[:timeout] || 15,
|
96
|
+
memory_size: settings[:memory_size] || stack_opts[:memory_size] || 128,
|
97
|
+
runtime: RUBY_VERSION,
|
98
|
+
code: {
|
99
|
+
s3_bucket: bucket,
|
100
|
+
s3_key: LAMBDA_HANDLER_FILE_NAME + '.rb.zip'
|
101
|
+
},
|
102
|
+
layers: [
|
103
|
+
Humidifier.ref(app_name + 'Layer'),
|
104
|
+
Humidifier.ref(app_name + 'GemsLayer')
|
105
|
+
]
|
106
|
+
)
|
107
|
+
id = ['Lambda', name_parts, mod[:method].capitalize].join
|
108
|
+
@stack.add(id, lambda_function)
|
109
|
+
add_lambda_invoke_permission(id: id, gateway: gateway)
|
110
|
+
id
|
111
|
+
end
|
112
|
+
|
113
|
+
# invoke permission
|
114
|
+
def add_lambda_invoke_permission(id:, gateway:)
|
115
|
+
arn_path_matcher = gateway[:path].split('/').each_with_object([]) do |fragment, matcher|
|
116
|
+
fragment = '*' if fragment.start_with?(':')
|
117
|
+
matcher << fragment
|
118
|
+
end.join('/')
|
119
|
+
@stack.add('LambdaPermission', Humidifier::Lambda::Permission.new(
|
120
|
+
action: "lambda:InvokeFunction",
|
121
|
+
function_name: Humidifier.fn.get_att([id, 'Arn']),
|
122
|
+
principal: "apigateway.amazonaws.com",
|
123
|
+
source_arn: Humidifier.fn.sub("arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${#{gateway_id}}/*/#{gateway[:verb]}/#{arn_path_matcher}")
|
124
|
+
)
|
125
|
+
)
|
126
|
+
end
|
127
|
+
|
128
|
+
# gateway method
|
129
|
+
def add_api_gateway_resources(gateway:, lambda:)
|
130
|
+
|
131
|
+
# example: calculator/algebra/:x/:y/sum -> module name, args, method name
|
132
|
+
path = gateway[:path].split('/')
|
133
|
+
|
134
|
+
# root resource
|
135
|
+
root_resource = path.shift
|
136
|
+
@stack.add(root_resource.camelize, Humidifier::ApiGateway::Resource.new(
|
137
|
+
rest_api_id: Humidifier.ref(AwsStackBuilder.gateway_id),
|
138
|
+
parent_id: Humidifier.fn.get_att(["ApiGateway", "RootResourceId"]),
|
139
|
+
path_part: root_resource
|
140
|
+
)
|
141
|
+
)
|
142
|
+
|
143
|
+
# args and method name are nested resources
|
144
|
+
parent_resource = root_resource.camelize
|
145
|
+
path.each do |fragment|
|
146
|
+
if fragment.start_with?(':')
|
147
|
+
fragment = fragment[1..-1]
|
148
|
+
dynamic_fragment = "{#{fragment}}"
|
149
|
+
end
|
150
|
+
@stack.add(parent_resource + fragment.camelize, Humidifier::ApiGateway::Resource.new(
|
151
|
+
rest_api_id: Humidifier.ref(AwsStackBuilder.gateway_id),
|
152
|
+
parent_id: Humidifier.ref(parent_resource),
|
153
|
+
path_part: dynamic_fragment || fragment
|
154
|
+
)
|
155
|
+
)
|
156
|
+
parent_resource = parent_resource + fragment.camelize
|
157
|
+
end
|
158
|
+
|
159
|
+
# attach lambda to last resource
|
160
|
+
id = 'EndpointFor' + (gateway[:path].gsub(':', '').gsub('/', '_')).camelize
|
161
|
+
@stack.add(id, Humidifier::ApiGateway::Method.new(
|
162
|
+
authorization_type: 'NONE',
|
163
|
+
http_method: gateway[:verb].to_s.upcase,
|
164
|
+
integration: {
|
165
|
+
integration_http_method: 'POST',
|
166
|
+
type: "AWS_PROXY",
|
167
|
+
uri: Humidifier.fn.sub([
|
168
|
+
"arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations",
|
169
|
+
'lambdaArn' => Humidifier.fn.get_att([lambda, 'Arn'])
|
170
|
+
])
|
171
|
+
},
|
172
|
+
rest_api_id: Humidifier.ref(gateway_id),
|
173
|
+
resource_id: Humidifier.ref(parent_resource) # last evaluated resource
|
174
|
+
)
|
175
|
+
)
|
176
|
+
|
177
|
+
# deployment depends on the method
|
178
|
+
@api_gateway_deployment.depends_on << id
|
179
|
+
end
|
180
|
+
|
181
|
+
def add_lambda_iam_role(function_name: nil)
|
182
|
+
@stack.add('LambdaRole', Humidifier::IAM::Role.new(
|
183
|
+
assume_role_policy_document: {
|
184
|
+
'Version' => "2012-10-17",
|
185
|
+
'Statement' => [
|
186
|
+
{
|
187
|
+
'Action' => ["sts:AssumeRole"],
|
188
|
+
'Effect' => "Allow",
|
189
|
+
'Principal' => {
|
190
|
+
'Service' => ["lambda.amazonaws.com"]
|
191
|
+
}
|
192
|
+
}
|
193
|
+
]
|
194
|
+
},
|
195
|
+
policies: [
|
196
|
+
{
|
197
|
+
'policy_document' => {
|
198
|
+
'Version' => "2012-10-17",
|
199
|
+
'Statement' => [
|
200
|
+
{
|
201
|
+
'Action' => [
|
202
|
+
"logs:CreateLogStream",
|
203
|
+
"logs:PutLogEvents",
|
204
|
+
],
|
205
|
+
'Effect' => "Allow",
|
206
|
+
'Resource' => Humidifier.fn.sub("arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*")
|
207
|
+
},
|
208
|
+
{
|
209
|
+
'Action' => [
|
210
|
+
"logs:CreateLogGroup",
|
211
|
+
],
|
212
|
+
'Effect' => "Allow",
|
213
|
+
'Resource' => "*"
|
214
|
+
}
|
215
|
+
]
|
216
|
+
},
|
217
|
+
'policy_name' => "cloud-watch-access"
|
218
|
+
}
|
219
|
+
]
|
220
|
+
)
|
221
|
+
)
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'aws-sdk-s3'
|
2
|
+
require 'digest'
|
3
|
+
require 'bundler'
|
4
|
+
|
5
|
+
module AwsStackBuilder
|
6
|
+
module_function
|
7
|
+
|
8
|
+
S3Client = Aws::S3::Client.new
|
9
|
+
|
10
|
+
def upload_lambda_handler
|
11
|
+
bucket_name = Humidifier.ref("S3Bucket").reference
|
12
|
+
lambda_handler_key = LAMBDA_HANDLER_FILE_NAME + '.rb.zip'
|
13
|
+
source = <<~SOURCE
|
14
|
+
# see handler AwsApiGatewayEventHandler.call(event: event, context: context) in required file
|
15
|
+
require 'lambda/aws_lambda_handler'
|
16
|
+
Dir.chdir('/opt/ruby/lib/' + ENV['app_dir'])
|
17
|
+
SOURCE
|
18
|
+
|
19
|
+
existing_handler = S3Client.get_object(
|
20
|
+
bucket: bucket,
|
21
|
+
key: lambda_handler_key
|
22
|
+
) rescue false # not found
|
23
|
+
|
24
|
+
existing_source = Zip::InputStream.open(existing_handler.body) do |zip_file|
|
25
|
+
zip_file.get_next_entry
|
26
|
+
zip_file.read
|
27
|
+
end if existing_handler
|
28
|
+
|
29
|
+
if existing_source != source
|
30
|
+
puts '- uploading generic lambda handler'
|
31
|
+
source_zip_file = Zip::OutputStream.write_buffer do |zip|
|
32
|
+
zip.put_next_entry LAMBDA_HANDLER_FILE_NAME + '.rb'
|
33
|
+
zip.print source
|
34
|
+
end
|
35
|
+
S3Client.put_object(
|
36
|
+
bucket: bucket,
|
37
|
+
key: lambda_handler_key,
|
38
|
+
body: source_zip_file.tap(&:rewind).read
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def upload_gems_layer
|
44
|
+
if !app_path.join('Gemfile').exist?
|
45
|
+
puts '- no Gemfile detected'
|
46
|
+
return
|
47
|
+
end
|
48
|
+
|
49
|
+
# calculate Gemfile checksum
|
50
|
+
checksum_path = app_path.join('.modulator/gemfile_checksum')
|
51
|
+
old_checksum = (checksum_path.read rescue nil)
|
52
|
+
new_checksum = Digest::MD5.hexdigest(File.read(app_path.join('Gemfile.lock')))
|
53
|
+
|
54
|
+
zip_file_name = app_dir + '_gems.zip'
|
55
|
+
gems_path = app_path.join('.modulator/gems')
|
56
|
+
gems_zip_path = gems_path.parent.join(zip_file_name)
|
57
|
+
|
58
|
+
if old_checksum != new_checksum
|
59
|
+
puts '- uploading gems layer'
|
60
|
+
checksum_path.write(new_checksum)
|
61
|
+
|
62
|
+
# bundle gems
|
63
|
+
Bundler.with_clean_env do
|
64
|
+
Dir.chdir(app_path) do
|
65
|
+
`bundle install --path=./.modulator/gems --clean`
|
66
|
+
end
|
67
|
+
end
|
68
|
+
ZipFileGenerator.new(gems_path, gems_zip_path).write
|
69
|
+
|
70
|
+
# upload zipped file
|
71
|
+
gem_layer = S3Client.put_object(
|
72
|
+
bucket: bucket,
|
73
|
+
key: zip_file_name,
|
74
|
+
body: gems_zip_path.read
|
75
|
+
)
|
76
|
+
# delete zipped file
|
77
|
+
FileUtils.remove_dir(gems_path)
|
78
|
+
gems_zip_path.delete
|
79
|
+
else
|
80
|
+
puts '- using existing gems layer'
|
81
|
+
gem_layer = S3Client.get_object(bucket: bucket, key: zip_file_name)
|
82
|
+
end
|
83
|
+
|
84
|
+
add_layer(
|
85
|
+
name: app_name + 'Gems',
|
86
|
+
description: "App gems",
|
87
|
+
s3_key: zip_file_name,
|
88
|
+
s3_object_version: gem_layer.version_id
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
def upload_app_layer
|
93
|
+
wd = Pathname.getwd
|
94
|
+
zip_file_name = app_dir + '.zip'
|
95
|
+
app_zip_path = wd.join(zip_file_name)
|
96
|
+
|
97
|
+
# calculate checksum for app folder
|
98
|
+
checksum_path = app_path.join('.modulator/app_checksum')
|
99
|
+
old_checksum = (checksum_path.read rescue nil)
|
100
|
+
new_checksum = checksum(app_path)
|
101
|
+
|
102
|
+
if old_checksum != new_checksum
|
103
|
+
puts '- uploading app layer'
|
104
|
+
checksum_path.write(new_checksum)
|
105
|
+
ZipFileGenerator.new(app_path, app_zip_path).write
|
106
|
+
# upload zipped file
|
107
|
+
app_layer = S3Client.put_object(
|
108
|
+
bucket: bucket,
|
109
|
+
key: zip_file_name,
|
110
|
+
body: app_zip_path.read
|
111
|
+
)
|
112
|
+
# delete zipped file
|
113
|
+
app_zip_path.delete
|
114
|
+
else
|
115
|
+
puts '- using existing app layer'
|
116
|
+
app_layer = S3Client.get_object(bucket: bucket, key: zip_file_name)
|
117
|
+
end
|
118
|
+
|
119
|
+
add_layer(
|
120
|
+
name: app_name,
|
121
|
+
description: "App source. MD5: #{new_checksum}",
|
122
|
+
s3_key: zip_file_name,
|
123
|
+
s3_object_version: app_layer.version_id
|
124
|
+
)
|
125
|
+
end
|
126
|
+
|
127
|
+
# add layer
|
128
|
+
def add_layer(name:, description:, s3_key:, s3_object_version:)
|
129
|
+
stack.add(name + 'Layer', Humidifier::Lambda::LayerVersion.new(
|
130
|
+
compatible_runtimes: [RUBY_VERSION],
|
131
|
+
layer_name: name,
|
132
|
+
description: description,
|
133
|
+
content: {
|
134
|
+
s3_bucket: bucket,
|
135
|
+
s3_key: s3_key,
|
136
|
+
s3_object_version: s3_object_version
|
137
|
+
}
|
138
|
+
)
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
def checksum(dir)
|
143
|
+
files = Dir["#{dir}/**/*"].reject{|f| File.directory?(f)}
|
144
|
+
content = files.map{|f| File.read(f)}.join
|
145
|
+
Digest::MD5.hexdigest(content)
|
146
|
+
end
|
147
|
+
end
|
data/lib/modulator.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require 'lambda/aws_lambda_handler'
|
4
|
+
require 'lambda/aws_stack_builder'
|
5
|
+
require 'utils'
|
6
|
+
|
7
|
+
module Modulator
|
8
|
+
module_function
|
9
|
+
LAMBDAS = {}
|
10
|
+
|
11
|
+
def add_lambda(lambda_def, **opts) # opts are for overrides
|
12
|
+
if lambda_def.is_a?(Hash)
|
13
|
+
add_lambda_from_hash(lambda_def)
|
14
|
+
else
|
15
|
+
add_lambda_from_module(lambda_def, **opts)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_lambda_from_hash(hash)
|
20
|
+
LAMBDAS[hash[:name]] = {
|
21
|
+
name: hash[:name],
|
22
|
+
gateway: hash[:gateway],
|
23
|
+
module: hash[:module],
|
24
|
+
wrapper: hash[:wrapper] || {},
|
25
|
+
env: hash[:env] || {},
|
26
|
+
settings: hash[:settings] || {}
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_lambda_from_module(mod, **opts)
|
31
|
+
mod.singleton_methods.sort.each do |module_method|
|
32
|
+
module_name = mod.to_s
|
33
|
+
module_names = module_name.split('::').map(&:downcase)
|
34
|
+
verb = 'GET'
|
35
|
+
path_fragments = module_names.dup
|
36
|
+
|
37
|
+
# process parameters
|
38
|
+
# method(a, b, c = 1, *args, d:, e: 2, **opts)
|
39
|
+
# [[:req, :a], [:req, :b], [:opt, :c], [:rest, :args], [:keyreq, :d], [:key, :e], [:keyrest, :opts]]
|
40
|
+
mod.method(module_method).parameters.each do |param|
|
41
|
+
param_type = param[0]
|
42
|
+
param_name = param[1]
|
43
|
+
|
44
|
+
# collect required params
|
45
|
+
path_fragments << ":#{param_name}" if param_type == :req
|
46
|
+
|
47
|
+
# post if we have optional key param, ie. pet: {}
|
48
|
+
verb = 'POST' if param_type == :key
|
49
|
+
end
|
50
|
+
|
51
|
+
# delete is special case based on method name
|
52
|
+
verb = 'DELETE' if %w[destroy delete remove implode].include? module_method.to_s
|
53
|
+
|
54
|
+
# finalize path
|
55
|
+
path_fragments << module_method
|
56
|
+
path = path_fragments.join('/')
|
57
|
+
|
58
|
+
add_lambda_from_hash(
|
59
|
+
{
|
60
|
+
name: "#{module_names.join('-')}-#{module_method}",
|
61
|
+
gateway: {
|
62
|
+
verb: opts.dig(module_method, :gateway, :verb) || verb,
|
63
|
+
path: opts.dig(module_method, :gateway, :path) || path
|
64
|
+
},
|
65
|
+
module: {
|
66
|
+
name: module_name,
|
67
|
+
method: module_method.to_s,
|
68
|
+
path: module_names.join('/') # file name
|
69
|
+
},
|
70
|
+
wrapper: opts.dig(module_method, :wrapper) || opts[:wrapper],
|
71
|
+
env: opts.dig(module_method, :env),
|
72
|
+
settings: opts.dig(module_method, :settings)
|
73
|
+
}
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def set_env(lambda_def)
|
79
|
+
# remove wrapper if already set
|
80
|
+
%w(name method path).each{|name| ENV.delete('wrapper_' + name)}
|
81
|
+
# set env values
|
82
|
+
lambda_def[:module].each{|name, value| ENV['module_' + name.to_s] = value.to_s}
|
83
|
+
lambda_def[:gateway].each{|name, value| ENV['gateway_' + name.to_s] = value.to_s}
|
84
|
+
lambda_def[:wrapper]&.each{|name, value| ENV['wrapper_' + name.to_s] = value.to_s}
|
85
|
+
lambda_def[:env]&.each{|name, value| ENV[name.to_s] = value.to_s} # custom values
|
86
|
+
end
|
87
|
+
|
88
|
+
def init_stack(app_name:, bucket:, **stack_opts)
|
89
|
+
stack = AwsStackBuilder.init({
|
90
|
+
app_name: app_name.camelize,
|
91
|
+
bucket: bucket,
|
92
|
+
}.merge(stack_opts))
|
93
|
+
|
94
|
+
# add lambdas to stack
|
95
|
+
puts 'Generating endpoints'
|
96
|
+
LAMBDAS.each do |name, config|
|
97
|
+
puts "- adding #{config.dig(:module, :name)}.#{config.dig(:module, :method)} to #{config.dig(:gateway, :path)}"
|
98
|
+
stack.add_lambda_endpoint(
|
99
|
+
gateway: config[:gateway],
|
100
|
+
mod: config[:module],
|
101
|
+
wrapper: config[:wrapper] || {},
|
102
|
+
env: config[:env] || {},
|
103
|
+
settings: config[:settings] || {}
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
# validate stack
|
108
|
+
# puts 'Validating stack'
|
109
|
+
# puts '- it is valid' if stack.valid?
|
110
|
+
|
111
|
+
# return humidifier instance
|
112
|
+
stack
|
113
|
+
end
|
114
|
+
end
|
data/lib/utils.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'zip'
|
2
|
+
|
3
|
+
# NOTE: this file is not required while running your code
|
4
|
+
# the patched classes are used only in tests and tools
|
5
|
+
class String
|
6
|
+
def camelize
|
7
|
+
split('_').collect do |word|
|
8
|
+
word[0] = word[0].upcase
|
9
|
+
word
|
10
|
+
end.join
|
11
|
+
end
|
12
|
+
|
13
|
+
def underscore
|
14
|
+
gsub(/([A-Z]+)([0-9]|[A-Z]|\z)/){"#{$1.capitalize}#{$2}"}
|
15
|
+
.gsub(/(.)([A-Z])/, '\1_\2')
|
16
|
+
.downcase
|
17
|
+
end
|
18
|
+
|
19
|
+
def dasherize
|
20
|
+
underscore.gsub('_', '-')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Object
|
25
|
+
def symbolize_keys
|
26
|
+
case self
|
27
|
+
when Hash
|
28
|
+
hash = {}
|
29
|
+
each {|k, v| hash[k.to_sym] = v.symbolize_keys}
|
30
|
+
hash
|
31
|
+
when Array
|
32
|
+
map {|x| x.symbolize_keys}
|
33
|
+
else
|
34
|
+
self
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def stringify_keys
|
40
|
+
case self
|
41
|
+
when Hash
|
42
|
+
hash = {}
|
43
|
+
each {|k, v| hash[k.to_s] = v.stringify_keys}
|
44
|
+
hash
|
45
|
+
when Array
|
46
|
+
map {|x| x.stringify_keys}
|
47
|
+
else
|
48
|
+
self
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module Utils
|
54
|
+
module_function
|
55
|
+
|
56
|
+
def load_json(path)
|
57
|
+
JSON.parse(File.read(path))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# NOTE: this code is taken from https://github.com/rubyzip/rubyzip examples
|
62
|
+
# Usage:
|
63
|
+
# directoryToZip = "/tmp/input"
|
64
|
+
# outputFile = "/tmp/out.zip"
|
65
|
+
# zf = ZipFileGenerator.new(directoryToZip, outputFile)
|
66
|
+
# zf.write()
|
67
|
+
class ZipFileGenerator
|
68
|
+
|
69
|
+
# Initialize with the directory to zip and the location of the output archive.
|
70
|
+
def initialize(inputDir, outputFile)
|
71
|
+
@inputDir = inputDir
|
72
|
+
@outputFile = outputFile
|
73
|
+
end
|
74
|
+
|
75
|
+
# Zip the input directory.
|
76
|
+
def write()
|
77
|
+
entries = Dir.entries(@inputDir); entries.delete("."); entries.delete("..")
|
78
|
+
io = Zip::File.open(@outputFile, Zip::File::CREATE);
|
79
|
+
|
80
|
+
writeEntries(entries, "", io)
|
81
|
+
io.close();
|
82
|
+
end
|
83
|
+
|
84
|
+
# A helper method to make the recursion work.
|
85
|
+
private
|
86
|
+
def writeEntries(entries, path, io)
|
87
|
+
|
88
|
+
entries.each { |e|
|
89
|
+
zipFilePath = path == "" ? e : File.join(path, e)
|
90
|
+
diskFilePath = File.join(@inputDir, zipFilePath)
|
91
|
+
# puts "Deflating " + diskFilePath
|
92
|
+
if File.directory?(diskFilePath)
|
93
|
+
io.mkdir(zipFilePath)
|
94
|
+
subdir =Dir.entries(diskFilePath); subdir.delete("."); subdir.delete("..")
|
95
|
+
writeEntries(subdir, zipFilePath, io)
|
96
|
+
else
|
97
|
+
io.get_output_stream(zipFilePath) { |f| f.puts(File.open(diskFilePath, "rb").read())}
|
98
|
+
end
|
99
|
+
}
|
100
|
+
end
|
101
|
+
end
|
data/lib/version.rb
ADDED
data/modulator.gemspec
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "modulator"
|
8
|
+
spec.version = Modulator::VERSION
|
9
|
+
spec.authors = ["Damir Roso"]
|
10
|
+
spec.email = ["damir.roso@webteh.us"]
|
11
|
+
|
12
|
+
spec.summary = %q{Publish ruby methods as aws lambdas}
|
13
|
+
spec.description = %q{Publish ruby methods as aws lambdas}
|
14
|
+
spec.homepage = "https://github.com/damir/modulator"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Specify which files should be added to the gem when it is released.
|
18
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
19
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
20
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
|
+
end
|
22
|
+
spec.bindir = "exe"
|
23
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
|
+
spec.require_paths = ["lib"]
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
29
|
+
spec.add_development_dependency "rack-test", "~> 1.1"
|
30
|
+
|
31
|
+
# stack builder
|
32
|
+
# custom source is not supported by bundler, it is added to gemfile
|
33
|
+
# spec.add_dependency "humidifier", github: 'damir/humidifier'
|
34
|
+
spec.add_dependency "aws-sdk-s3"
|
35
|
+
spec.add_dependency "aws-sdk-cloudformation"
|
36
|
+
spec.add_dependency "rubyzip"
|
37
|
+
|
38
|
+
# local gateway
|
39
|
+
spec.add_dependency "puma"
|
40
|
+
spec.add_dependency "roda"
|
41
|
+
spec.add_dependency "rerun"
|
42
|
+
end
|