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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d4b8b6547644c895f4b9f86ea9c58d1732f89bd68cc3f71a73cf4d2a6733eb0
4
- data.tar.gz: e6447ef5b8099c9c405be40315cf9aa2dc711958158a79a86f578c1f0258117d
3
+ metadata.gz: 22ccf0e1b4f1fe6841677f5e80a47d28420176db2b89d0e66852d71841feac32
4
+ data.tar.gz: 0dff68b46ad8f254a1fc8ffe71d95d52b58cc21b1ccd4cf78ec0e62c212d4ed6
5
5
  SHA512:
6
- metadata.gz: 5a90c4010311c435bc32714738dd83ece60822a8c34cb9d848c198bd0d8d9518129a764c5edf3ea873747f3ff38d3630ec991a907f5afd9acf6befabada4fd58
7
- data.tar.gz: b2013f481f5be249ee7798bb746306d25e52ff9f1515020b28c3e7e7583511ea3796666734a202c3514235d0d6816ce28e677ef397238c88531a44116f0cbbb7
6
+ metadata.gz: 3facb23a28890cea9a9627808198e7499975ad1874ffb4371fa14a757ff45b3f74e1daa853d51ca966c170450ee7e2b0d6e48a3dea621aad62936cf8f1b98312
7
+ data.tar.gz: 46369c376727ebc86de017043ff079abbd077f2a1c090193ad0cebd728528e3e2794df003b1d9c8667cd333dec8880673cb92e60d8eeee929790f88c4f3978c5
data/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  This project *loosely tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
5
5
 
6
+ ## [1.8.0]
7
+ - #191 Email Support via ActionMailer
8
+ - #192 S3 Event Support
9
+ - Turbine after_initializer
10
+ - Improve Jets.boot ordering
11
+
6
12
  ## [1.7.2]
7
13
  - #189: spec_helpers: `get` request now converts dangling params to query params. `query` keyword can be used to do the same thing explicitly.
8
14
  - #190 SNS Event Lambda Trigger Support
data/README.md CHANGED
@@ -31,7 +31,7 @@ Jets supports writing AWS Lambda functions with Ruby. You define them in the `ap
31
31
  app/functions/simple.rb:
32
32
 
33
33
  ```ruby
34
- def handler_function(event:, context:)
34
+ def lambda_handler(event:, context:)
35
35
  puts "hello world"
36
36
  {hello: "world"}
37
37
  end
data/jets.gemspec CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
29
29
 
30
30
  # spec.add_dependency "actionpack", "~> 5.2.1" # using vendor/rails version
31
31
  # spec.add_dependency "actionview", "~> 5.2.1" # using vendor/rails version
32
+ spec.add_dependency "actionmailer", "~> 5.2.1"
32
33
  spec.add_dependency "activerecord", "~> 5.2.1"
33
34
  spec.add_dependency "activesupport", "~> 5.2.1"
34
35
  spec.add_dependency "aws-sdk-apigateway"
@@ -39,6 +40,7 @@ Gem::Specification.new do |spec|
39
40
  spec.add_dependency "aws-sdk-s3"
40
41
  spec.add_dependency "aws-sdk-sns"
41
42
  spec.add_dependency "aws-sdk-sqs"
43
+ spec.add_dependency "cfnresponse"
42
44
  spec.add_dependency "dotenv"
43
45
  spec.add_dependency "gems" # jets-gems dependency
44
46
  spec.add_dependency "hashie"
data/lib/jets.rb CHANGED
@@ -36,6 +36,7 @@ module Jets
36
36
  autoload :Klass, 'jets/klass'
37
37
  autoload :Lambda, 'jets/lambda'
38
38
  autoload :Logger, "jets/logger"
39
+ autoload :Mailer, "jets/mailer"
39
40
  autoload :Mega, "jets/mega"
40
41
  autoload :Middleware, "jets/middleware"
41
42
  autoload :Naming, 'jets/naming'
@@ -12,13 +12,33 @@ class Jets::Application
12
12
  end
13
13
 
14
14
  def setup!
15
- load_configs
15
+ load_default_config
16
+ setup_auto_load_paths
17
+ end
18
+
19
+ def configs!
20
+ load_environments_config
21
+ load_db_config
22
+ set_iam_policy # relies on dependent values, must be called afterwards
23
+ normalize_env_vars!
24
+ end
25
+
26
+ # After the mimimal template gets build, we need to reload it for the full stack
27
+ # creation. This allows us to reference IAM policies configs that depend on the
28
+ # creation of the s3 bucket.
29
+ def reload_configs!
30
+ # Tricky: reset only the things that depends on the minimal stack
31
+ @config.iam_policy = nil
32
+ configs!
16
33
  end
17
34
 
18
35
  def finish!
36
+ deprecated_configs_message
19
37
  load_inflections
20
- setup_auto_load_paths
21
38
  load_routes
39
+ # Load libraries at the end to trigger onload so we can defined options in any order.
40
+ # Only action_mailer library have been used properly this way so far.
41
+ require 'action_mailer'
22
42
  end
23
43
 
24
44
  def load_inflections
@@ -98,6 +118,26 @@ class Jets::Application
98
118
  config.encoding = ActiveSupport::OrderedOptions.new
99
119
  config.encoding.default = "utf-8"
100
120
 
121
+ config.s3_event = ActiveSupport::OrderedOptions.new
122
+ # These notification_configuration properties correspond to the ruby aws-sdk
123
+ # s3.put_bucket_notification_configuration
124
+ # in jets/s3_bucket_config.rb, not the CloudFormation Bucket properties. The CloudFormation
125
+ # bucket properties have a similiar structure but is slightly different so it can be confusing.
126
+ #
127
+ # Ruby aws-sdk S3 Docs: https://amzn.to/2N7m5Lr
128
+ config.s3_event.configure_bucket = true
129
+ config.s3_event.notification_configuration = {
130
+ topic_configurations: [
131
+ {
132
+ events: ["s3:ObjectCreated:*"],
133
+ topic_arn: "!Ref SnsTopic", # must use this logical id
134
+ },
135
+ ],
136
+ }
137
+
138
+ # So tried to defined this in the jets/mailer.rb Turbine only but jets new requires it
139
+ # config.action_mailer = ActiveSupport::OrderedOptions.new
140
+
101
141
  config
102
142
  end
103
143
 
@@ -116,14 +156,11 @@ class Jets::Application
116
156
  project_name_line.gsub(/.*=/,'').strip.gsub(/["']/,'') # project_name
117
157
  end
118
158
 
119
- def load_app_config
159
+ def load_default_config
120
160
  @config = default_config
121
161
  set_dependent_configs! # things like project_namespace that need project_name
122
162
  eval_app_config # this overwrites Jets.config.project_name
123
163
  Jets.config.project_name = parse_project_name # Must set again because JETS_PROJECT_NAME is possible
124
-
125
- set_iam_policy # relies on dependent values, must be called afterwards
126
- normalize_env_vars!
127
164
  end
128
165
 
129
166
  def eval_app_config
@@ -131,13 +168,6 @@ class Jets::Application
131
168
  load app_config # use load instead of require so reload_configs! works
132
169
  end
133
170
 
134
- # After the mimimal template gets build, we need to reload it for the full stack
135
- # creation. This allows us to reference IAM policies configs that depend on the
136
- # creation of the s3 bucket.
137
- def reload_configs!
138
- load_configs
139
- end
140
-
141
171
  def load_environments_config
142
172
  env_file = "#{Jets.root}/config/environments/#{Jets.env}.rb"
143
173
  if File.exist?(env_file)
@@ -146,13 +176,6 @@ class Jets::Application
146
176
  end
147
177
  end
148
178
 
149
- def load_configs
150
- load_app_config
151
- load_db_config
152
- load_environments_config
153
- deprecated_configs_message
154
- end
155
-
156
179
  def deprecated_configs_message
157
180
  unless config.ruby.lazy_load.nil?
158
181
  puts "Detected config.ruby.lazy_load = #{config.ruby.lazy_load.inspect}".color(:yellow)
@@ -195,8 +218,9 @@ class Jets::Application
195
218
  internal = File.expand_path("../internal", __FILE__)
196
219
  paths = %w[
197
220
  app/controllers
198
- app/models
221
+ app/helpers
199
222
  app/jobs
223
+ app/models
200
224
  ]
201
225
  paths.map { |path| "#{internal}/#{path}" }
202
226
  end
@@ -10,6 +10,8 @@ require "aws-sdk-sqs"
10
10
 
11
11
  module Jets::AwsServices
12
12
  autoload :StackStatus, 'jets/aws_services/stack_status'
13
+ autoload :S3Bucket, 'jets/aws_services/s3_bucket'
14
+
13
15
  include StackStatus
14
16
  extend Memoist
15
17
 
@@ -0,0 +1,26 @@
1
+ module Jets::AwsServices
2
+ class S3Bucket
3
+ include Jets::AwsServices
4
+
5
+ def self.ensure_exists(bucket_name)
6
+ new(bucket_name).ensure_exists
7
+ end
8
+
9
+ def initialize(name)
10
+ @name = name
11
+ end
12
+
13
+ def ensure_exists
14
+ s3.create_bucket(bucket: @name) unless exists?
15
+ end
16
+
17
+ def exists?
18
+ begin
19
+ s3.head_bucket(bucket: @name)
20
+ true
21
+ rescue
22
+ false
23
+ end
24
+ end
25
+ end
26
+ end
data/lib/jets/booter.rb CHANGED
@@ -10,11 +10,19 @@ class Jets::Booter
10
10
  Jets::Dotenv.load!
11
11
 
12
12
  Jets.application.setup!
13
+ # eager_load_jets is called to ensure that internal Turbines get loaded after auto_load paths configured in setup!
14
+ eager_load_jets
15
+ run_turbines(:initializers)
16
+ # Load configs after Turbine initializers so Turbines can defined some config options
17
+ # and they are available in user's project environment configs.
18
+ Jets.application.configs!
13
19
  app_initializers
14
- turbine_initializers
20
+ run_turbines(:after_initializers)
15
21
  Jets.application.finish!
16
22
 
17
- Jets.eager_load!
23
+ # Eager load project code. Rather have user find out early than late.
24
+ eager_load_app
25
+
18
26
  setup_db
19
27
  # build_middleware_stack # TODO: figure out how to build middleware during Jets.boot without breaking jets new and webpacker:install
20
28
 
@@ -51,6 +59,17 @@ class Jets::Booter
51
59
  end
52
60
  end
53
61
 
62
+ # run_turbines(:initializers)
63
+ # run_turbines(:after_initializers)
64
+ def run_turbines(name)
65
+ Jets::Turbine.subclasses.each do |subclass|
66
+ hooks = subclass.send(name) || []
67
+ hooks.each do |label, block|
68
+ block.call(Jets.application)
69
+ end
70
+ end
71
+ end
72
+
54
73
  # require_bundle_gems called when environment boots up via Jets.boot. It
55
74
  # also useful for when to loading Rake tasks in
56
75
  # Jets::Commands::RakeTasks.load!
@@ -127,5 +146,66 @@ class Jets::Booter
127
146
  exit 1
128
147
  end
129
148
  end
149
+
150
+ # Eager load jet's lib and classes
151
+ def eager_load_jets
152
+ lib_jets = File.expand_path(".", File.dirname(__FILE__))
153
+ Dir.glob("#{lib_jets}/**/*.rb").select do |path|
154
+ next if !File.file?(path)
155
+ next if skip_eager_load_paths?(path)
156
+
157
+ path = path.sub("#{lib_jets}/","jets/")
158
+ class_name = path
159
+ .sub(/\.rb$/,'') # remove .rb
160
+ .sub(/^\.\//,'') # remove ./
161
+ .sub(/app\/\w+\//,'') # remove app/controllers or app/jobs etc
162
+ .camelize
163
+ # special class mappings
164
+ class_name = class_mappings(class_name)
165
+ class_name.constantize # use constantize instead of require so dont have to worry about order.
166
+ end
167
+ end
168
+
169
+ # Skip these paths because eager loading doesnt work for them.
170
+ # Scope to /jets as much as possible in case it collides with a user path
171
+ def skip_eager_load_paths?(path)
172
+ path =~ %r{/jets/builders/rackup_wrappers} ||
173
+ path =~ %r{/jets/builders/reconfigure_rails} ||
174
+ path =~ %r{/jets/cli} ||
175
+ path =~ %r{/jets/commands/templates/webpacker} ||
176
+ path =~ %r{/jets/controller/middleware/webpacker_setup} ||
177
+ path =~ %r{/jets/core_ext} ||
178
+ path =~ %r{/jets/internal/app} ||
179
+ path =~ %r{/jets/overrides} ||
180
+ path =~ %r{/jets/spec} ||
181
+ path =~ %r{/jets/stack} ||
182
+ path =~ %r{/jets/turbo/project/} ||
183
+ path =~ %r{/jets/version} ||
184
+ path =~ %r{/templates/}
185
+ end
186
+
187
+ def class_mappings(class_name)
188
+ map = {
189
+ "Jets::Io" => "Jets::IO",
190
+ }
191
+ map[class_name] || class_name
192
+ end
193
+
194
+ # Eager load user's application
195
+ def eager_load_app
196
+ Dir.glob("#{Jets.root}/app/**/*.rb").select do |path|
197
+ next if !File.file?(path) or path =~ %r{/javascript/} or path =~ %r{/views/}
198
+ next if path.include?('app/functions') || path.include?('app/shared/functions') || path.include?('app/internal/functions')
199
+
200
+ class_name = path
201
+ .sub(/\.rb$/,'') # remove .rb
202
+ .sub(%{^\./},'') # remove ./
203
+ .sub("#{Jets.root}/",'')
204
+ .sub(%r{app/shared/\w+/},'') # remove shared/resources or shared/extensions
205
+ .sub(%r{app/\w+/},'') # remove app/controllers or app/jobs etc
206
+ class_name = class_name.classify
207
+ class_name.constantize # use constantize instead of require so dont have to worry about order.
208
+ end
209
+ end
130
210
  end
131
211
  end
@@ -53,7 +53,7 @@ class Jets::Builders
53
53
  Md5.compute! # populates Md5.checksums hash
54
54
  end
55
55
 
56
- def generate_node_shims
56
+ def generate_shims
57
57
  headline "Generating shims in the handlers folder."
58
58
  # Crucial that the Dir.pwd is in the tmp_code because for
59
59
  # Jets::Builders::app_files because Jets.boot set ups
@@ -97,8 +97,8 @@ class Jets::Builders
97
97
 
98
98
  # Code prep and zipping
99
99
  check_code_size!
100
- calculate_md5s # must be called before generate_node_shims and create_zip_files
101
- generate_node_shims
100
+ calculate_md5s # must be called before generate_shims and create_zip_files
101
+ generate_shims
102
102
  create_zip_files
103
103
  end
104
104
 
@@ -106,16 +106,29 @@ class Jets::Builders
106
106
  CodeSize.check!
107
107
  end
108
108
 
109
- # We copy the files into the project because we cannot require simple functions
110
- # directly since they are wrapped by an anonymous class.
111
- # TODO: Do this with the other files we required the same way.
109
+ # Materialized internal code into actually user Jets app as part of the deploy process.
110
+ # Examples of things that we might materialize:
111
+ #
112
+ # Views
113
+ # Simple Functions
114
+ #
115
+ # For functions, We copy the files into the project because we cannot require
116
+ # simple functions directly since they are wrapped by an anonymous class.
112
117
  def copy_internal_jets_code
113
118
  files = []
119
+
120
+ mailers_controller = Jets::Router.has_controller?("Jets::MailersController")
121
+ if mailers_controller
122
+ files << "app/controllers/jets/mailers_controller.rb"
123
+ files << "app/views/jets/mailers"
124
+ files << "app/helpers/jets/mailers_helper.rb"
125
+ end
126
+
114
127
  files.each do |relative_path|
115
128
  src = File.expand_path("../internal/#{relative_path}", File.dirname(__FILE__))
116
129
  dest = "#{"#{stage_area}/code"}/#{relative_path}"
117
130
  FileUtils.mkdir_p(File.dirname(dest))
118
- FileUtils.cp(src, dest)
131
+ FileUtils.cp_r(src, dest)
119
132
  end
120
133
  end
121
134
 
@@ -96,20 +96,31 @@ class Jets::Builders
96
96
 
97
97
  def internal_shims
98
98
  jets_base_path if Jets.custom_domain?
99
+ s3_bucket_config if Jets.s3_event?
99
100
  end
100
101
 
101
102
  def jets_base_path
102
- path = "jets/base_path.rb"
103
+ copy_function_template("functions/jets/base_path.rb", stage_name: Jets::Resource::ApiGateway::Deployment.stage_name)
104
+ end
105
+
106
+ def s3_bucket_config
107
+ copy_function_template("shared/functions/jets/s3_bucket_config.rb")
108
+ end
109
+
110
+ # Copy code from internal folder to materialized app code
111
+ def copy_function_template(path, vars={})
103
112
  internal = File.expand_path("../internal", File.dirname(__FILE__))
104
- src = "#{internal}/app/functions/#{path}"
105
- result = Jets::Erb.result(src, stage_name: Jets::Resource::ApiGateway::Deployment.stage_name)
106
- dest = "#{tmp_code}/handlers/functions/#{path}"
113
+ src = "#{internal}/app/#{path}"
114
+ result = Jets::Erb.result(src, vars)
115
+ dest = "#{tmp_code}/handlers/#{path}"
107
116
  FileUtils.mkdir_p(File.dirname(dest))
108
117
  IO.write(dest, result)
109
118
  end
110
119
 
111
120
  # app/shared/functions/kevin.py => /tmp/jets/demo/app_root/handlers/shared/functions/kevin.py
112
121
  def copy_source_as_handler(fun)
122
+ return if fun.internal?
123
+
113
124
  source_path = fun.source_file
114
125
  unless source_path
115
126
  attributes = fun.template.values.first
@@ -140,8 +151,11 @@ class Jets::Builders
140
151
  end
141
152
 
142
153
  def shared_ruby_shim(fun)
143
- vars = Jets::Builders::ShimVars::Shared.new(fun)
144
- generate_handler(vars)
154
+ # Cant use native_function because that requires task. Just re-implement
155
+ dest_path = fun.handler_dest
156
+ source_path = dest_path.sub(/^handlers/,'app')
157
+ FileUtils.mkdir_p(File.dirname(dest_path))
158
+ FileUtils.cp(source_path, dest_path)
145
159
  end
146
160
 
147
161
  def common_base_shim
@@ -27,29 +27,17 @@ class Jets::Cfn::Builders
27
27
  add_parameter(k, Description: k)
28
28
  end
29
29
 
30
- depends_on_params.each do |logical_id, desc|
31
- add_parameter(logical_id, Description: desc)
30
+ depends_on_params.each do |output_key, output_value|
31
+ desc = output_value.gsub("!GetAtt ", "") # desc doesnt allow !GetAtt
32
+ add_parameter(output_key, Description: desc)
32
33
  end
33
34
  end
34
35
 
35
36
  def depends_on_params
36
37
  return {} unless @app_class.depends_on
37
38
 
38
- params = {}
39
- @app_class.depends_on.each do |shared_stack|
40
- dependency = shared_stack.to_s.camelize # logical_id
41
- dependency_outputs(dependency).each do |output|
42
- dependency_class = dependency.to_s.classify
43
- desc = "From #{dependency_class}.Outputs.#{output}"
44
- # key: logical_id , value: description
45
- params[output] = desc
46
- end
47
- end
48
- params
49
- end
50
-
51
- def dependency_outputs(dependency)
52
- dependency.to_s.classify.constantize.output_keys
39
+ depends = Jets::Stack::Depends.new(@app_class.depends_on)
40
+ depends.params
53
41
  end
54
42
 
55
43
  def add_functions