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,21 @@
1
+ class Jets::Mega::Request
2
+ class Source
3
+ def initialize(event)
4
+ @event = event
5
+ env = Jets::Controller::Rack::Env.new(@event, {}).convert # convert to Rack env
6
+ @source_request = Rack::Request.new(env)
7
+ end
8
+
9
+ def body
10
+ if @source_request.body.respond_to?(:read)
11
+ body = @source_request.body.read
12
+ @source_request.body.rewind
13
+ end
14
+ body
15
+ end
16
+
17
+ def content_length
18
+ @source_request.content_length.to_i
19
+ end
20
+ end
21
+ end
@@ -1,6 +1,6 @@
1
1
  # Based on Rails MiddlewareStackProxy
2
2
  #
3
- # Configurator is a proxy for the Jets middleware stack that allows
3
+ # Configurator is a recorder for the Jets middleware stack that allows
4
4
  # you to configure middlewares in your application. It works basically as a
5
5
  # command recorder, saving each command to be applied after initialization
6
6
  # over the default middleware stack, so you can add, swap, or remove any
@@ -8,9 +8,9 @@ module Jets::Middleware
8
8
 
9
9
  def build_stack
10
10
  Stack.new do |middleware|
11
- middleware.use Jets::Controller::Middleware::Local # mimics AWS Lambda for local server only
12
11
  middleware.use Rack::Runtime
13
- middleware.use Rack::MethodOverride
12
+ middleware.use Rack::MethodOverride # must come before Middleware::Local for multipart post forms to work
13
+ middleware.use Jets::Controller::Middleware::Local # mimics AWS Lambda for local server only
14
14
  middleware.use session_store, session_options # use session_store, session_options
15
15
  middleware.use Rack::Head
16
16
  middleware.use Rack::ConditionalGet
@@ -11,6 +11,7 @@ class Jets::Resource
11
11
  autoload :Iam, 'jets/resource/iam'
12
12
  autoload :Permission, 'jets/resource/permission'
13
13
  autoload :Replacer, 'jets/resource/replacer'
14
+ autoload :Route53, 'jets/resource/route53'
14
15
  autoload :S3, 'jets/resource/s3'
15
16
  autoload :Sns, 'jets/resource/sns'
16
17
  autoload :Standardizer, 'jets/resource/standardizer'
@@ -1,7 +1,9 @@
1
1
  module Jets::Resource::ApiGateway
2
- autoload :Resource, 'jets/resource/api_gateway/resource'
3
- autoload :RestApi, 'jets/resource/api_gateway/rest_api'
2
+ autoload :BasePath, 'jets/resource/api_gateway/base_path'
3
+ autoload :Cors, 'jets/resource/api_gateway/cors'
4
4
  autoload :Deployment, 'jets/resource/api_gateway/deployment'
5
+ autoload :DomainName, 'jets/resource/api_gateway/domain_name'
5
6
  autoload :Method, 'jets/resource/api_gateway/method'
6
- autoload :Cors, 'jets/resource/api_gateway/cors'
7
+ autoload :Resource, 'jets/resource/api_gateway/resource'
8
+ autoload :RestApi, 'jets/resource/api_gateway/rest_api'
7
9
  end
@@ -0,0 +1,5 @@
1
+ module Jets::Resource::ApiGateway::BasePath
2
+ autoload :Function, 'jets/resource/api_gateway/base_path/function'
3
+ autoload :Mapping, 'jets/resource/api_gateway/base_path/mapping'
4
+ autoload :Role, 'jets/resource/api_gateway/base_path/role'
5
+ end
@@ -0,0 +1,42 @@
1
+ module Jets::Resource::ApiGateway::BasePath
2
+ class Function < Jets::Resource::Base
3
+ include Jets::Resource::Function::Environment
4
+
5
+ def definition
6
+ {
7
+ base_path_function: {
8
+ type: "AWS::Lambda::Function",
9
+ properties: {
10
+ function_name: function_name,
11
+ code: {
12
+ s3_bucket: "!Ref S3Bucket",
13
+ s3_key: code_s3_key,
14
+ },
15
+ role: "!GetAtt BasePathRole.Arn",
16
+ handler: handler,
17
+ runtime: "ruby2.5",
18
+ timeout: 60,
19
+ memory_size: 1536,
20
+ environment: env_properties[:environment],
21
+ }
22
+ }
23
+ }
24
+ end
25
+
26
+ def function_name
27
+ method = "jets-base-path"
28
+ # need to add the deployment timestamp because or else function name collides between deploys
29
+ timestamp = Jets::Resource::ApiGateway::Deployment.timestamp
30
+ "#{Jets.config.project_namespace}-#{method}-#{timestamp}"
31
+ end
32
+
33
+ def handler
34
+ "handlers/functions/jets/base_path.lambda_handler"
35
+ end
36
+
37
+ def code_s3_key
38
+ checksum = Jets::Builders::Md5.checksums["stage/code"]
39
+ "jets/code/code-#{checksum}.zip" # s3_key
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,44 @@
1
+ # CloudFormation Docs AWS::ApiGateway::DomainName: https://amzn.to/2BsrSqo
2
+ #
3
+ # Example:
4
+ #
5
+ # Type: AWS::ApiGateway::BasePathMapping
6
+ # Properties:
7
+ # BasePath: String
8
+ # DomainName: String
9
+ # RestApiId: String
10
+ # Stage: String
11
+ #
12
+ # Currently unable to add base path mapping in-place with CloudFormation.
13
+ # The workaround for this is to do it post deployment with raw API calls outside
14
+ # of CloudFormation. Leaving this around for now in case there's a workaround
15
+ # to get this into CloudFormation instead of raw API calls. Some notes:
16
+ # * Also tried to change the domain name of to something like demo-dev-[random].mydomain.com
17
+ # That does not work because the domain name has to match the route53 record exactly.
18
+ #
19
+ module Jets::Resource::ApiGateway::BasePath
20
+ class Mapping < Jets::Resource::Base
21
+ def definition
22
+ function_logical_id = "BasePathFunction" # lambda function that supports custom resource
23
+ {
24
+ base_path_mapping: {
25
+ type: "Custom::BasePathMapping",
26
+ properties: {
27
+ service_token: "!GetAtt #{function_logical_id}.Arn",
28
+ # base_path: '', # empty path represents root
29
+ # domain_name: "!Ref DomainName",
30
+ # rest_api_id: "!Ref RestApi", # since this is in the Deployment template
31
+ # stage: Deployment.stage_name,
32
+ },
33
+ depends_on: Jets::Resource::ApiGateway::Deployment.logical_id,
34
+ }
35
+ }
36
+ end
37
+
38
+ def outputs
39
+ {
40
+ "BasePathMapping" => "!Ref BasePathMapping",
41
+ }
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,76 @@
1
+ module Jets::Resource::ApiGateway::BasePath
2
+ class Role < Jets::Resource::Base
3
+ extend Memoist
4
+ include Jets::AwsServices
5
+
6
+ def definition
7
+ {
8
+ base_path_role: {
9
+ type: "AWS::IAM::Role",
10
+ properties: {
11
+ role_name: role_name,
12
+ path: "/",
13
+ assume_role_policy_document: {
14
+ version: "2012-10-17",
15
+ statement: [{
16
+ effect: "Allow",
17
+ principal: {service: ["lambda.amazonaws.com"]},
18
+ action: ["sts:AssumeRole"]}
19
+ ]
20
+ },
21
+ policies: [
22
+ policy_name: "#{role_name}-policy",
23
+ policy_document: policy_document,
24
+ ]
25
+ },
26
+ }
27
+ }
28
+ end
29
+
30
+ def policy_document
31
+ project_namespace = Jets.config.project_namespace
32
+ default_policy_statements = Jets::Application.default_iam_policy # Array of Hashes
33
+ apigateway = [{
34
+ action: [ "apigateway:*" ],
35
+ effect: "Allow",
36
+ resource: "arn:aws:apigateway:#{Jets.aws.region}::/restapis/*", # scoped to all restapis because this changes
37
+ },{
38
+ action: [ "apigateway:*" ],
39
+ effect: "Allow",
40
+ resource: "arn:aws:apigateway:#{Jets.aws.region}::/domainnames/*", # scoped to all restapis because this changes
41
+ }]
42
+ cloudformation = [{
43
+ action: ["cloudformation:DescribeStacks"],
44
+ effect: "Allow",
45
+ resource: "arn:aws:cloudformation:#{Jets.aws.region}:#{Jets.aws.account}:stack/#{project_namespace}*",
46
+ }]
47
+
48
+ # Combine the statements
49
+ {
50
+ version: '2012-10-17',
51
+ statement: default_policy_statements + apigateway + cloudformation
52
+ }
53
+ end
54
+
55
+ # Duplicated in rest_api/change_detection.rb, base_path/role.rb, rest_api/routes.rb
56
+ def rest_api_id
57
+ stack_name = Jets::Naming.parent_stack_name
58
+ return default unless stack_exists?(stack_name)
59
+
60
+ stack = cfn.describe_stacks(stack_name: stack_name).stacks.first
61
+
62
+ api_gateway_stack_arn = lookup(stack[:outputs], "ApiGateway")
63
+
64
+ # resources = cfn.describe_stack_resources(stack_name: api_gateway_stack_arn).stack_resources
65
+ stack = cfn.describe_stacks(stack_name: api_gateway_stack_arn).stacks.first
66
+ rest_api_id = lookup(stack[:outputs], "RestApi")
67
+ end
68
+ memoize :rest_api_id
69
+
70
+ def role_name
71
+ # TODO: dont think we should change the role name every time but have to right now because the deployment logical id changes
72
+ timestamp = Jets::Resource::ApiGateway::Deployment.timestamp
73
+ "#{Jets.config.project_namespace}-base-path-mapping-#{timestamp}"
74
+ end
75
+ end
76
+ end
@@ -9,7 +9,7 @@ module Jets::Resource::ApiGateway
9
9
 
10
10
  properties: {
11
11
  resource_id: "!Ref #{resource_id}",
12
- rest_api_id: "!Ref RestApi",
12
+ rest_api_id: "!Ref #{RestApi.logical_id}",
13
13
  authorization_type: authorization_type,
14
14
  http_method: "OPTIONS",
15
15
  method_responses: [{
@@ -6,23 +6,27 @@ module Jets::Resource::ApiGateway
6
6
  type: "AWS::ApiGateway::Deployment",
7
7
  properties: {
8
8
  description: "Version #{timestamp} deployed by jets",
9
- rest_api_id: "!Ref RestApi",
9
+ rest_api_id: "!Ref #{RestApi.logical_id}",
10
10
  stage_name: stage_name,
11
11
  }
12
12
  }
13
13
  }
14
14
  end
15
15
 
16
- # value is Description
17
16
  def parameters
18
- {
17
+ p = {
18
+ "IamRole" => "IamRole",
19
19
  "RestApi" => "RestApi",
20
+ "S3Bucket" => "S3Bucket",
20
21
  }
22
+ p[:DomainName] = "DomainName" if Jets.custom_domain?
23
+ p
21
24
  end
22
25
 
23
- def outputs
26
+ def outputs(internal=false)
27
+ rest_api = internal ? RestApi.internal_logical_id : "RestApi"
24
28
  {
25
- "RestApiUrl" => "!Sub 'https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/#{stage_name}/'",
29
+ "RestApiUrl" => "!Sub 'https://${#{rest_api}}.execute-api.${AWS::Region}.amazonaws.com/#{stage_name}/'",
26
30
  }
27
31
  end
28
32
 
@@ -0,0 +1,56 @@
1
+ # CloudFormation Docs AWS::ApiGateway::DomainName: https://amzn.to/2Bsnmbq
2
+ #
3
+ # Example:
4
+ #
5
+ # MyDomainName:
6
+ # Type: 'AWS::ApiGateway::DomainName'
7
+ # Properties:
8
+ # DomainName: api.mydomain.com
9
+ # CertificateArn: arn:aws:acm:us-east-1:111122223333:certificate/fb1b9770-a305-495d-aefb-27e5e101ff3
10
+ #
11
+ module Jets::Resource::ApiGateway
12
+ class DomainName < Jets::Resource::Base
13
+ def definition
14
+ properties = {
15
+ domain_name: domain_name,
16
+ endpoint_configuration: {
17
+ types: endpoint_types
18
+ }
19
+ }
20
+ # Can really only be REGIONAL or EDGE
21
+ if endpoint_types.include?("REGIONAL")
22
+ properties[:regional_certificate_arn] = cert_arn
23
+ end
24
+ if endpoint_types.include?("EDGE")
25
+ properties[:certificate_arn] = cert_arn
26
+ end
27
+
28
+ {
29
+ domain_name: {
30
+ type: "AWS::ApiGateway::DomainName",
31
+ properties: properties
32
+ }
33
+ }
34
+ end
35
+
36
+ def outputs
37
+ {
38
+ "DomainName" => "!Ref DomainName",
39
+ }
40
+ end
41
+
42
+ def domain_name
43
+ subdomain = Jets.project_namespace
44
+ managed_domain_name = "#{subdomain}.#{Jets.config.domain.hosted_zone_name}"
45
+ Jets.config.domain.name || managed_domain_name
46
+ end
47
+
48
+ def endpoint_types
49
+ [Jets.config.domain.endpoint_type].flatten
50
+ end
51
+
52
+ def cert_arn
53
+ Jets.config.domain.cert_arn
54
+ end
55
+ end
56
+ end
@@ -1,5 +1,3 @@
1
- require "active_support/core_ext/object"
2
-
3
1
  # Converts a Jets::Route to a CloudFormation Jets::Resource::ApiGateway::Method resource
4
2
  module Jets::Resource::ApiGateway
5
3
  class Method < Jets::Resource::Base
@@ -18,7 +16,7 @@ module Jets::Resource::ApiGateway
18
16
  type: "AWS::ApiGateway::Method",
19
17
  properties: {
20
18
  resource_id: "!Ref #{resource_id}",
21
- rest_api_id: "!Ref RestApi",
19
+ rest_api_id: "!Ref #{RestApi.logical_id}",
22
20
  http_method: @route.method,
23
21
  request_parameters: {},
24
22
  authorization_type: authorization_type,
@@ -47,7 +45,8 @@ module Jets::Resource::ApiGateway
47
45
  .gsub(/[^0-9a-z]/i, ' ')
48
46
  .gsub(/\s+/, '_')
49
47
  path = nil if path == ''
50
- [path, "{namespace}_api_method"].compact.join('_')
48
+ http_verb = @route.method.downcase
49
+ [path, "{namespace}_#{http_verb}_api_method"].compact.join('_')
51
50
  end
52
51
 
53
52
  def replacements
@@ -1,7 +1,8 @@
1
1
  module Jets::Resource::ApiGateway
2
2
  class Resource < Jets::Resource::Base
3
- def initialize(path)
3
+ def initialize(path, internal: false)
4
4
  @path = path # Examples: "posts/:id/edit" or "posts"
5
+ @internal = internal
5
6
  end
6
7
 
7
8
  def definition
@@ -11,7 +12,7 @@ module Jets::Resource::ApiGateway
11
12
  properties: {
12
13
  parent_id: parent_id,
13
14
  path_part: path_part,
14
- rest_api_id: "!Ref RestApi",
15
+ rest_api_id: "!Ref #{RestApi.logical_id(@internal)}",
15
16
  }
16
17
  }
17
18
  }
@@ -42,7 +43,7 @@ module Jets::Resource::ApiGateway
42
43
  parent_logical_id = path_logical_id(parent_path)
43
44
  "!Ref #{parent_logical_id}ApiResource"
44
45
  else
45
- "!GetAtt RestApi.RootResourceId"
46
+ "!GetAtt #{RestApi.logical_id(@internal)}.RootResourceId"
46
47
  end
47
48
  end
48
49
 
@@ -1,32 +1,60 @@
1
1
  module Jets::Resource::ApiGateway
2
2
  class RestApi < Jets::Resource::Base
3
+ autoload :ChangeDetection, 'jets/resource/api_gateway/rest_api/change_detection'
4
+ autoload :LogicalId, 'jets/resource/api_gateway/rest_api/logical_id'
5
+ autoload :Routes, 'jets/resource/api_gateway/rest_api/routes'
3
6
 
4
7
  def definition
8
+ properties = {
9
+ name: Jets::Naming.gateway_api_name,
10
+ endpoint_configuration: { types: endpoint_types }
11
+ }
12
+ properties[:binary_media_types] = binary_media_types if binary_media_types
13
+
5
14
  {
6
- rest_api: {
15
+ internal_logical_id => {
7
16
  type: "AWS::ApiGateway::RestApi",
8
- properties: {
9
- name: Jets::Naming.gateway_api_name,
10
- endpoint_configuration: {
11
- types: types
12
- }
13
- # binary_media_types: ['*/*'], # TODO: comment out, breaking form post
14
- }
17
+ properties: properties
15
18
  }
16
19
  }
17
20
  end
18
-
21
+
22
+ def internal_logical_id
23
+ self.class.logical_id(true)
24
+ end
25
+
26
+ def self.logical_id(internal=false)
27
+ internal ? internal_logical_id : "RestApi"
28
+ end
29
+
30
+ @@internal_logical_id = nil
31
+ def self.internal_logical_id
32
+ @@internal_logical_id ||= LogicalId.new.get
33
+ end
34
+
19
35
  def outputs
20
36
  {
21
- "RestApi" => "!Ref RestApi",
37
+ "RestApi" => "!Ref #{internal_logical_id}",
22
38
  "Region" => "!Ref AWS::Region",
23
- "RootResourceId" => "!GetAtt RestApi.RootResourceId",
39
+ "RootResourceId" => "!GetAtt #{internal_logical_id}.RootResourceId",
24
40
  }
25
41
  end
26
42
 
27
- def types
28
- endpoint_type = Jets.config.api.endpoint_type || 'EDGE'
29
- [endpoint_type.upcase]
43
+ def endpoint_types
44
+ [Jets.config.api.endpoint_type].flatten
45
+ end
46
+
47
+ # TODO: Looks like there's a bug with CloudFormation. On an API Gateway update
48
+ # we need to pass in the escaped version: multipart~1form-data
49
+ # On a brand new API Gateway creation, we need to pass in the unescaped form:
50
+ # multipart/form-data
51
+ # We are handling this with a full API Gateway replacement instead because it
52
+ # can be generalized more easily.
53
+ def binary_media_types
54
+ types = Jets.config.api.binary_media_types
55
+ return nil if types.nil? || types.empty?
56
+
57
+ [types].flatten
30
58
  end
31
59
  end
32
60
  end