jets 1.7.2 → 1.8.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.
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"}}}