jets 1.1.5 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/Gemfile.lock +10 -6
  4. data/README/testing.md +5 -1
  5. data/jets.gemspec +1 -0
  6. data/lib/jets.rb +5 -1
  7. data/lib/jets/application.rb +39 -19
  8. data/lib/jets/aws_services.rb +16 -10
  9. data/lib/jets/aws_services/stack_status.rb +7 -0
  10. data/lib/jets/booter.rb +6 -2
  11. data/lib/jets/builders/code_builder.rb +14 -0
  12. data/lib/jets/builders/handler_generator.rb +15 -0
  13. data/lib/jets/builders/shim_vars/app.rb +4 -3
  14. data/lib/jets/builders/shim_vars/shared.rb +8 -4
  15. data/lib/jets/builders/templates/shim.js +7 -3
  16. data/lib/jets/camelizer.rb +2 -1
  17. data/lib/jets/cfn/builders.rb +0 -1
  18. data/lib/jets/cfn/builders/api_deployment_builder.rb +27 -0
  19. data/lib/jets/cfn/builders/api_gateway_builder.rb +22 -2
  20. data/lib/jets/cfn/ship.rb +38 -6
  21. data/lib/jets/commands/call.rb +0 -1
  22. data/lib/jets/commands/call/guesser.rb +0 -3
  23. data/lib/jets/commands/clean/log.rb +18 -0
  24. data/lib/jets/commands/console.rb +1 -1
  25. data/lib/jets/commands/import/sequence.rb +2 -3
  26. data/lib/jets/commands/runner.rb +1 -1
  27. data/lib/jets/commands/sequence.rb +0 -1
  28. data/lib/jets/commands/templates/skeleton/config/application.rb.tt +11 -0
  29. data/lib/jets/commands/url.rb +32 -7
  30. data/lib/jets/controller/base.rb +21 -5
  31. data/lib/jets/controller/layout.rb +0 -3
  32. data/lib/jets/controller/middleware/local/api_gateway.rb +2 -5
  33. data/lib/jets/controller/middleware/local/mimic_aws_call.rb +2 -2
  34. data/lib/jets/controller/params.rb +42 -10
  35. data/lib/jets/controller/rack/adapter.rb +5 -2
  36. data/lib/jets/controller/rack/env.rb +17 -8
  37. data/lib/jets/controller/renderers/rack_renderer.rb +1 -1
  38. data/lib/jets/controller/rendering.rb +4 -1
  39. data/lib/jets/core.rb +8 -16
  40. data/lib/jets/internal/app/functions/jets/base_path.rb +153 -0
  41. data/lib/jets/klass.rb +38 -5
  42. data/lib/jets/lambda/dsl.rb +0 -2
  43. data/lib/jets/mega/request.rb +44 -13
  44. data/lib/jets/mega/request/source.rb +21 -0
  45. data/lib/jets/middleware/configurator.rb +1 -1
  46. data/lib/jets/middleware/default_stack.rb +2 -2
  47. data/lib/jets/resource.rb +1 -0
  48. data/lib/jets/resource/api_gateway.rb +5 -3
  49. data/lib/jets/resource/api_gateway/base_path.rb +5 -0
  50. data/lib/jets/resource/api_gateway/base_path/function.rb +42 -0
  51. data/lib/jets/resource/api_gateway/base_path/mapping.rb +44 -0
  52. data/lib/jets/resource/api_gateway/base_path/role.rb +76 -0
  53. data/lib/jets/resource/api_gateway/cors.rb +1 -1
  54. data/lib/jets/resource/api_gateway/deployment.rb +9 -5
  55. data/lib/jets/resource/api_gateway/domain_name.rb +56 -0
  56. data/lib/jets/resource/api_gateway/method.rb +3 -4
  57. data/lib/jets/resource/api_gateway/resource.rb +4 -3
  58. data/lib/jets/resource/api_gateway/rest_api.rb +42 -14
  59. data/lib/jets/resource/api_gateway/rest_api/change_detection.rb +42 -0
  60. data/lib/jets/resource/api_gateway/rest_api/logical_id.rb +59 -0
  61. data/lib/jets/resource/api_gateway/rest_api/routes.rb +127 -0
  62. data/lib/jets/resource/child_stack/api_deployment.rb +5 -1
  63. data/lib/jets/resource/function.rb +3 -20
  64. data/lib/jets/resource/function/environment.rb +23 -0
  65. data/lib/jets/resource/iam/application_role.rb +1 -1
  66. data/lib/jets/resource/route53.rb +3 -0
  67. data/lib/jets/resource/route53/record_set.rb +70 -0
  68. data/lib/jets/router.rb +2 -0
  69. data/lib/jets/ruby_server.rb +6 -3
  70. data/lib/jets/stack.rb +1 -3
  71. data/lib/jets/stack/main/dsl.rb +1 -1
  72. data/lib/jets/stack/main/extensions/lambda.rb +4 -2
  73. data/lib/jets/turbine.rb +0 -3
  74. data/lib/jets/version.rb +1 -1
  75. data/vendor/jets-gems/lib/jets/gems.rb +1 -0
  76. data/vendor/jets-gems/lib/jets/gems/agree.rb +41 -0
  77. data/vendor/jets-gems/lib/jets/gems/check.rb +15 -2
  78. metadata +30 -2
@@ -0,0 +1,42 @@
1
+ class Jets::Resource::ApiGateway::RestApi
2
+ class ChangeDetection
3
+ extend Memoist
4
+ include Jets::AwsServices
5
+
6
+ def changed?
7
+ return false unless parent_stack_exists?
8
+ current_binary_media_types != new_binary_media_types ||
9
+ Routes.changed?
10
+ end
11
+
12
+ def new_binary_media_types
13
+ rest_api = Jets::Resource::ApiGateway::RestApi.new
14
+ rest_api.binary_media_types
15
+ end
16
+ memoize :new_binary_media_types
17
+
18
+ # Duplicated in rest_api/change_detection.rb, base_path/role.rb, rest_api/routes.rb
19
+ def current_binary_media_types
20
+ return nil unless parent_stack_exists?
21
+
22
+ stack = cfn.describe_stacks(stack_name: parent_stack_name).stacks.first
23
+
24
+ api_gateway_stack_arn = lookup(stack[:outputs], "ApiGateway")
25
+
26
+ stack = cfn.describe_stacks(stack_name: api_gateway_stack_arn).stacks.first
27
+ rest_api_id = lookup(stack[:outputs], "RestApi")
28
+
29
+ resp = apigateway.get_rest_api(rest_api_id: rest_api_id)
30
+ resp.binary_media_types
31
+ end
32
+ memoize :current_binary_media_types
33
+
34
+ def parent_stack_exists?
35
+ stack_exists?(parent_stack_name)
36
+ end
37
+
38
+ def parent_stack_name
39
+ Jets::Naming.parent_stack_name
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,59 @@
1
+ class Jets::Resource::ApiGateway::RestApi
2
+ class LogicalId
3
+ extend Memoist
4
+ include Jets::AwsServices
5
+
6
+ def get
7
+ return default unless stack_exists?(parent_stack_name) && api_gateway_exists?
8
+
9
+ change_detection = ChangeDetection.new
10
+ if change_detection.changed?
11
+ new_id
12
+ else
13
+ current
14
+ end
15
+ end
16
+
17
+ # Takes current logical id and increments the number that is appended to it.
18
+ #
19
+ # Examples:
20
+ #
21
+ # RestApi => RestApi1
22
+ # RestApi1 => RestApi2
23
+ # RestApi2 => RestApi3
24
+ # RestApi7 => RestApi8
25
+ def new_id
26
+ regexp = /(\d+)/
27
+ md = current.match(regexp)
28
+ if md
29
+ current.gsub(regexp,'') + (md[1].to_i + 1).to_s
30
+ else
31
+ current + "1"
32
+ end
33
+ end
34
+
35
+ def current
36
+ resources = cfn.describe_stack_resources(stack_name: api_gateway_stack_arn).stack_resources
37
+ rest_api = resources.find { |r| r.resource_type == 'AWS::ApiGateway::RestApi' }
38
+ rest_api.logical_resource_id
39
+ end
40
+ memoize :current
41
+
42
+ def api_gateway_stack_arn
43
+ stack = cfn.describe_stacks(stack_name: parent_stack_name).stacks.first
44
+ lookup(stack[:outputs], "ApiGateway") # api_gateway_stack_arn
45
+ end
46
+
47
+ def api_gateway_exists?
48
+ !!api_gateway_stack_arn
49
+ end
50
+
51
+ def parent_stack_name
52
+ Jets::Naming.parent_stack_name
53
+ end
54
+
55
+ def default
56
+ "RestApi"
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,127 @@
1
+ class Jets::Resource::ApiGateway::RestApi
2
+ class Routes
3
+ extend Memoist
4
+ include Jets::AwsServices
5
+
6
+ def self.changed?
7
+ new.changed?
8
+ end
9
+
10
+ def changed?
11
+ deployed_routes = build
12
+ deployed_routes.each do |deployed_route|
13
+ new_route = find_comparable_route(deployed_route)
14
+ if new_route && new_route.to != deployed_route.to
15
+ # change in already deployed route has been detected, requires bluegreen deploy
16
+ return true
17
+ end
18
+ end
19
+ false # Reaching here means no routes have been changed in a way that requires a bluegreen deploy
20
+ end
21
+
22
+ # Build up deployed routes from the existing CloudFormation resources.
23
+ def build
24
+ routes = []
25
+
26
+ resources, position = [], true
27
+ while position
28
+ position = nil if position == true # start of loop
29
+ resp = apigateway.get_resources(
30
+ rest_api_id: rest_api_id,
31
+ position: position,
32
+ )
33
+ resources += resp.items
34
+ position = resp.position
35
+ end
36
+
37
+ resources.each do |resource|
38
+ resource_methods = resource.resource_methods
39
+ next if resource_methods.nil?
40
+
41
+ resource_methods.each do |http_verb, resource_method|
42
+ # puts "#{http_verb} #{resource.path} | resource.id #{resource.id}"
43
+ # puts to(resource.id, http_verb)
44
+
45
+ # Test changing config.cors and CloudFormation does an in-place update
46
+ # on the resource. So no need to do bluegreen deployments for OPTIONS.
47
+ next if http_verb == "OPTIONS"
48
+
49
+ path = recreate_path(resource.path)
50
+ method = http_verb.downcase.to_sym
51
+ to = to(resource.id, http_verb)
52
+ route = Jets::Route.new(path: path, method: method, to: to)
53
+ routes << route
54
+ end
55
+ end
56
+ routes
57
+ end
58
+
59
+ # Find a route that has the same path and method. This is a comparable route
60
+ # Then we will compare the to or controller action to see if an already
61
+ # deployed route has been changed.
62
+ def find_comparable_route(deployed_route)
63
+ new_routes.find do |new_route|
64
+ new_route.path == deployed_route.path &&
65
+ new_route.method == deployed_route.method
66
+ end
67
+ end
68
+
69
+ def new_routes
70
+ Jets::Router.routes
71
+ end
72
+ memoize :new_routes
73
+
74
+ def recreate_path(path)
75
+ path = path.gsub(%r{^/},'')
76
+ path = path.sub(/{(.*)\+}/, '*\1')
77
+ path.sub(/{(.*)}/, ':\1')
78
+ end
79
+
80
+ def to(resource_id, http_method)
81
+ uri = method_uri(resource_id, http_method)
82
+ recreate_to(uri) unless uri.nil?
83
+ end
84
+
85
+ def method_uri(resource_id, http_method)
86
+ resp = apigateway.get_method(
87
+ rest_api_id: rest_api_id,
88
+ resource_id: resource_id,
89
+ http_method: http_method
90
+ )
91
+ resp.method_integration.uri
92
+ end
93
+
94
+ # Parses method uri and recreates a Route to argument.
95
+ # So:
96
+ # "arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-2:112233445566:function:demo-test-posts_controller-new/invocations"
97
+ # Returns:
98
+ # posts#new
99
+ def recreate_to(method_uri)
100
+ md = method_uri.match(/function:(.*)\//)
101
+ function_name = md[1] # IE: demo-dev-posts_controller-new
102
+ controller_action = function_name.sub("#{Jets.project_namespace}-", '')
103
+ md = controller_action.match(/(.*)_controller-(.*)/)
104
+ controller = md[1]
105
+ controller = controller.gsub('-','/')
106
+ action = md[2]
107
+ "#{controller}##{action}" # IE: posts#new
108
+ end
109
+
110
+ # Duplicated in rest_api/change_detection.rb, base_path/role.rb, rest_api/routes.rb
111
+ def rest_api_id
112
+ stack_name = Jets::Naming.parent_stack_name
113
+ return default unless stack_exists?(stack_name)
114
+
115
+ stack = cfn.describe_stacks(stack_name: stack_name).stacks.first
116
+
117
+ api_gateway_stack_arn = lookup(stack[:outputs], "ApiGateway")
118
+
119
+ # resources = cfn.describe_stack_resources(stack_name: api_gateway_stack_arn).stack_resources
120
+ stack = cfn.describe_stacks(stack_name: api_gateway_stack_arn).stacks.first
121
+ rest_api_id = lookup(stack[:outputs], "RestApi")
122
+ end
123
+ memoize :rest_api_id
124
+
125
+ # apigateway.get_rest_api(rest_api_id: rest_api_id) # resp
126
+ end
127
+ end
@@ -19,9 +19,13 @@ module Jets::Resource::ChildStack
19
19
  end
20
20
 
21
21
  def parameters
22
- {
22
+ p = {
23
+ IamRole: "!GetAtt IamRole.Arn",
23
24
  RestApi: "!GetAtt ApiGateway.Outputs.RestApi",
25
+ S3Bucket: "!Ref S3Bucket",
24
26
  }
27
+ p[:DomainName] = "!GetAtt ApiGateway.Outputs.DomainName" if Jets.custom_domain?
28
+ p
25
29
  end
26
30
 
27
31
  def depends_on
@@ -1,5 +1,8 @@
1
1
  class Jets::Resource
2
2
  class Function < Jets::Resource::Base
3
+ autoload :Environment, 'jets/resource/function/environment'
4
+ include Environment
5
+
3
6
  def initialize(task)
4
7
  @task = task
5
8
  @app_class = task.class_name.to_s
@@ -30,26 +33,6 @@ class Jets::Resource
30
33
  finalize_properties!(props)
31
34
  end
32
35
 
33
- def env_properties
34
- env_vars = Jets::Dotenv.load!(true)
35
- variables = environment.merge(env_vars)
36
- {environment: { variables: variables }}
37
- end
38
-
39
- def environment
40
- env = Jets.config.environment ? Jets.config.environment.to_h : {}
41
- env.deep_merge(jets_env)
42
- end
43
-
44
- # These jets env variables are always included
45
- def jets_env
46
- env = {}
47
- env[:JETS_ENV] = Jets.env.to_s
48
- env[:JETS_ENV_EXTRA] = Jets.config.env_extra if Jets.config.env_extra
49
- env[:JETS_STAGE] = Jets::Resource::ApiGateway::Deployment.stage_name
50
- env
51
- end
52
-
53
36
  # Global properties example:
54
37
  # jets defaults are in jets/default/application.rb.
55
38
  # Your application's default config/application.rb then get used. Example:
@@ -0,0 +1,23 @@
1
+ class Jets::Resource::Function
2
+ module Environment
3
+ def env_properties
4
+ env_vars = Jets::Dotenv.load!(true)
5
+ variables = environment.merge(env_vars)
6
+ {environment: { variables: variables }}
7
+ end
8
+
9
+ def environment
10
+ env = Jets.config.environment ? Jets.config.environment.to_h : {}
11
+ env.deep_merge(jets_env)
12
+ end
13
+
14
+ # These jets env variables are always included
15
+ def jets_env
16
+ env = {}
17
+ env[:JETS_ENV] = Jets.env.to_s
18
+ env[:JETS_ENV_EXTRA] = Jets.config.env_extra if Jets.config.env_extra
19
+ env[:JETS_STAGE] = Jets::Resource::ApiGateway::Deployment.stage_name
20
+ env
21
+ end
22
+ end
23
+ end
@@ -15,7 +15,7 @@ module Jets::Resource::Iam
15
15
  end
16
16
 
17
17
  def role_name
18
- "#{Jets.config.project_namespace}-application-role" # camelized because used as template value
18
+ "#{Jets.config.project_namespace}-application-role"
19
19
  end
20
20
 
21
21
  def outputs
@@ -0,0 +1,3 @@
1
+ module Jets::Resource::Route53
2
+ autoload :RecordSet, 'jets/resource/route53/record_set'
3
+ end
@@ -0,0 +1,70 @@
1
+ # CloudFormation Docs AWS::Route53::RecordSet: https://amzn.to/2BtP9s5
2
+ #
3
+ # Example:
4
+ #
5
+ # DnsRecord:
6
+ # Type: AWS::Route53::RecordSet
7
+ # Properties:
8
+ # HostedZoneName: !Ref 'HostedZoneResource'
9
+ # Comment: DNS name for my instance.
10
+ # Name: !Join ['', [!Ref 'Ec2Instance', ., !Ref 'AWS::Region', ., !Ref 'HostedZone', .]]
11
+ # Type: A
12
+ # TTL: '900'
13
+ # ResourceRecords:
14
+ # - !GetAtt Ec2Instance.PublicIp
15
+ module Jets::Resource::Route53
16
+ class RecordSet < Jets::Resource::Base
17
+ def definition
18
+ {
19
+ dns_record: {
20
+ type: "AWS::Route53::RecordSet",
21
+ properties: {
22
+ hosted_zone_name: hosted_zone_name,
23
+ comment: "DNS record managed by Jets",
24
+ name: name,
25
+ type: "CNAME",
26
+ ttl: "60",
27
+ resource_records: [
28
+ cname,
29
+ ],
30
+ }
31
+ }
32
+ }
33
+ end
34
+
35
+ def cname
36
+ if endpoint_types.include?("REGIONAL")
37
+ "!GetAtt DomainName.RegionalDomainName"
38
+ else
39
+ "!GetAtt DomainName.DistributionDomainName"
40
+ end
41
+ end
42
+
43
+ def domain_name
44
+ Jets::Resource::ApiGateway::DomainName.new
45
+ end
46
+ memoize :domain_name
47
+
48
+ def endpoint_types
49
+ domain_name.endpoint_types
50
+ end
51
+
52
+ # IE: demo-dev.mydomain.com
53
+ def name
54
+ # Weird looking but correct: domain_name is object and domain_name is also method
55
+ domain_name.domain_name
56
+ end
57
+
58
+ # IE: mydomain.com
59
+ def hosted_zone_name
60
+ name = Jets.config.domain.hosted_zone_name
61
+ name.ends_with?('.') ? name : "#{name}." # add trailing period if missing
62
+ end
63
+
64
+ def outputs
65
+ {
66
+ "DnsRecord" => "!Ref DnsRecord",
67
+ }
68
+ end
69
+ end
70
+ end
@@ -26,6 +26,8 @@ module Jets
26
26
  post "#{name}", to: "#{name}#create"
27
27
  get "#{name}/:id/edit", to: "#{name}#edit" unless api_mode?
28
28
  put "#{name}/:id", to: "#{name}#update"
29
+ post "#{name}/:id", to: "#{name}#update" # for binary uploads
30
+ patch "#{name}/:id", to: "#{name}#update"
29
31
  delete "#{name}/:id", to: "#{name}#delete"
30
32
  end
31
33
 
@@ -17,7 +17,6 @@ module Jets
17
17
 
18
18
  def run
19
19
  Jets.boot(stringio: true) # outside of child process for COW
20
- Jets.eager_load!
21
20
 
22
21
  # INT - ^C
23
22
  trap('INT') do
@@ -94,9 +93,10 @@ module Jets
94
93
  loop do
95
94
  client = server.accept # Wait for a client to connect
96
95
 
97
- input_completed, event, handler = nil, nil, nil
96
+ input_completed, event, context, handler = nil, nil, nil
98
97
  unless input_completed
99
98
  event = client.gets&.strip # text or nil
99
+ context = client.gets&.strip # text or nil
100
100
  handler = client.gets&.strip # text or nil
101
101
  # The event is nil when a client connects and immediately disconnects without sending data
102
102
  if event.nil?
@@ -107,9 +107,12 @@ module Jets
107
107
  input_completed = true
108
108
  end
109
109
 
110
+ # puts "ruby_server.rb event: #{event.inspect}"
111
+ # puts "ruby_server.rb context: #{context.inspect}"
112
+
110
113
  result = event['_prewarm'] ?
111
114
  prewarm_request(event) :
112
- standard_request(event, '{}', handler)
115
+ standard_request(event, context, handler)
113
116
 
114
117
  Jets::IO.flush # flush output and write to disk for node shim
115
118
 
@@ -1,5 +1,3 @@
1
- require 'active_support/concern'
2
-
3
1
  module Jets
4
2
  class Stack
5
3
  autoload :Definition, 'jets/stack/definition' # Registration and definitions
@@ -75,7 +73,7 @@ module Jets
75
73
 
76
74
  def eager_load_shared_resources!
77
75
  ActiveSupport::Dependencies.autoload_paths += ["#{Jets.root}app/shared/resources"]
78
- Dir.glob("#{Jets.root}app/shared/resources/*.rb").select do |path|
76
+ Dir.glob("#{Jets.root}app/shared/resources/**/*.rb").select do |path|
79
77
  next if !File.file?(path) or path =~ %r{/javascript/} or path =~ %r{/views/}
80
78
 
81
79
  class_name = path