jets 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/Gemfile.lock +1 -1
- data/lib/jets.rb +2 -0
- data/lib/jets/cfn/ship.rb +2 -0
- data/lib/jets/cfn/template_builders/base_child_builder.rb +14 -0
- data/lib/jets/cfn/template_builders/controller_builder.rb +4 -74
- data/lib/jets/cfn/template_builders/interface.rb +5 -0
- data/lib/jets/cfn/template_builders/job_builder.rb +1 -55
- data/lib/jets/cfn/template_builders/rule_builder.rb +7 -58
- data/lib/jets/cfn/template_mappers.rb +1 -4
- data/lib/jets/cfn/template_mappers/child_mapper.rb +1 -1
- data/lib/jets/cfn/template_mappers/gateway_resource_mapper.rb +5 -4
- data/lib/jets/commands/build.rb +2 -2
- data/lib/jets/commands/templates/skeleton/config/application.rb.tt +4 -1
- data/lib/jets/internal/app/jobs/jets/preheat_job.rb +2 -2
- data/lib/jets/job.rb +0 -1
- data/lib/jets/job/dsl.rb +52 -34
- data/lib/jets/lambda/dsl.rb +43 -1
- data/lib/jets/lambda/task.rb +2 -1
- data/lib/jets/pascalize.rb +26 -8
- data/lib/jets/resource.rb +7 -0
- data/lib/jets/resource/attributes.rb +46 -0
- data/lib/jets/resource/creator.rb +17 -0
- data/lib/jets/resource/permission.rb +43 -0
- data/lib/jets/resource/replacer.rb +40 -0
- data/lib/jets/resource/replacer/base.rb +98 -0
- data/lib/jets/resource/replacer/config_rule.rb +18 -0
- data/lib/jets/resource/route.rb +67 -0
- data/lib/jets/resource/route/attributes.rb +8 -0
- data/lib/jets/resource/route/cors.rb +60 -0
- data/lib/jets/rule.rb +0 -2
- data/lib/jets/rule/dsl.rb +71 -31
- data/lib/jets/version.rb +1 -1
- metadata +12 -8
- data/lib/jets/cfn/template_mappers/config_rule_mapper.rb +0 -34
- data/lib/jets/cfn/template_mappers/events_rule_mapper.rb +0 -40
- data/lib/jets/cfn/template_mappers/gateway_method_mapper.rb +0 -56
- data/lib/jets/job/task.rb +0 -17
- data/lib/jets/rule/aws_managed_rule.rb +0 -12
- data/lib/jets/rule/task.rb +0 -44
data/lib/jets/job.rb
CHANGED
data/lib/jets/job/dsl.rb
CHANGED
@@ -1,57 +1,75 @@
|
|
1
1
|
# Jets::Job::Base < Jets::Lambda::Functions
|
2
2
|
# Both Jets::Job::Base and Jets::Lambda::Functions have Dsl modules included.
|
3
3
|
# So the Jets::Job::Dsl overrides some of the Jets::Lambda::Functions behavior.
|
4
|
+
#
|
5
|
+
# Implements:
|
6
|
+
# default_associated_resource: must return @resources
|
4
7
|
module Jets::Job::Dsl
|
5
8
|
extend ActiveSupport::Concern
|
6
9
|
|
7
10
|
included do
|
8
11
|
class << self
|
9
12
|
def rate(expression)
|
10
|
-
|
13
|
+
update_properties(schedule_expression: "rate(#{expression})")
|
11
14
|
end
|
12
15
|
|
13
16
|
def cron(expression)
|
14
|
-
|
17
|
+
update_properties(schedule_expression: "cron(#{expression}")
|
15
18
|
end
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
def event_pattern(details={})
|
21
|
+
event_rule(event_pattern: details)
|
22
|
+
add_descriptions # useful: generic description in the Event Rule console
|
20
23
|
end
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
+
def add_descriptions
|
26
|
+
numbered_resources = []
|
27
|
+
n = 1
|
28
|
+
@resources.map do |definition|
|
29
|
+
logical_id = definition.keys.first
|
30
|
+
attributes = definition.values.first
|
31
|
+
attributes[:properties][:description] = "#{self.name} Event Rule #{n}"
|
32
|
+
numbered_resources << { "#{logical_id}" => attributes }
|
33
|
+
n += 1
|
34
|
+
end
|
35
|
+
@resources = numbered_resources
|
25
36
|
end
|
26
37
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
def register_task(meth, lang=:ruby)
|
31
|
-
if @rate || @cron || @disable
|
32
|
-
# Job lambda function.
|
33
|
-
all_tasks[meth] = Jets::Job::Task.new(self.name, meth,
|
34
|
-
rate: @rate,
|
35
|
-
cron: @cron,
|
36
|
-
state: @state,
|
37
|
-
properties: @properties,
|
38
|
-
lang: lang)
|
39
|
-
true
|
40
|
-
else
|
41
|
-
task_name = "#{name}##{meth}" # IE: HardJob#dig
|
42
|
-
puts "[WARNING] #{task_name} created without a rate or cron expression. " \
|
43
|
-
"Add a rate or cron expression above the method definition if you want this method to be scheduled. " \
|
44
|
-
"If #{task_name} is not meant to be a scheduled lambda function, you can put the method under after a private keyword to get rid of this warning. " \
|
45
|
-
"#{task_name} defined at #{caller[1].inspect}."
|
46
|
-
false
|
47
|
-
end
|
48
|
-
# Done storing options, clear out for the next added method.
|
49
|
-
clear_properties
|
38
|
+
def default_associated_resource
|
39
|
+
event_rule
|
40
|
+
@resources # must return @resoures for update_properties
|
50
41
|
end
|
51
42
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
43
|
+
def event_rule(props={})
|
44
|
+
default_props = {
|
45
|
+
state: "ENABLED",
|
46
|
+
targets: [{
|
47
|
+
arn: "!GetAtt {namespace}LambdaFunction.Arn",
|
48
|
+
id: "{namespace}RuleTarget"
|
49
|
+
}]
|
50
|
+
}
|
51
|
+
properties = default_props.deep_merge(props)
|
52
|
+
|
53
|
+
resource("{namespace}EventsRule" => {
|
54
|
+
type: "AWS::Events::Rule",
|
55
|
+
properties: properties
|
56
|
+
})
|
57
|
+
|
58
|
+
add_logical_id_counter if @resources.size > 1
|
59
|
+
end
|
60
|
+
|
61
|
+
# Loop back through the resources and add a counter to the end of the id
|
62
|
+
# to handle multiple events.
|
63
|
+
# Then replace @resources entirely
|
64
|
+
def add_logical_id_counter
|
65
|
+
numbered_resources = []
|
66
|
+
n = 1
|
67
|
+
@resources.map do |definition|
|
68
|
+
logical_id = definition.keys.first
|
69
|
+
numbered_resources << { "#{logical_id}#{n}" => definition.values.first }
|
70
|
+
n += 1
|
71
|
+
end
|
72
|
+
@resources = numbered_resources
|
55
73
|
end
|
56
74
|
end
|
57
75
|
end
|
data/lib/jets/lambda/dsl.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# Other dsl that rely on this must implement
|
2
|
+
# default_associated_resource: must return @resources
|
1
3
|
module Jets::Lambda::Dsl
|
2
4
|
extend ActiveSupport::Concern
|
3
5
|
|
@@ -120,6 +122,44 @@ module Jets::Lambda::Dsl
|
|
120
122
|
!!(class_iam_policy || class_managed_iam_policy)
|
121
123
|
end
|
122
124
|
|
125
|
+
#############################
|
126
|
+
# Generic method that registers a resource to be associated with the Lambda function.
|
127
|
+
# In the future all DSL methods can lead here.
|
128
|
+
def resources(*definitions)
|
129
|
+
if definitions == [nil] # when resources called with no arguments
|
130
|
+
@resources || []
|
131
|
+
else
|
132
|
+
@resources ||= []
|
133
|
+
@resources += definitions
|
134
|
+
@resources.flatten!
|
135
|
+
end
|
136
|
+
end
|
137
|
+
alias_method :resource, :resources
|
138
|
+
|
139
|
+
# Main method that the convenience methods call for to create resources associated
|
140
|
+
# with the Lambda function. References the first resource and updates it inplace.
|
141
|
+
# Useful for associated resources that are meant to be declare and associated
|
142
|
+
# with only one Lambda function. Example:
|
143
|
+
#
|
144
|
+
# Config Rule <=> Lambda function is 1-to-1
|
145
|
+
#
|
146
|
+
# Note: This methods calls default_associated_resource. The inheriting DSL class
|
147
|
+
# must implement default_associated_resource. The default_associated_resource should
|
148
|
+
# wrap another method that is nicely name so that the nicely name method is
|
149
|
+
# available in the DSL. Example:
|
150
|
+
#
|
151
|
+
# def default_associated_resource
|
152
|
+
# config_rule
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
def update_properties(values={})
|
156
|
+
@resources ||= default_associated_resource
|
157
|
+
definition = @resources.first # singleton
|
158
|
+
attributes = definition.values.first
|
159
|
+
attributes[:properties].merge!(values)
|
160
|
+
@resources
|
161
|
+
end
|
162
|
+
|
123
163
|
# meth is a Symbol
|
124
164
|
def method_added(meth)
|
125
165
|
return if %w[initialize method_missing].include?(meth.to_s)
|
@@ -133,7 +173,8 @@ module Jets::Lambda::Dsl
|
|
133
173
|
# We adjust the class name when we build the functions later in
|
134
174
|
# FunctionContstructor#adjust_tasks.
|
135
175
|
all_tasks[meth] = Jets::Lambda::Task.new(self.name, meth,
|
136
|
-
|
176
|
+
resources: @resources, # associated resources
|
177
|
+
properties: @properties, # lambda function properties
|
137
178
|
iam_policy: @iam_policy,
|
138
179
|
managed_iam_policy: @managed_iam_policy,
|
139
180
|
lang: lang)
|
@@ -152,6 +193,7 @@ module Jets::Lambda::Dsl
|
|
152
193
|
end
|
153
194
|
|
154
195
|
def clear_properties
|
196
|
+
@resources = nil
|
155
197
|
@properties = nil
|
156
198
|
@iam_policy = nil
|
157
199
|
@managed_iam_policy = nil
|
data/lib/jets/lambda/task.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
class Jets::Lambda::Task
|
2
2
|
attr_accessor :class_name, :type
|
3
|
-
attr_reader :meth, :properties, :iam_policy, :managed_iam_policy, :lang
|
3
|
+
attr_reader :meth, :resources, :properties, :iam_policy, :managed_iam_policy, :lang
|
4
4
|
def initialize(class_name, meth, options={})
|
5
5
|
@class_name = class_name.to_s # use at EventsRuleMapper#full_task_name
|
6
6
|
@meth = meth
|
7
7
|
@options = options
|
8
8
|
@type = options[:type] || get_type # controller, job, or function
|
9
|
+
@resources = options[:resources] || {}
|
9
10
|
@properties = options[:properties] || {}
|
10
11
|
@iam_policy = options[:iam_policy]
|
11
12
|
@managed_iam_policy = options[:managed_iam_policy]
|
data/lib/jets/pascalize.rb
CHANGED
@@ -4,14 +4,14 @@ module Jets
|
|
4
4
|
# Specialized pascalize that will not pascalize keys under the
|
5
5
|
# Variables part of the hash structure.
|
6
6
|
# Based on: https://stackoverflow.com/questions/8706930/converting-nested-hash-keys-from-camelcase-to-snake-case-in-ruby
|
7
|
-
def pascalize(value,
|
7
|
+
def pascalize(value, parent_keys=[])
|
8
8
|
case value
|
9
9
|
when Array
|
10
10
|
value.map { |v| pascalize(v) }
|
11
11
|
when Hash
|
12
12
|
initializer = value.map do |k, v|
|
13
|
-
new_key = pascal_key(k,
|
14
|
-
[new_key, pascalize(v, new_key)]
|
13
|
+
new_key = pascal_key(k, parent_keys)
|
14
|
+
[new_key, pascalize(v, parent_keys+[new_key])]
|
15
15
|
end
|
16
16
|
Hash[initializer]
|
17
17
|
else
|
@@ -19,14 +19,32 @@ module Jets
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
def pascal_key(k,
|
23
|
-
|
24
|
-
|
22
|
+
def pascal_key(k, parent_keys=[])
|
23
|
+
k = k.to_s
|
24
|
+
if parent_keys.include?("Variables") # do not pascalize keys anything under Variables
|
25
|
+
k # pass through untouch
|
26
|
+
elsif parent_keys.include?("ResponseParameters")
|
27
|
+
k # pass through untouch
|
28
|
+
elsif k.include?('-') || k.include?('/')
|
29
|
+
k # pass through untouch
|
30
|
+
elsif parent_keys.last == "EventPattern" # top-level
|
31
|
+
k.dasherize
|
32
|
+
elsif parent_keys.include?("EventPattern")
|
33
|
+
# any keys at 2nd level under EventPattern will be camelize
|
34
|
+
new_k = k.camelize # an earlier pascalize has made the first char upcase
|
35
|
+
# so we need to downcase it again
|
36
|
+
first_char = new_k[0..0].downcase
|
37
|
+
new_k[0] = first_char
|
38
|
+
new_k
|
25
39
|
else
|
26
|
-
k
|
27
|
-
k.slice(0,1).capitalize + k.slice(1..-1) # capitalize first letter only
|
40
|
+
pascalize_string(k)
|
28
41
|
end
|
29
42
|
end
|
43
|
+
|
44
|
+
def pascalize_string(s)
|
45
|
+
s = s.to_s.camelize
|
46
|
+
s.slice(0,1).capitalize + s.slice(1..-1) # capitalize first letter only
|
47
|
+
end
|
30
48
|
end
|
31
49
|
end
|
32
50
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Jets::Resource
|
2
|
+
class Attributes
|
3
|
+
extend Memoist
|
4
|
+
|
5
|
+
def initialize(data, task, replacements={})
|
6
|
+
@data = data
|
7
|
+
@task = task
|
8
|
+
@replacements = replacements
|
9
|
+
end
|
10
|
+
|
11
|
+
def logical_id
|
12
|
+
id = @data.keys.first
|
13
|
+
# replace possible {namespace} in the logical id
|
14
|
+
id = replacer.replace_value(id)
|
15
|
+
Jets::Pascalize.pascalize_string(id)
|
16
|
+
end
|
17
|
+
|
18
|
+
def type
|
19
|
+
attributes['Type']
|
20
|
+
end
|
21
|
+
|
22
|
+
def properties
|
23
|
+
attributes['Properties']
|
24
|
+
end
|
25
|
+
|
26
|
+
def attributes
|
27
|
+
attributes = @data.values.first
|
28
|
+
attributes = replacer.replace_placeholders(attributes, @replacements)
|
29
|
+
Jets::Pascalize.pascalize(attributes)
|
30
|
+
end
|
31
|
+
|
32
|
+
def replacer
|
33
|
+
# Use raw @data to avoid infinite loop from using attributes
|
34
|
+
attributes = Jets::Pascalize.pascalize(@data.values.first)
|
35
|
+
type = attributes['Type']
|
36
|
+
replacer_class = Replacer.lookup(type)
|
37
|
+
replacer_class.new(@task)
|
38
|
+
end
|
39
|
+
memoize :replacer
|
40
|
+
|
41
|
+
def permission
|
42
|
+
Permission.new(@task, self)
|
43
|
+
end
|
44
|
+
memoize :permission
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Jets::Resource
|
2
|
+
class Creator
|
3
|
+
extend Memoist
|
4
|
+
|
5
|
+
def initialize(definition, task)
|
6
|
+
@definition = definition
|
7
|
+
@task = task # task that the definition belongs to
|
8
|
+
end
|
9
|
+
|
10
|
+
# Template snippet that gets injected into the CloudFormation template.
|
11
|
+
def attributes
|
12
|
+
Attributes.new(@definition, @task)
|
13
|
+
end
|
14
|
+
alias_method :resource, :attributes
|
15
|
+
memoize :attributes
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Jets::Resource
|
2
|
+
class Permission
|
3
|
+
extend Memoist
|
4
|
+
|
5
|
+
def initialize(task, resource)
|
6
|
+
@task = task
|
7
|
+
@resource_attributes = resource
|
8
|
+
end
|
9
|
+
|
10
|
+
def attributes
|
11
|
+
logical_id = "{namespace}Permission"
|
12
|
+
md = @resource_attributes.logical_id.match(/(\d+)/)
|
13
|
+
if md
|
14
|
+
counter = md[1]
|
15
|
+
end
|
16
|
+
logical_id = [logical_id, counter].compact.join('')
|
17
|
+
|
18
|
+
attributes = {
|
19
|
+
logical_id => {
|
20
|
+
type: "AWS::Lambda::Permission",
|
21
|
+
properties: {
|
22
|
+
function_name: "!GetAtt {namespace}LambdaFunction.Arn",
|
23
|
+
action: "lambda:InvokeFunction",
|
24
|
+
principal: principal,
|
25
|
+
source_arn: source_arn,
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
Attributes.new(attributes, @task)
|
30
|
+
end
|
31
|
+
memoize :attributes
|
32
|
+
|
33
|
+
# Auto-detect principal from the associated resources.
|
34
|
+
def principal
|
35
|
+
Replacer.principal_map(@resource_attributes.type)
|
36
|
+
end
|
37
|
+
|
38
|
+
def source_arn
|
39
|
+
default_arn = "!GetAtt #{@resource_attributes.logical_id}.Arn"
|
40
|
+
Replacer.source_arn_map(@resource_attributes.type) || default_arn
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Jets::Resource
|
2
|
+
module Replacer
|
3
|
+
autoload :Base, 'jets/resource/replacer/base'
|
4
|
+
autoload :ConfigRule, 'jets/resource/replacer/config_rule'
|
5
|
+
# TODO: handle autoloading for plugins
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def lookup(type)
|
9
|
+
klass = replacer_map[type] || "Jets::Resource::Replacer::Base"
|
10
|
+
klass.constantize
|
11
|
+
end
|
12
|
+
|
13
|
+
# Maps
|
14
|
+
# TODO: get rid of this map, and use a convention
|
15
|
+
# * connect a plugin to figure out interface.
|
16
|
+
# * add ability to explicitly override principal and source_arn.
|
17
|
+
def replacer_map
|
18
|
+
{
|
19
|
+
"AWS::Config::ConfigRule" => "Jets::Resource::Replacer::ConfigRule"
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
# Examples:
|
24
|
+
# "AWS::Events::Rule" => "events.amazonaws.com",
|
25
|
+
# "AWS::Config::ConfigRule" => "config.amazonaws.com",
|
26
|
+
# "AWS::ApiGateway::Method" => "apigateway.amazonaws.com"
|
27
|
+
def principal_map(type)
|
28
|
+
service = type.split('::')[1].downcase
|
29
|
+
"#{service}.amazonaws.com"
|
30
|
+
end
|
31
|
+
|
32
|
+
def source_arn_map(type)
|
33
|
+
map = {
|
34
|
+
"AWS::ApiGateway::Method" => "!Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*",
|
35
|
+
}
|
36
|
+
map[type]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Jets::Resource::Replacer
|
2
|
+
class Base
|
3
|
+
extend Memoist
|
4
|
+
|
5
|
+
def initialize(task)
|
6
|
+
@task = task
|
7
|
+
@app_class = task.class_name.to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
# Replace placeholder hash values with replacements. This does a deep replacement
|
11
|
+
# to the hash values. The replacement "key" is the string value within the value.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# attributes = {whatever: "foo REPLACE_KEY bar" }
|
16
|
+
# replace_placeholders(attributes, REPLACE_KEY: "blah:arn")
|
17
|
+
# => {whatever: "foo blah:arn bar" }
|
18
|
+
#
|
19
|
+
# Also, we always replace the special {namespace} value in the hash values. Example:
|
20
|
+
#
|
21
|
+
# attributes = {whatever: "{namespace}LambdaFunction" }
|
22
|
+
# replace_placeholders(attributes, {})
|
23
|
+
# => {whatever: "foo PostsControllerIndexLambdaFunction bar" }
|
24
|
+
#
|
25
|
+
def replace_placeholders(attributes, replacements={})
|
26
|
+
update_values(attributes, replacements)
|
27
|
+
end
|
28
|
+
|
29
|
+
def update_values(original, replacements={})
|
30
|
+
case original
|
31
|
+
when Array
|
32
|
+
original.map { |v| update_values(v, replacements) }
|
33
|
+
when Hash
|
34
|
+
initializer = original.map do |k, v|
|
35
|
+
[k, update_values(v, replacements)]
|
36
|
+
end
|
37
|
+
Hash[initializer]
|
38
|
+
else
|
39
|
+
replace_value(original, replacements)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def replace_value(text, replacements={})
|
44
|
+
text = text.to_s # normalize to String
|
45
|
+
# custom replacements
|
46
|
+
replacements.each do |k,v|
|
47
|
+
text = text.gsub(k.to_s, v)
|
48
|
+
end
|
49
|
+
# Values to always replace
|
50
|
+
text = replace_core_values(text)
|
51
|
+
text
|
52
|
+
end
|
53
|
+
|
54
|
+
# Values to always replace.
|
55
|
+
def replace_core_values(text)
|
56
|
+
text = text.gsub('{namespace}', namespace) # always replace namespace
|
57
|
+
core_replacements.each do |k,v|
|
58
|
+
text = text.gsub("{#{k}}", v)
|
59
|
+
end
|
60
|
+
text
|
61
|
+
end
|
62
|
+
|
63
|
+
# Meant to beoverriden by different resource types in the child class.
|
64
|
+
# These values replace the variables in the resource template.
|
65
|
+
#
|
66
|
+
# Example:
|
67
|
+
#
|
68
|
+
# In child class:
|
69
|
+
#
|
70
|
+
# def core_replacements
|
71
|
+
# { config_rule_name: "my-config-rule" }
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# And we declare these properties in the resource:
|
75
|
+
#
|
76
|
+
# properties: {
|
77
|
+
# config_rule_name: "{config_rule_name}",
|
78
|
+
# ...
|
79
|
+
#
|
80
|
+
# The replacements result in:
|
81
|
+
#
|
82
|
+
# properties: {
|
83
|
+
# config_rule_name: "my-config-rule",
|
84
|
+
# ...
|
85
|
+
#
|
86
|
+
def core_replacements
|
87
|
+
{}
|
88
|
+
end
|
89
|
+
|
90
|
+
# Full camelized namespace
|
91
|
+
# Example: HardJobDig, PostsControllerIndex, SleepJobPerform
|
92
|
+
def namespace
|
93
|
+
class_name = @task.class_name.gsub('::','')
|
94
|
+
function_name = @task.meth.to_s.camelize
|
95
|
+
"#{class_name}#{function_name}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|