jets 1.1.5 → 1.2.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 (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