jets 1.0.18 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/Gemfile.lock +10 -10
  4. data/README/testing.md +5 -2
  5. data/lib/jets.rb +2 -2
  6. data/lib/jets/application.rb +69 -40
  7. data/lib/jets/booter.rb +17 -20
  8. data/lib/jets/builders/code_builder.rb +7 -8
  9. data/lib/jets/cfn/ship.rb +0 -6
  10. data/lib/jets/commands/build.rb +0 -5
  11. data/lib/jets/commands/deploy.rb +0 -4
  12. data/lib/jets/commands/main.rb +31 -4
  13. data/lib/jets/commands/templates/skeleton/{.env → .env.tt} +1 -0
  14. data/lib/jets/commands/templates/skeleton/config.ru +1 -0
  15. data/lib/jets/commands/upgrade/v1.rb +12 -0
  16. data/lib/jets/controller.rb +5 -0
  17. data/lib/jets/controller/base.rb +43 -21
  18. data/lib/jets/controller/cookies.rb +40 -0
  19. data/lib/jets/controller/cookies/jar.rb +269 -0
  20. data/lib/jets/controller/middleware.rb +4 -0
  21. data/lib/jets/controller/middleware/local.rb +119 -0
  22. data/lib/jets/{server/lambda_aws_proxy.rb → controller/middleware/local/api_gateway.rb} +11 -49
  23. data/lib/jets/controller/middleware/local/mimic_aws_call.rb +38 -0
  24. data/lib/jets/{server → controller/middleware/local}/route_matcher.rb +4 -4
  25. data/lib/jets/controller/middleware/main.rb +46 -0
  26. data/lib/jets/{server → controller/middleware}/webpacker_setup.rb +0 -1
  27. data/lib/jets/controller/params.rb +2 -1
  28. data/lib/jets/controller/rack.rb +5 -0
  29. data/lib/jets/controller/rack/adapter.rb +60 -0
  30. data/lib/jets/controller/rack/env.rb +96 -0
  31. data/lib/jets/controller/redirection.rb +1 -1
  32. data/lib/jets/controller/renderers.rb +1 -1
  33. data/lib/jets/controller/renderers/base_renderer.rb +0 -4
  34. data/lib/jets/controller/renderers/{aws_proxy_renderer.rb → rack_renderer.rb} +7 -19
  35. data/lib/jets/controller/renderers/template_renderer.rb +1 -1
  36. data/lib/jets/controller/request.rb +14 -44
  37. data/lib/jets/controller/response.rb +55 -7
  38. data/lib/jets/internal/app/controllers/jets/rack_controller.rb +13 -3
  39. data/lib/jets/mega.rb +7 -0
  40. data/lib/jets/{rack → mega}/hash_converter.rb +1 -1
  41. data/lib/jets/{rack → mega}/request.rb +17 -4
  42. data/lib/jets/middleware.rb +38 -0
  43. data/lib/jets/middleware/configurator.rb +84 -0
  44. data/lib/jets/middleware/default_stack.rb +44 -0
  45. data/lib/jets/middleware/layer.rb +34 -0
  46. data/lib/jets/middleware/stack.rb +77 -0
  47. data/lib/jets/resource/function.rb +1 -1
  48. data/lib/jets/ruby_server.rb +1 -1
  49. data/lib/jets/server.rb +48 -13
  50. data/lib/jets/version.rb +1 -1
  51. metadata +24 -17
  52. data/lib/jets/application/middleware.rb +0 -23
  53. data/lib/jets/default/application.rb +0 -23
  54. data/lib/jets/rack.rb +0 -7
  55. data/lib/jets/rack/server.rb +0 -47
  56. data/lib/jets/server/api_gateway.rb +0 -39
  57. data/lib/jets/server/timing_middleware.rb +0 -33
  58. data/lib/jets/timing.rb +0 -65
  59. data/lib/jets/timing/report.rb +0 -82
@@ -2,7 +2,6 @@ require 'digest'
2
2
 
3
3
  module Jets::Commands
4
4
  class Build
5
- include Jets::Timing
6
5
  include StackInfo
7
6
 
8
7
  def initialize(options)
@@ -17,18 +16,15 @@ module Jets::Commands
17
16
  @options.merge!(stack_type: stack_type, s3_bucket: s3_bucket)
18
17
  build
19
18
  end
20
- time :run
21
19
 
22
20
  def build
23
21
  build_code unless @options[:templates]
24
22
  build_templates
25
23
  end
26
- time :build
27
24
 
28
25
  def build_code
29
26
  Jets::Builders::CodeBuilder.new.build unless @options[:noop]
30
27
  end
31
- time :build_code
32
28
 
33
29
  def build_templates
34
30
  puts "Building CloudFormation templates."
@@ -36,7 +32,6 @@ module Jets::Commands
36
32
  build_minimal_template
37
33
  build_all_templates if full?
38
34
  end
39
- time :build_templates
40
35
 
41
36
  def full?
42
37
  @options[:templates] || @options[:stack_type] == :full
@@ -2,7 +2,6 @@ module Jets::Commands
2
2
  class Deploy
3
3
  extend Memoist
4
4
  include StackInfo
5
- include Jets::Timing
6
5
 
7
6
  def initialize(options)
8
7
  @options = options
@@ -32,7 +31,6 @@ module Jets::Commands
32
31
  # deploy full nested stack when stack already exists
33
32
  ship(stack_type: :full, s3_bucket: s3_bucket)
34
33
  end
35
- time :run
36
34
 
37
35
  def delete_minimal_stack
38
36
  puts "Existing stack is in ROLLBACK_COMPLETE state from a previous failed minimal deploy. Deleting stack and continuing."
@@ -51,7 +49,6 @@ module Jets::Commands
51
49
  def build_code
52
50
  Jets::Commands::Build.new(@options).build_code
53
51
  end
54
- time :build_code
55
52
 
56
53
  # Checks that all routes are validate and have corresponding lambda functions
57
54
  def validate_routes!
@@ -70,7 +67,6 @@ module Jets::Commands
70
67
  Jets::Commands::Build.new(options).build_templates
71
68
  Jets::Cfn::Ship.new(options).run
72
69
  end
73
- time :ship
74
70
 
75
71
  def status
76
72
  Jets::Cfn::Status.new(stack_name)
@@ -18,9 +18,7 @@ module Jets::Commands
18
18
  # environment parameter. It is not actually set here. It is set earlier
19
19
  # in cli.rb: set_jets_env_for_deploy_command!
20
20
  def deploy(environment=nil)
21
- Jets::Timing.clear # must happen outside Deploy#run
22
21
  Deploy.new(options).run
23
- Jets::Timing.report
24
22
  end
25
23
 
26
24
  desc "delete", "Delete the Jets project and all its resources"
@@ -35,13 +33,16 @@ module Jets::Commands
35
33
  long_desc Help.text(:server)
36
34
  option :port, default: "8888", desc: "use PORT"
37
35
  option :host, default: "127.0.0.1", desc: "listen on HOST"
36
+ option :reload, type: :boolean, default: true, desc: "Enables hot-reloading for development"
38
37
  def server
39
38
  # shell out to shotgun for automatic reloading
40
39
  o = options
41
- command = "bundle exec shotgun --port #{o[:port]} --host #{o[:host]}"
40
+ server_command = o[:reload] ? "shotgun" : "rackup"
41
+ command = "bundle exec #{server_command} --port #{o[:port]} --host #{o[:host]}"
42
42
  puts "=> #{command}".colorize(:green)
43
43
  puts Jets::Booter.message
44
- Jets::Rack::Server.start unless ENV['JETS_RACK'] == '0' # rack server runs in background by default
44
+ Jets::Booter.check_config_ru!
45
+ Jets::Server.start(options) unless ENV['JETS_RACK'] == '0' # rack server runs in background by default
45
46
  system(command)
46
47
  end
47
48
 
@@ -80,6 +81,16 @@ module Jets::Commands
80
81
  option :guess, type: :boolean, default: true, desc: "Enables guess mode. Uses inference to allows use of all dashes to specify functions. Smart mode verifies that the function exists in the code base."
81
82
  option :local, type: :boolean, desc: "Enables local mode. Instead of invoke the AWS Lambda function, the method gets called locally with current app code. With local mode guess mode is always used."
82
83
  def call(function_name, payload='')
84
+ # Printing to stdout can mangle up the response when piping
85
+ # the value to jq. For example:
86
+ #
87
+ # `jets call --local .. | jq`
88
+ #
89
+ # By redirecting stderr we can use jq safely.
90
+ #
91
+ $stdout.sync = true
92
+ $stderr.sync = true
93
+ $stdout = $stderr # jets call operation
83
94
  Call.new(function_name, payload, options).run
84
95
  end
85
96
 
@@ -101,6 +112,22 @@ module Jets::Commands
101
112
  Jets::Commands::Url.new(options).display
102
113
  end
103
114
 
115
+ desc "secret", "Generates secret"
116
+ long_desc Help.text(:secret)
117
+ def secret
118
+ puts SecureRandom.hex(64)
119
+ end
120
+
121
+ desc "middleware", "Prints list of middleware"
122
+ long_desc Help.text(:middleware)
123
+ def middleware
124
+ stack = Jets.application.middlewares
125
+ stack.middlewares.each do |middleware|
126
+ puts "use #{middleware.name}"
127
+ end
128
+ puts "run #{Jets.application.endpoint}"
129
+ end
130
+
104
131
  desc "version", "Prints Jets version"
105
132
  long_desc Help.text(:version)
106
133
  def version
@@ -1,2 +1,3 @@
1
1
  # Example .env, meant to be updated.
2
2
  # Variables in here are available and shared across all environments: development, production, etc.
3
+ SECRET_KEY_BASE=<%= SecureRandom.hex(64) %>
@@ -1,4 +1,5 @@
1
1
  # This file is used by Rack-based servers to start the application.
2
2
 
3
3
  require "jets"
4
+ Jets.boot
4
5
  run Jets.application
@@ -1,5 +1,6 @@
1
1
  require 'fileutils'
2
2
 
3
+ # This class tries to be idempotent, so users should be able to run it multiple times safely.
3
4
  class Jets::Commands::Upgrade
4
5
  class V1
5
6
  def initialize(options)
@@ -11,6 +12,7 @@ class Jets::Commands::Upgrade
11
12
  environment_configs
12
13
  update_routes
13
14
  update_mode_setting
15
+ update_config_ru
14
16
  puts "Upgrade complete."
15
17
  end
16
18
 
@@ -67,5 +69,15 @@ class Jets::Commands::Upgrade
67
69
  content = lines.join
68
70
  IO.write(application_file, content)
69
71
  end
72
+
73
+ def update_config_ru
74
+ config_ru = File.read("#{Jets.root}config.ru")
75
+ return if config_ru.include?("Jets.boot")
76
+
77
+ src = File.expand_path("../templates/skeleton/config.ru", File.dirname(__FILE__))
78
+ dest = "#{Jets.root}config.ru"
79
+ puts "Update: config.ru"
80
+ FileUtils.cp(src, dest)
81
+ end
70
82
  end
71
83
  end
@@ -1,8 +1,13 @@
1
1
  class Jets::Controller
2
+ DEFAULT_CONTENT_TYPE = "text/html; charset=utf-8"
3
+
2
4
  autoload :Base, "jets/controller/base"
3
5
  autoload :Callbacks, "jets/controller/callbacks"
6
+ autoload :Cookies, "jets/controller/cookies"
4
7
  autoload :Layout, "jets/controller/layout"
8
+ autoload :Middleware, "jets/controller/middleware"
5
9
  autoload :Params, "jets/controller/params"
10
+ autoload :Rack, "jets/controller/rack"
6
11
  autoload :Redirection, "jets/controller/redirection"
7
12
  autoload :Renderers, "jets/controller/renderers"
8
13
  autoload :Rendering, "jets/controller/rendering"
@@ -6,38 +6,60 @@ require "rack/utils" # Rack::Utils.parse_nested_query
6
6
  # Controller public methods get turned into Lambda functions.
7
7
  class Jets::Controller
8
8
  class Base < Jets::Lambda::Functions
9
- include Layout
10
9
  include Callbacks
11
- include Rendering
10
+ include Cookies
11
+ include Layout
12
12
  include Params
13
+ include Rendering
13
14
 
14
- def self.process(event, context={}, meth)
15
- t1 = Time.now
16
- Jets.logger.info "Processing by #{self}##{meth}"
15
+ delegate :headers, to: :request
16
+ delegate :set_header, to: :response
17
+ attr_reader :request, :response
18
+ attr_accessor :session
19
+ def initialize(event, context={}, meth)
20
+ super
21
+ @request = Request.new(event, context)
22
+ @response = Response.new
23
+ end
17
24
 
18
- controller = new(event, context, meth)
25
+ def process!
26
+ adapter = Jets::Controller::Rack::Adapter.new(event, context, meth)
27
+ adapter.rack_vars(
28
+ 'jets.controller' => self,
29
+ 'lambda.context' => context,
30
+ 'lambda.event' => event,
31
+ 'lambda.meth' => meth,
32
+ )
33
+ # adapter.process ultimately calls app controller action at the very last
34
+ # middleware stack.
35
+ adapter.process # Returns API Gateway hash structure
36
+ end
19
37
 
20
- Jets.logger.info " Event: #{event.inspect}"
21
- Jets.logger.info " Parameters: #{controller.params(raw: true).to_h.inspect}"
38
+ def dispatch!
39
+ t1 = Time.now
40
+ Jets.logger.info "Processing by #{self.class.name}##{@meth}"
41
+ Jets.logger.info " Event: #{@event.inspect}"
42
+ Jets.logger.info " Parameters: #{params(raw: true).to_h.inspect}"
22
43
 
23
- controller.run_before_actions
24
- controller.send(meth)
25
- resp = controller.ensure_render
26
- controller.run_after_actions
44
+ run_before_actions
45
+ send(@meth)
46
+ triplet = ensure_render
47
+ run_after_actions
27
48
 
28
49
  took = Time.now - t1
29
- Jets.logger.info "Completed Status Code #{resp["statusCode"]} in #{took}s"
50
+ status = triplet[0]
51
+ Jets.logger.info "Completed Status Code #{status} in #{took}s"
30
52
 
31
- resp
53
+ triplet # status, headers, body
32
54
  end
33
55
 
34
- delegate :headers, to: :request
35
- delegate :set_header, to: :response
36
- attr_reader :request, :response
37
- def initialize(event, context={}, meth)
38
- super
39
- @request = Request.new(event)
40
- @response = Response.new(event)
56
+ def self.process(event, context={}, meth)
57
+ controller = new(event, context, meth)
58
+ # Using send because process! is private method in Jets::RackController so
59
+ # it doesnt create a lambda function. It's doesnt matter what scope process!
60
+ # is in Controller::Base because Jets lambda functions inheritance doesnt
61
+ # include methods in Controller::Base.
62
+ controller.send(:process!)
41
63
  end
42
64
 
43
65
  class_attribute :internal_controller
@@ -0,0 +1,40 @@
1
+ # Based on sinatra/cookies.rb
2
+ # https://github.com/sinatra/sinatra/blob/master/sinatra-contrib/lib/sinatra/cookies.rb
3
+ class Jets::Controller
4
+ # = Jets::Controller::Cookies
5
+ #
6
+ # Easy way to deal with cookies
7
+ #
8
+ # == Usage
9
+ #
10
+ # Allows you to read cookies:
11
+ #
12
+ # def index
13
+ # "value: #{cookies[:something]}"
14
+ # end
15
+ #
16
+ # And of course to write cookies:
17
+ #
18
+ # def show
19
+ # cookies[:something] = 'foobar'
20
+ # render json: cookies
21
+ # end
22
+ #
23
+ # And generally behaves like a hash:
24
+ #
25
+ # def index
26
+ # cookies.merge! 'foo' => 'bar', 'bar' => 'baz'
27
+ # cookies.keep_if { |key, value| key.start_with? 'b' }
28
+ # foo, bar = cookies.values_at 'foo', 'bar'
29
+ # puts "size: #{cookies.length}"
30
+ # render json: cookies
31
+ # end
32
+ #
33
+ module Cookies
34
+ autoload :Jar, "jets/controller/cookies/jar"
35
+
36
+ def cookies
37
+ @cookies ||= Jar.new(self)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,269 @@
1
+ # Based on sinatra/cookies.rb
2
+ # https://github.com/sinatra/sinatra/blob/master/sinatra-contrib/lib/sinatra/cookies.rb
3
+ module Jets::Controller::Cookies
4
+ class Jar
5
+ include Enumerable
6
+ attr_reader :options
7
+
8
+ def initialize(controller)
9
+ @response_string = nil
10
+ @response_hash = {}
11
+ @response = controller.response
12
+ @request = controller.request
13
+ @deleted = []
14
+
15
+ @options = {
16
+ path: @request.script_name.to_s.empty? ? '/' : @request.script_name,
17
+ domain: @request.host == 'localhost' ? nil : @request.host,
18
+ secure: @request.ssl?,
19
+ httponly: true
20
+ }
21
+ end
22
+
23
+ def ==(other)
24
+ other.respond_to? :to_hash and to_hash == other.to_hash
25
+ end
26
+
27
+ def [](key)
28
+ response_cookies[key.to_s] || request_cookies[key.to_s]
29
+ end
30
+
31
+ def []=(key, value)
32
+ set(key, value: value)
33
+ end
34
+
35
+ def assoc(key)
36
+ to_hash.assoc(key.to_s)
37
+ end if Hash.method_defined? :assoc
38
+
39
+ def clear
40
+ each_key { |k| delete(k) }
41
+ end
42
+
43
+ def compare_by_identity?
44
+ false
45
+ end
46
+
47
+ def default
48
+ nil
49
+ end
50
+
51
+ alias default_proc default
52
+
53
+ def delete(key)
54
+ result = self[key]
55
+ @response.delete_cookie(key.to_s, @options)
56
+ result
57
+ end
58
+
59
+ def delete_if
60
+ return enum_for(__method__) unless block_given?
61
+ each { |k, v| delete(k) if yield(k, v) }
62
+ self
63
+ end
64
+
65
+ def each(&block)
66
+ return enum_for(__method__) unless block_given?
67
+ to_hash.each(&block)
68
+ end
69
+
70
+ def each_key(&block)
71
+ return enum_for(__method__) unless block_given?
72
+ to_hash.each_key(&block)
73
+ end
74
+
75
+ alias each_pair each
76
+
77
+ def each_value(&block)
78
+ return enum_for(__method__) unless block_given?
79
+ to_hash.each_value(&block)
80
+ end
81
+
82
+ def empty?
83
+ to_hash.empty?
84
+ end
85
+
86
+ def fetch(key, &block)
87
+ response_cookies.fetch(key.to_s) do
88
+ request_cookies.fetch(key.to_s, &block)
89
+ end
90
+ end
91
+
92
+ def flatten
93
+ to_hash.flatten
94
+ end if Hash.method_defined? :flatten
95
+
96
+ def has_key?(key)
97
+ response_cookies.has_key? key.to_s or request_cookies.has_key? key.to_s
98
+ end
99
+
100
+ def has_value?(value)
101
+ response_cookies.has_value? value or request_cookies.has_value? value
102
+ end
103
+
104
+ def hash
105
+ to_hash.hash
106
+ end
107
+
108
+ alias include? has_key?
109
+ alias member? has_key?
110
+
111
+ def index(value)
112
+ warn "Hash#index is deprecated; use Hash#key"
113
+ key(value)
114
+ end
115
+
116
+ def inspect
117
+ "<##{self.class}: #{to_hash.inspect[1..-2]}>"
118
+ end
119
+
120
+ def invert
121
+ to_hash.invert
122
+ end if Hash.method_defined? :invert
123
+
124
+ def keep_if
125
+ return enum_for(__method__) unless block_given?
126
+ delete_if { |*a| not yield(*a) }
127
+ end
128
+
129
+ def key(value)
130
+ to_hash.key(value)
131
+ end
132
+
133
+ alias key? has_key?
134
+
135
+ def keys
136
+ to_hash.keys
137
+ end
138
+
139
+ def length
140
+ to_hash.length
141
+ end
142
+
143
+ def merge(other, &block)
144
+ to_hash.merge(other, &block)
145
+ end
146
+
147
+ def merge!(other)
148
+ other.each_pair do |key, value|
149
+ if block_given? and include? key
150
+ self[key] = yield(key.to_s, self[key], value)
151
+ else
152
+ self[key] = value
153
+ end
154
+ end
155
+ end
156
+
157
+ def rassoc(value)
158
+ to_hash.rassoc(value)
159
+ end
160
+
161
+ def rehash
162
+ response_cookies.rehash
163
+ request_cookies.rehash
164
+ self
165
+ end
166
+
167
+ def reject(&block)
168
+ return enum_for(__method__) unless block_given?
169
+ to_hash.reject(&block)
170
+ end
171
+
172
+ alias reject! delete_if
173
+
174
+ def replace(other)
175
+ select! { |k, v| other.include?(k) or other.include?(k.to_s) }
176
+ merge! other
177
+ end
178
+
179
+ def select(&block)
180
+ return enum_for(__method__) unless block_given?
181
+ to_hash.select(&block)
182
+ end
183
+
184
+ alias select! keep_if if Hash.method_defined? :select!
185
+
186
+ def set(key, options = {})
187
+ @response.set_cookie key.to_s, @options.merge(options)
188
+ end
189
+
190
+ def shift
191
+ key, value = to_hash.shift
192
+ delete(key)
193
+ [key, value]
194
+ end
195
+
196
+ alias size length
197
+
198
+ def sort(&block)
199
+ to_hash.sort(&block)
200
+ end if Hash.method_defined? :sort
201
+
202
+ alias store []=
203
+
204
+ def to_hash
205
+ request_cookies.merge(response_cookies)
206
+ end
207
+
208
+ def to_a
209
+ to_hash.to_a
210
+ end
211
+
212
+ def to_s
213
+ to_hash.to_s
214
+ end
215
+
216
+ alias update merge!
217
+ alias value? has_value?
218
+
219
+ def values
220
+ to_hash.values
221
+ end
222
+
223
+ def values_at(*list)
224
+ list.map { |k| self[k] }
225
+ end
226
+
227
+ private
228
+
229
+ def warn(message)
230
+ super "#{caller.first[/^[^:]:\d+:/]} warning: #{message}"
231
+ end
232
+
233
+ def deleted
234
+ parse_response
235
+ @deleted
236
+ end
237
+
238
+ def response_cookies
239
+ parse_response
240
+ @response_hash
241
+ end
242
+
243
+ def parse_response
244
+ string = @response.headers['Set-Cookie']
245
+ return if @response_string == string
246
+
247
+ hash = {}
248
+
249
+ string.each_line do |line|
250
+ key, value = line.split(';', 2).first.to_s.split('=', 2)
251
+ next if key.nil?
252
+ key = Rack::Utils.unescape(key)
253
+ if line =~ /expires=Thu, 01[-\s]Jan[-\s]1970/
254
+ @deleted << key
255
+ else
256
+ @deleted.delete key
257
+ hash[key] = value
258
+ end
259
+ end
260
+
261
+ @response_hash.replace hash
262
+ @response_string = string
263
+ end
264
+
265
+ def request_cookies
266
+ @request.cookies.reject { |key, value| deleted.include? key }
267
+ end
268
+ end
269
+ end