jets 1.7.2 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +1 -1
  4. data/jets.gemspec +2 -0
  5. data/lib/jets.rb +1 -0
  6. data/lib/jets/application.rb +45 -21
  7. data/lib/jets/aws_services.rb +2 -0
  8. data/lib/jets/aws_services/s3_bucket.rb +26 -0
  9. data/lib/jets/booter.rb +82 -2
  10. data/lib/jets/builders/code_builder.rb +20 -7
  11. data/lib/jets/builders/handler_generator.rb +20 -6
  12. data/lib/jets/cfn/builders/base_child_builder.rb +5 -17
  13. data/lib/jets/cfn/builders/parent_builder.rb +1 -1
  14. data/lib/jets/commands/build.rb +6 -0
  15. data/lib/jets/commands/deploy.rb +10 -0
  16. data/lib/jets/commands/templates/skeleton/config/environments/development.rb +4 -1
  17. data/lib/jets/commands/templates/skeleton/config/environments/production.rb +6 -1
  18. data/lib/jets/commands/templates/skeleton/config/environments/test.rb +7 -0
  19. data/lib/jets/controller/base.rb +1 -2
  20. data/lib/jets/controller/rendering/rack_renderer.rb +11 -3
  21. data/lib/jets/core.rb +5 -72
  22. data/lib/jets/internal/app/controllers/jets/mailers_controller.rb +97 -0
  23. data/lib/jets/internal/app/helpers/jets/mailers_helper.rb +9 -0
  24. data/lib/jets/internal/app/shared/functions/jets/s3_bucket_config.rb +43 -0
  25. data/lib/jets/internal/app/views/jets/mailers/email.html.erb +145 -0
  26. data/lib/jets/internal/app/views/jets/mailers/index.html.erb +8 -0
  27. data/lib/jets/internal/app/views/jets/mailers/mailer.html.erb +6 -0
  28. data/lib/jets/job/base.rb +10 -0
  29. data/lib/jets/job/dsl.rb +2 -0
  30. data/lib/jets/job/dsl/s3_event.rb +36 -0
  31. data/lib/jets/job/dsl/sns_event.rb +8 -0
  32. data/lib/jets/job/s3_event_helper.rb +13 -0
  33. data/lib/jets/lambda/dsl.rb +11 -1
  34. data/lib/jets/mailer.rb +51 -0
  35. data/lib/jets/resource/child_stack/app_class.rb +6 -15
  36. data/lib/jets/resource/child_stack/shared.rb +3 -1
  37. data/lib/jets/resource/events/rule.rb +1 -1
  38. data/lib/jets/resource/lambda/event_source_mapping.rb +1 -1
  39. data/lib/jets/resource/permission.rb +1 -1
  40. data/lib/jets/resource/replacer.rb +8 -0
  41. data/lib/jets/resource/s3.rb +3 -17
  42. data/lib/jets/resource/s3/bucket.rb +24 -0
  43. data/lib/jets/resource/sns.rb +1 -0
  44. data/lib/jets/resource/sns/subscription.rb +1 -1
  45. data/lib/jets/resource/sns/topic.rb +1 -1
  46. data/lib/jets/resource/sns/topic_policy.rb +40 -0
  47. data/lib/jets/resource/sqs/queue.rb +1 -1
  48. data/lib/jets/stack.rb +19 -3
  49. data/lib/jets/stack/builder.rb +6 -1
  50. data/lib/jets/stack/depends.rb +36 -0
  51. data/lib/jets/stack/depends/item.rb +9 -0
  52. data/lib/jets/stack/function.rb +19 -10
  53. data/lib/jets/stack/main/dsl.rb +4 -0
  54. data/lib/jets/stack/main/extensions/iam.rb +8 -0
  55. data/lib/jets/stack/main/extensions/lambda.rb +20 -7
  56. data/lib/jets/stack/main/extensions/s3.rb +12 -0
  57. data/lib/jets/stack/main/extensions/sns.rb +4 -0
  58. data/lib/jets/stack/s3_event.rb +87 -0
  59. data/lib/jets/turbine.rb +11 -0
  60. data/lib/jets/version.rb +1 -1
  61. metadata +48 -2
@@ -0,0 +1,8 @@
1
+ <% @previews.each do |preview| %>
2
+ <h3><%= link_to(preview.preview_name.titleize, "/jets/mailers/#{preview.preview_name}") %></h3>
3
+ <ul>
4
+ <% preview.emails.each do |email| %>
5
+ <li><%= link_to(email, "/jets/mailers/#{preview.preview_name}/#{email}") %></li>
6
+ <% end %>
7
+ </ul>
8
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <h3><%= @preview.preview_name.titleize %></h3>
2
+ <ul>
3
+ <% @preview.emails.each do |email| %>
4
+ <li><%= link_to(email, "/jets/mailers/#{@preview.preview_name}/#{email}") %></li>
5
+ <% end %>
6
+ </ul>
data/lib/jets/job/base.rb CHANGED
@@ -6,9 +6,19 @@ require 'json'
6
6
  # Both Jets::Job::Base and Jets::Lambda::Functions have Dsl modules included.
7
7
  # So the Jets::Job::Dsl overrides some of the Jets::Lambda::Functions behavior.
8
8
  class Jets::Job
9
+ autoload :S3EventHelper, "jets/job/s3_event_helper"
10
+
9
11
  class Base < Jets::Lambda::Functions
10
12
  include Dsl
11
13
 
14
+ # non-DSL methods
15
+ include S3EventHelper
16
+
17
+ # Tracks bucket each time an s3_event is declared
18
+ # Map of bucket_name => stack_name (nested part)
19
+ cattr_accessor :s3_events # dont want this to be inheritable intentionally
20
+ self.s3_events = {}
21
+
12
22
  class << self
13
23
  def process(event, context, meth)
14
24
  job = new(event, context, meth)
data/lib/jets/job/dsl.rb CHANGED
@@ -9,12 +9,14 @@
9
9
  module Jets::Job::Dsl
10
10
  extend ActiveSupport::Concern
11
11
  autoload :EventSourceMapping, "jets/job/dsl/event_source_mapping" # base for sqs_event, etc
12
+ autoload :S3Event, "jets/job/dsl/s3_event"
12
13
  autoload :SnsEvent, "jets/job/dsl/sns_event"
13
14
  autoload :SqsEvent, "jets/job/dsl/sqs_event"
14
15
 
15
16
  included do
16
17
  class << self
17
18
  include EventSourceMapping
19
+ include S3Event
18
20
  include SnsEvent
19
21
  include SqsEvent
20
22
 
@@ -0,0 +1,36 @@
1
+ module Jets::Job::Dsl
2
+ module S3Event
3
+ def s3_event(bucket_name, props={})
4
+ stack_name = declare_s3_bucket_resources(bucket_name) # only set up once per bucket
5
+ declare_sns_subscription(topic_arn: "!Ref #{stack_name}SnsTopic") # set up subscription every time
6
+ end
7
+
8
+ # Returns stack_name
9
+ def declare_s3_bucket_resources(bucket_name)
10
+ # If shared s3 bucket resources have already been declared.
11
+ # We will not generate them again. However, we still need to always
12
+ # add the depends_on declaration to ensure that the shared stack parameters
13
+ # are properly passed to the nested child stack.
14
+ stack_name = s3_events[bucket_name] # already registered
15
+ if stack_name
16
+ depends_on stack_name.underscore.to_sym, class_prefix: true # always add this
17
+ return stack_name
18
+ end
19
+
20
+ # Create shared resources - one time
21
+ stack_name = declare_shared_s3_event_resources(bucket_name)
22
+ depends_on stack_name.underscore.to_sym, class_prefix: true # always add this
23
+ self.s3_events[bucket_name] = stack_name # tracks buckets already set up
24
+ end
25
+
26
+ def declare_shared_s3_event_resources(bucket_name)
27
+ s3_stack = Jets::Stack::S3Event.new(bucket_name)
28
+ s3_stack.build_stack
29
+ s3_stack.stack_name
30
+ end
31
+
32
+ def s3_events
33
+ Jets::Job::Base.s3_events
34
+ end
35
+ end
36
+ end
@@ -25,6 +25,14 @@ module Jets::Job::Dsl
25
25
  end
26
26
  end
27
27
 
28
+ def declare_sns_topic_policy(props={})
29
+ props ||= {} # options.delete(:topic_policy_properties) can be nil
30
+ r = Jets::Resource::Sns::TopicPolicy.new(props)
31
+ with_fresh_properties do
32
+ resource(r.definition) # add associated resource immediately
33
+ end
34
+ end
35
+
28
36
  def declare_sns_subscription(props={})
29
37
  r = Jets::Resource::Sns::Subscription.new(props)
30
38
  with_fresh_properties do
@@ -0,0 +1,13 @@
1
+ class Jets::Job
2
+ module S3EventHelper
3
+ def s3_event_message
4
+ message = event["Records"][0]["Sns"]["Message"]
5
+ h = JSON.load(message)
6
+ ActiveSupport::HashWithIndifferentAccess.new(h)
7
+ end
8
+
9
+ def s3_object
10
+ s3_event_message["Records"][0]["s3"]["object"]
11
+ end
12
+ end
13
+ end
@@ -213,12 +213,22 @@ module Jets::Lambda::Dsl
213
213
  @associated_resources = numbered_resources
214
214
  end
215
215
 
216
+ # Examples:
217
+ #
218
+ # depends_on :custom
219
+ # depends_on :custom, :alert
220
+ # depends_on :custom, class_prefix: true
221
+ # depends_on :custom, :alert, class_prefix: true
222
+ #
216
223
  def depends_on(*stacks)
217
224
  if stacks == []
218
225
  @depends_on
219
226
  else
220
227
  @depends_on ||= []
221
- @depends_on += stacks
228
+ options = stacks.last.is_a?(Hash) ? stacks.pop : {}
229
+ stacks.each do |stack|
230
+ @depends_on << Jets::Stack::Depends::Item.new(stack, options)
231
+ end
222
232
  end
223
233
  end
224
234
 
@@ -0,0 +1,51 @@
1
+ module Jets
2
+ # Reference: https://github.com/rails/rails/blob/master/actionmailer/lib/action_mailer/railtie.rb
3
+ class Mailer < ::Jets::Turbine
4
+ config.action_mailer = ActiveSupport::OrderedOptions.new
5
+
6
+ initializer "action_mailer.logger" do
7
+ ActiveSupport.on_load(:action_mailer) { self.logger ||= Jets.logger }
8
+ end
9
+
10
+ initializer "action_mailer.set_configs" do |app|
11
+ options = app.config.action_mailer
12
+ options.default_url_options ||= {}
13
+ options.default_url_options[:protocol] ||= "https"
14
+ options.show_previews = false if options.show_previews.nil?
15
+ options.preview_path ||= "#{Jets.root}/app/previews" if options.show_previews
16
+ options.view_paths ||= "#{Jets.root}/app/views"
17
+
18
+ # TODO: Dont think Jets sets asset_host the same way
19
+ # make sure readers methods get compiled
20
+ # options.asset_host ||= app.config.asset_host
21
+ # options.relative_url_root ||= app.config.relative_url_root
22
+
23
+ ActiveSupport.on_load(:action_mailer) do
24
+ include AbstractController::UrlFor
25
+ # TODO: figure out rest of the helpers
26
+ # extend ::AbstractController::Railties::RoutesHelpers.with(app.routes, false)
27
+ # include app.routes.mounted_helpers
28
+
29
+ register_interceptors(options.delete(:interceptors))
30
+ register_preview_interceptors(options.delete(:preview_interceptors))
31
+ register_observers(options.delete(:observers))
32
+
33
+ options.each { |k, v| send("#{k}=", v) }
34
+ end
35
+ end
36
+
37
+ after_initializer "action_mailer.routes" do |app|
38
+ if app.config.action_mailer.show_previews
39
+ app.routes.draw do
40
+ get "jets/mailers", to: "jets/mailers#index"
41
+ get "jets/mailers/*path", to: "jets/mailers#preview"
42
+ end
43
+
44
+ ActiveSupport.on_load :action_controller do
45
+ internal_views = File.expand_path("internal/app/views", File.dirname(__FILE__))
46
+ ActionController::Base.append_view_path(internal_views)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -29,24 +29,15 @@ module Jets::Resource::ChildStack
29
29
  klass = current_app_class.constantize
30
30
  return unless klass.depends_on
31
31
 
32
- klass.depends_on.map do |shared_stack|
33
- shared_stack.to_s.camelize # logical_id
34
- end
32
+ depends = Jets::Stack::Depends.new(klass.depends_on)
33
+ depends.stack_list
35
34
  end
36
35
 
37
36
  def depends_on_params
38
- params = {}
39
- depends_on.each do |dependency|
40
- dependency_outputs(dependency).each do |output|
41
- dependency_class = dependency.to_s.classify
42
- params[output] = "!GetAtt #{dependency_class}.Outputs.#{output}"
43
- end
44
- end
45
- params
46
- end
47
-
48
- def dependency_outputs(dependency)
49
- dependency.to_s.classify.constantize.output_keys
37
+ klass = current_app_class.constantize
38
+ return unless klass.depends_on
39
+ depends = Jets::Stack::Depends.new(klass.depends_on)
40
+ depends.params
50
41
  end
51
42
 
52
43
  def parameters
@@ -40,10 +40,12 @@ module Jets::Resource::ChildStack
40
40
  end
41
41
 
42
42
  def common_parameters
43
- {
43
+ parameters = {
44
44
  IamRole: "!GetAtt IamRole.Arn",
45
45
  S3Bucket: "!Ref S3Bucket",
46
46
  }
47
+ parameters[:GemLayer] = "!Ref GemLayer" unless Jets.poly_only?
48
+ parameters
47
49
  end
48
50
 
49
51
  # Returns output keys associated with the stack. They are the resource logical ids.
@@ -1,6 +1,6 @@
1
1
  module Jets::Resource::Events
2
2
  class Rule < Jets::Resource::Base
3
- def initialize(props)
3
+ def initialize(props={})
4
4
  @props = props # associated_properties from dsl.rb
5
5
  end
6
6
 
@@ -1,7 +1,7 @@
1
1
  # Note the Lambda function timeout must be less than or equal to the sqs queue default timeout.
2
2
  module Jets::Resource::Lambda
3
3
  class EventSourceMapping < Jets::Resource::Base
4
- def initialize(props)
4
+ def initialize(props={})
5
5
  @props = props # associated_properties from dsl.rb
6
6
  end
7
7
 
@@ -28,7 +28,7 @@ class Jets::Resource
28
28
 
29
29
  def permission_logical_id
30
30
  logical_id = "{namespace}_permission"
31
- md = @associated_resource.logical_id.match(/(\d+)/)
31
+ md = @associated_resource.logical_id.match(/(\d+)$/)
32
32
  counter = md[1] if md
33
33
  [logical_id, counter].compact.join('').underscore
34
34
  end
@@ -59,9 +59,17 @@ class Jets::Resource
59
59
  # "AWS::ApiGateway::Method" => "apigateway.amazonaws.com"
60
60
  def principal_map(type)
61
61
  service = type.split('::')[1].downcase
62
+ service = special_principal_map(service)
62
63
  "#{service}.amazonaws.com"
63
64
  end
64
65
 
66
+ def special_principal_map(service)
67
+ # special map
68
+ # s3_event actually uses sns topic events to trigger a Lambda function
69
+ map = { "s3" => "sns" }
70
+ map[service] || service
71
+ end
72
+
65
73
  # From AWS docs: https://amzn.to/2N0QXQL
66
74
  # source_arn is "not supported by all event sources"
67
75
  #
@@ -1,17 +1,3 @@
1
- class Jets::Resource
2
- class S3 < Jets::Resource::Base
3
- def definition
4
- {
5
- s3_bucket: {
6
- type: "AWS::S3::Bucket"
7
- }
8
- }
9
- end
10
-
11
- def outputs
12
- {
13
- "S3Bucket" => "!Ref S3Bucket",
14
- }
15
- end
16
- end
17
- end
1
+ module Jets::Resource::S3
2
+ autoload :Bucket, 'jets/resource/s3/bucket'
3
+ end
@@ -0,0 +1,24 @@
1
+ module Jets::Resource::S3
2
+ class Bucket < Jets::Resource::Base
3
+ attr_reader :bucket_logical_id
4
+ def initialize(props={})
5
+ @props = props # associated_properties from dsl.rb
6
+ @bucket_logical_id = props.delete(:logical_id) || "{namespace}_s3_bucket"
7
+ end
8
+
9
+ def definition
10
+ {
11
+ bucket_logical_id => {
12
+ type: "AWS::S3::Bucket",
13
+ properties: @props,
14
+ }
15
+ }
16
+ end
17
+
18
+ def outputs
19
+ {
20
+ bucket_logical_id => "!Ref #{bucket_logical_id.to_s.camelize}",
21
+ }
22
+ end
23
+ end
24
+ end
@@ -1,4 +1,5 @@
1
1
  module Jets::Resource::Sns
2
2
  autoload :Topic, 'jets/resource/sns/topic'
3
+ autoload :TopicPolicy, 'jets/resource/sns/topic_policy'
3
4
  autoload :Subscription, 'jets/resource/sns/subscription'
4
5
  end
@@ -1,7 +1,7 @@
1
1
  # CloudFormation SNS Subscription docs: https://amzn.to/2SJtN3C
2
2
  module Jets::Resource::Sns
3
3
  class Subscription < Jets::Resource::Base
4
- def initialize(props)
4
+ def initialize(props={})
5
5
  @props = props # associated_properties from dsl.rb
6
6
  end
7
7
 
@@ -1,7 +1,7 @@
1
1
  # CloudFormation SNS Topic docs: https://amzn.to/2MYbUZc
2
2
  module Jets::Resource::Sns
3
3
  class Topic < Jets::Resource::Base
4
- def initialize(props)
4
+ def initialize(props={})
5
5
  @props = props # associated_properties from dsl.rb
6
6
  end
7
7
 
@@ -0,0 +1,40 @@
1
+ # CloudFormation SNS TopicPolicy docs: https://amzn.to/2SBMq9v
2
+ module Jets::Resource::Sns
3
+ class TopicPolicy < Jets::Resource::Base
4
+ def initialize(props={})
5
+ @props = props # associated_properties from dsl.rb
6
+ end
7
+
8
+ def definition
9
+ {
10
+ policy_logical_id => {
11
+ type: "AWS::SNS::TopicPolicy",
12
+ properties: merged_properties,
13
+ }
14
+ }
15
+ end
16
+
17
+ # Do not name this method properties, that is a computed method of `Jets::Resource::Base`
18
+ def merged_properties
19
+ {
20
+ policy_document: {
21
+ version: "2012-10-17",
22
+ statement: {
23
+ effect: "Allow",
24
+ principal: { service: "s3.amazonaws.com"},
25
+ action: "sns:Publish",
26
+ resource: "*", # TODO: figure out good syntax to limit easily
27
+ # Condition:
28
+ # ArnLike:
29
+ # aws:SourceArn: arn:aws:s3:::aa-test-95872017
30
+ }
31
+ },
32
+ topics: ["!Ref {namespace}SnsTopic"],
33
+ }.deep_merge(@props)
34
+ end
35
+
36
+ def policy_logical_id
37
+ "{namespace}_sns_topic_policy"
38
+ end
39
+ end
40
+ end
@@ -1,7 +1,7 @@
1
1
  # CloudFormation SQS Queue docs: https://amzn.to/2MVWk0j
2
2
  module Jets::Resource::Sqs
3
3
  class Queue < Jets::Resource::Base
4
- def initialize(props)
4
+ def initialize(props={})
5
5
  @props = props # associated_properties from dsl.rb
6
6
  end
7
7
 
data/lib/jets/stack.rb CHANGED
@@ -1,12 +1,14 @@
1
1
  module Jets
2
2
  class Stack
3
+ autoload :Builder, 'jets/stack/builder'
3
4
  autoload :Definition, 'jets/stack/definition' # Registration and definitions
5
+ autoload :Depends, 'jets/stack/depends'
6
+ autoload :Function, 'jets/stack/function'
4
7
  autoload :Main, 'jets/stack/main'
5
- autoload :Parameter, 'jets/stack/parameter'
6
8
  autoload :Output, 'jets/stack/output'
9
+ autoload :Parameter, 'jets/stack/parameter'
7
10
  autoload :Resource, 'jets/stack/resource'
8
- autoload :Builder, 'jets/stack/builder'
9
- autoload :Function, 'jets/stack/function'
11
+ autoload :S3Event, 'jets/stack/s3_event'
10
12
 
11
13
  include Main::Dsl
12
14
  include Parameter::Dsl
@@ -26,6 +28,20 @@ module Jets
26
28
  self.subclasses << base if base.name
27
29
  end
28
30
 
31
+ # klass = Jets::Stack.new_class("Bucket3")
32
+ def new_class(class_name, &block)
33
+ # https://stackoverflow.com/questions/4113479/dynamic-class-definition-with-a-class-name
34
+ # Defining the constant this way gets around: SyntaxError: dynamic constant assignment error
35
+ klass = Class.new(Jets::Stack) # First klass is an anonymous class. IE: class.name is nil
36
+ klass = Object.const_set(class_name, klass) # now klass is a named class
37
+ Jets::Stack.subclasses << klass # mimic inherited hook because
38
+
39
+ # Must run class_eval after adding to subclasses in order for the resource declarations in the
40
+ # so that the resources get registered to the right subclass.
41
+ klass.class_eval(&block)
42
+ klass # return klass
43
+ end
44
+
29
45
  # Build it to figure out if we need to build the stack for the SharedBuilder
30
46
  def build?
31
47
  empty = template == {"Parameters"=>{"IamRole"=>{"Type"=>"String"}, "S3Bucket"=>{"Type"=>"String"}}}