jets 2.3.17 → 3.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +0 -3
  3. data/.python-version +1 -1
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +44 -0
  6. data/LICENSE.txt +1 -1
  7. data/README.md +5 -3
  8. data/backers.md +2 -0
  9. data/jets.gemspec +12 -10
  10. data/lib/jets.rb +9 -13
  11. data/lib/jets/application.rb +9 -2
  12. data/lib/jets/application/defaults.rb +17 -15
  13. data/lib/jets/autoloaders.rb +15 -1
  14. data/lib/jets/booter.rb +3 -3
  15. data/lib/jets/builders/code_builder.rb +16 -15
  16. data/lib/jets/builders/gem_replacer.rb +15 -4
  17. data/lib/jets/builders/lambda_layer.rb +4 -5
  18. data/lib/jets/builders/ruby_packager.rb +40 -20
  19. data/lib/jets/builders/tidy.rb +1 -2
  20. data/lib/jets/bundle.rb +6 -0
  21. data/lib/jets/cfn/builders/api_gateway_builder.rb +61 -7
  22. data/lib/jets/cfn/ship.rb +2 -1
  23. data/lib/jets/cfn/upload.rb +18 -2
  24. data/lib/jets/cli.rb +6 -1
  25. data/lib/jets/commands/base.rb +1 -1
  26. data/lib/jets/commands/call.rb +14 -1
  27. data/lib/jets/commands/clean.rb +1 -1
  28. data/lib/jets/commands/clean/base.rb +1 -1
  29. data/lib/jets/commands/configure.rb +51 -0
  30. data/lib/jets/commands/delete.rb +2 -2
  31. data/lib/jets/commands/gems.rb +2 -10
  32. data/lib/jets/commands/help/call.md +8 -0
  33. data/lib/jets/commands/help/gems/check.md +3 -5
  34. data/lib/jets/commands/main.rb +9 -1
  35. data/lib/jets/commands/new.rb +9 -1
  36. data/lib/jets/commands/sequence.rb +6 -0
  37. data/lib/jets/commands/templates/skeleton/Gemfile.tt +1 -1
  38. data/lib/jets/commands/templates/skeleton/config/application.rb.tt +1 -1
  39. data/lib/jets/commands/templates/skeleton/public/index.html.tt +1 -1
  40. data/lib/jets/commands/url.rb +1 -0
  41. data/lib/jets/controller/base.rb +14 -4
  42. data/lib/jets/controller/middleware/main.rb +2 -1
  43. data/lib/jets/controller/params.rb +26 -4
  44. data/lib/jets/controller/rack/env.rb +18 -1
  45. data/lib/jets/controller/rendering.rb +5 -2
  46. data/lib/jets/controller/rendering/rack_renderer.rb +7 -1
  47. data/lib/jets/core.rb +12 -4
  48. data/lib/jets/dotenv/ssm.rb +18 -4
  49. data/lib/jets/generator.rb +2 -3
  50. data/lib/jets/internal/app/controllers/jets/public_controller.rb +2 -2
  51. data/lib/jets/internal/app/functions/jets/base_path.rb +10 -149
  52. data/lib/jets/internal/app/functions/jets/base_path_mapping.rb +81 -0
  53. data/lib/jets/internal/app/shared/functions/jets/s3_bucket_config.rb +14 -24
  54. data/lib/jets/resource/api_gateway/base_path/function.rb +6 -1
  55. data/lib/jets/resource/api_gateway/deployment.rb +2 -0
  56. data/lib/jets/resource/api_gateway/method.rb +1 -1
  57. data/lib/jets/resource/api_gateway/resource.rb +1 -1
  58. data/lib/jets/resource/api_gateway/rest_api/logical_id.rb +34 -0
  59. data/lib/jets/resource/api_gateway/rest_api/logical_id/message.rb +49 -0
  60. data/lib/jets/resource/child_stack/api_deployment.rb +2 -0
  61. data/lib/jets/resource/lambda/function.rb +1 -1
  62. data/lib/jets/router/dsl.rb +7 -1
  63. data/lib/jets/router/method_creator/code.rb +1 -1
  64. data/lib/jets/router/scope.rb +7 -3
  65. data/lib/jets/spec_helpers/controllers.rb +10 -3
  66. data/lib/jets/spec_helpers/controllers/request.rb +12 -5
  67. data/lib/jets/stack/main/dsl/lambda.rb +1 -1
  68. data/lib/jets/turbo.rb +1 -0
  69. data/lib/jets/version.rb +1 -1
  70. metadata +63 -56
  71. data/vendor/cfn-status/CHANGELOG.md +0 -14
  72. data/vendor/cfn-status/Gemfile +0 -4
  73. data/vendor/cfn-status/LICENSE.txt +0 -21
  74. data/vendor/cfn-status/README.md +0 -56
  75. data/vendor/cfn-status/Rakefile +0 -6
  76. data/vendor/cfn-status/bin/console +0 -14
  77. data/vendor/cfn-status/bin/setup +0 -8
  78. data/vendor/cfn-status/cfn-status.gemspec +0 -30
  79. data/vendor/cfn-status/lib/cfn-status.rb +0 -1
  80. data/vendor/cfn-status/lib/cfn_status.rb +0 -245
  81. data/vendor/cfn-status/lib/cfn_status/aws_service.rb +0 -51
  82. data/vendor/cfn-status/lib/cfn_status/version.rb +0 -3
  83. data/vendor/cfn-status/spec/fixtures/cfn/pages/fresh/describe_stack_events-1.json +0 -1103
  84. data/vendor/cfn-status/spec/fixtures/cfn/pages/fresh/describe_stack_events-2.json +0 -1104
  85. data/vendor/cfn-status/spec/fixtures/cfn/pages/fresh/describe_stack_events-3.json +0 -1103
  86. data/vendor/cfn-status/spec/fixtures/cfn/pages/updating/describe_stack_events-1.json +0 -1103
  87. data/vendor/cfn-status/spec/fixtures/cfn/pages/updating/describe_stack_events-2.json +0 -1104
  88. data/vendor/cfn-status/spec/fixtures/cfn/pages/updating/describe_stack_events-3.json +0 -1103
  89. data/vendor/cfn-status/spec/fixtures/cfn/stack-events-complete.json +0 -1080
  90. data/vendor/cfn-status/spec/fixtures/cfn/stack-events-in-progress.json +0 -1080
  91. data/vendor/cfn-status/spec/fixtures/cfn/stack-events-update-rollback-complete.json +0 -1086
  92. data/vendor/cfn-status/spec/lib/cfn_status_spec.rb +0 -153
  93. data/vendor/cfn-status/spec/spec_helper.rb +0 -14
@@ -89,9 +89,8 @@ class Jets::Generator
89
89
  g = Rails::Configuration::Generators.new
90
90
  g.orm :active_record, migration: true, timestamps: true
91
91
  # TODO: support g.orm :dynamodb
92
- g.test_framework false #:test_unit, fixture: false
93
- # g.test_framework :rspec # need to
94
- # TODO: load rspec configuration to use rspec
92
+ g.test_framework nil #:test_unit, fixture: false
93
+ # g.test_framework :rspec # TODO: load rspec configuration to use rspec
95
94
  g.stylesheets false
96
95
  g.javascripts false
97
96
  g.assets false
@@ -1,5 +1,5 @@
1
1
  require "rack/mime"
2
- require "mimemagic"
2
+ require "mini_mime"
3
3
 
4
4
  class Jets::PublicController < Jets::Controller::Base
5
5
  layout false
@@ -12,7 +12,7 @@ class Jets::PublicController < Jets::Controller::Base
12
12
 
13
13
  if File.exist?(catchall_path)
14
14
  content_type = Rack::Mime.mime_type(File.extname(catchall_path))
15
- binary = !MimeMagic.by_path(catchall_path).text?
15
+ binary = !MiniMime.lookup_by_filename(catchall_path).content_type.include?("text")
16
16
 
17
17
  # For binary support to work, the client also has to send the right Accept header.
18
18
  # And the media type has been to added to api gateway.
@@ -1,157 +1,18 @@
1
- require 'aws-sdk-apigateway'
2
- require 'aws-sdk-cloudformation'
1
+ require 'bundler/setup'
2
+ require 'cfn_response'
3
+ require 'jets/internal/app/functions/jets/base_path_mapping'
3
4
 
4
5
  STAGE_NAME = "<%= @stage_name %>"
5
6
 
6
7
  def lambda_handler(event:, context:)
7
- puts("event['RequestType'] #{event['RequestType']}")
8
- puts("event: #{JSON.dump(event)}")
9
- puts("context: #{JSON.dump(context)}")
10
- puts("context.log_stream_name #{context.log_stream_name.inspect}")
11
-
12
- mimic = event['ResourceProperties']['Mimic']
13
- physical_id = event['ResourceProperties']['PhysicalId'] || "PhysicalId"
14
-
15
- puts "mimic: #{mimic}"
16
- puts "physical_id: #{physical_id}"
17
-
18
- if event['RequestType'] == 'Delete'
19
- if mimic == 'FAILED'
20
- send_response(event, context, "FAILED")
21
- else
22
- mapping = BasePathMapping.new(event)
8
+ cfn = CfnResponse.new(event, context)
9
+ cfn.response do
10
+ mapping = BasePathMapping.new(event, STAGE_NAME)
11
+ case event['RequestType']
12
+ when "Create", "Update"
13
+ mapping.update
14
+ when "Delete"
23
15
  mapping.delete(true) if mapping.should_delete?
24
- send_response(event, context, "SUCCESS")
25
16
  end
26
- return # early return
27
- end
28
-
29
- mapping = BasePathMapping.new(event)
30
- mapping.update
31
-
32
- response_status = mimic == "FAILED" ? "FAILED" : "SUCCESS"
33
- response_data = { "Hello" => "World" }
34
-
35
- send_response(event, context, response_status, response_data, physical_id)
36
-
37
- # We rescue all exceptions and send an message to CloudFormation so we dont have to
38
- # wait for over an hour for the stack operation to timeout and rollback.
39
- rescue Exception => e
40
- puts e.message
41
- puts e.backtrace
42
- sleep 10 # provide delete to make sure that the log gets sent to CloudWatch
43
- send_response(event, context, "FAILED")
44
- end
45
-
46
- def send_response(event, context, response_status, response_data={}, physical_id="PhysicalId")
47
- response_body = JSON.dump(
48
- Status: response_status,
49
- Reason: "See the details in CloudWatch Log Stream: #{context.log_stream_name.inspect}",
50
- PhysicalResourceId: physical_id,
51
- StackId: event['StackId'],
52
- RequestId: event['RequestId'],
53
- LogicalResourceId: event['LogicalResourceId'],
54
- Data: response_data
55
- )
56
-
57
- puts "RESPONSE BODY:\n"
58
- puts response_body
59
-
60
- url = event['ResponseURL']
61
- uri = URI(url)
62
- http = Net::HTTP.new(uri.host, uri.port)
63
- http.open_timeout = http.read_timeout = 30
64
- http.use_ssl = true if uri.scheme == 'https'
65
-
66
-
67
- # must used url to include the AWSAccessKeyId and Signature
68
- req = Net::HTTP::Put.new(url) # url includes query string and uri.path does not, must used url t
69
- req.body = response_body
70
- req.content_length = response_body.bytesize
71
-
72
- # set headers
73
- req['content-type'] = ''
74
- req['content-length'] = response_body.bytesize
75
-
76
- res = http.request(req)
77
- puts "status code: #{res.code}"
78
- puts "headers: #{res.each_header.to_h.inspect}"
79
- puts "body: #{res.body}"
80
- end
81
-
82
-
83
- class BasePathMapping
84
- def initialize(event)
85
- @event = event
86
- @rest_api_id = get_rest_api_id
87
- @domain_name = get_domain_name
88
- @base_path = ''
89
- end
90
-
91
- def update
92
- # Cannot use update_base_path_mapping to update the base_mapping because it doesnt
93
- # allow us to change the rest_api_id. So we delete and create.
94
- delete(true)
95
- create
96
- end
97
-
98
- # Dont delete the newly created base path mapping unless this is an operation
99
- # where we're fully deleting the stack
100
- def should_delete?
101
- deleting_parent?
102
- end
103
-
104
- def delete(fail_silently=false)
105
- apigateway.delete_base_path_mapping(
106
- domain_name: @domain_name, # required
107
- base_path: '(none)',
108
- )
109
- # https://github.com/tongueroo/jets/issues/255
110
- # Used to return: Aws::APIGateway::Errors::NotFoundException
111
- # Now returns: Aws::APIGateway::Errors::InternalFailure
112
- # So we'll use a more generic error
113
- rescue Aws::APIGateway::Errors::ServiceError => e
114
- raise(e) unless fail_silently
115
- end
116
-
117
- def create
118
- apigateway.create_base_path_mapping(
119
- domain_name: @domain_name, # required
120
- base_path: @base_path,
121
- rest_api_id: @rest_api_id, # required
122
- stage: STAGE_NAME,
123
- )
124
- end
125
-
126
- def get_domain_name
127
- param = deployment_stack[:parameters].find { |p| p.parameter_key == 'DomainName' }
128
- param.parameter_value
129
- end
130
-
131
- def deployment_stack
132
- @deployment_stack ||= cfn.describe_stacks(stack_name: @event['StackId']).stacks.first
133
- end
134
-
135
- def get_rest_api_id
136
- param = deployment_stack[:parameters].find { |p| p.parameter_key == 'RestApi' }
137
- param.parameter_value
138
- end
139
-
140
- def deleting_parent?
141
- stack = cfn.describe_stacks(stack_name: parent_stack_name).stacks.first
142
- stack.stack_status == 'DELETE_IN_PROGRESS'
143
- end
144
-
145
- def parent_stack_name
146
- deployment_stack[:root_id]
147
- end
148
-
149
- private
150
- def apigateway
151
- @apigateway ||= Aws::APIGateway::Client.new
152
- end
153
-
154
- def cfn
155
- @cfn ||= Aws::CloudFormation::Client.new
156
17
  end
157
18
  end
@@ -0,0 +1,81 @@
1
+ require 'aws-sdk-apigateway'
2
+ require 'aws-sdk-cloudformation'
3
+
4
+ class BasePathMapping
5
+ def initialize(event, stage_name)
6
+ @event, @stage_name = event, stage_name
7
+ end
8
+
9
+ # Cannot use update_base_path_mapping to update the base_mapping because it doesnt
10
+ # allow us to change the rest_api_id. So we delete and create.
11
+ def update
12
+ delete(true)
13
+ create
14
+ end
15
+
16
+ # Dont delete the newly created base path mapping unless this is an operation
17
+ # where we're fully deleting the stack
18
+ def should_delete?
19
+ deleting_parent?
20
+ end
21
+
22
+ def delete(fail_silently=false)
23
+ apigateway.delete_base_path_mapping(
24
+ domain_name: domain_name, # required
25
+ base_path: base_path.empty? ? '(none)' : base_path,
26
+ )
27
+ # https://github.com/tongueroo/jets/issues/255
28
+ # Used to return: Aws::APIGateway::Errors::NotFoundException
29
+ # Now returns: Aws::APIGateway::Errors::InternalFailure
30
+ # So we'll use a more generic error
31
+ rescue Aws::APIGateway::Errors::ServiceError => e
32
+ raise(e) unless fail_silently
33
+ end
34
+
35
+ def create
36
+ apigateway.create_base_path_mapping(
37
+ domain_name: domain_name, # required
38
+ base_path: base_path,
39
+ rest_api_id: rest_api_id, # required
40
+ stage: @stage_name,
41
+ )
42
+ end
43
+
44
+ def deployment_stack
45
+ @deployment_stack ||= cfn.describe_stacks(stack_name: @event['StackId']).stacks.first
46
+ end
47
+
48
+ def rest_api_id
49
+ @rest_api_id ||= parameter_value('RestApi')
50
+ end
51
+
52
+ def domain_name
53
+ @domain_name ||= parameter_value('DomainName')
54
+ end
55
+
56
+ def base_path
57
+ @base_path ||= parameter_value('BasePath') || ''
58
+ end
59
+
60
+ def parameter_value(parameter_key)
61
+ param = deployment_stack[:parameters].find { |p| p.parameter_key == parameter_key }
62
+ param&.parameter_value # possible for this to be nil when removing the config: IE: config.domain.name = nil
63
+ end
64
+
65
+ def deleting_parent?
66
+ stack = cfn.describe_stacks(stack_name: parent_stack_name).stacks.first
67
+ stack.stack_status == 'DELETE_IN_PROGRESS'
68
+ end
69
+
70
+ def parent_stack_name
71
+ deployment_stack[:root_id]
72
+ end
73
+
74
+ def apigateway
75
+ @apigateway ||= Aws::APIGateway::Client.new
76
+ end
77
+
78
+ def cfn
79
+ @cfn ||= Aws::CloudFormation::Client.new
80
+ end
81
+ end
@@ -1,33 +1,23 @@
1
- require "active_support/all"
2
- require "aws-sdk-s3"
3
- require "cfnresponse"
4
- include Cfnresponse
1
+ require 'bundler/setup'
2
+ require 'active_support/core_ext/hash'
3
+ require 'cfn_response'
5
4
 
6
5
  def lambda_handler(event:, context:)
7
- # Print out debugging info immediately just in case
8
- puts "event: #{json_pretty(event)}"
9
- puts "context: #{json_pretty(context)}"
10
-
11
- if %w[Create Update].include?(event['RequestType'])
12
- properties = event["ResourceProperties"].dup
13
- # After deleting ServiceToken, the rest of the values is the bucket configuration properties.
14
- properties.delete("ServiceToken")
15
- configurator = BucketConfigurator.new
16
- configurator.put(properties)
6
+ cfn = CfnResponse.new(event, context)
7
+ cfn.response do
8
+ case event['RequestType']
9
+ when "Create", "Update"
10
+ properties = event["ResourceProperties"].dup
11
+ # After deleting ServiceToken, the rest of the values are the bucket configuration properties.
12
+ properties.delete("ServiceToken")
13
+ configurator = BucketConfigurator.new
14
+ configurator.put(properties)
15
+ end
17
16
  end
18
-
19
- send_response(event, context, "SUCCESS")
20
-
21
- # We rescue all exceptions and send an message to CloudFormation so we dont have to
22
- # wait for over an hour for the stack operation to timeout and rollback.
23
- rescue Exception => e
24
- puts e.message
25
- puts e.backtrace
26
- sleep 10 # a little time for logs to be sent to CloudWatch
27
- send_response(event, context, "FAILED")
28
17
  end
29
18
 
30
19
  ########################################################
20
+ require "aws-sdk-s3"
31
21
 
32
22
  class BucketConfigurator
33
23
  def put(props={})
@@ -14,15 +14,20 @@ module Jets::Resource::ApiGateway::BasePath
14
14
  },
15
15
  role: "!GetAtt BasePathRole.Arn",
16
16
  handler: handler,
17
- runtime: "ruby2.5",
17
+ runtime: Jets.ruby_runtime,
18
18
  timeout: 60,
19
19
  memory_size: 1536,
20
20
  environment: env_properties[:environment],
21
+ layers: layers,
21
22
  }
22
23
  }
23
24
  }
24
25
  end
25
26
 
27
+ def layers
28
+ ["!Ref GemLayer"]
29
+ end
30
+
26
31
  def function_name
27
32
  method = "jets-base-path"
28
33
  # need to add the deployment timestamp because or else function name collides between deploys
@@ -15,11 +15,13 @@ module Jets::Resource::ApiGateway
15
15
 
16
16
  def parameters
17
17
  p = {
18
+ "GemLayer" => "GemLayer",
18
19
  "IamRole" => "IamRole",
19
20
  "RestApi" => "RestApi",
20
21
  "S3Bucket" => "S3Bucket",
21
22
  }
22
23
  p[:DomainName] = "DomainName" if Jets.custom_domain?
24
+ p[:BasePath] = "BasePath" unless Jets.config.domain.base_path.nil?
23
25
  p
24
26
  end
25
27
 
@@ -96,7 +96,7 @@ module Jets::Resource::ApiGateway
96
96
  def camelized_path
97
97
  path = @route.path
98
98
  path = "homepage" if path == ''
99
- path.gsub('/','_').gsub(':','').gsub('*','').camelize
99
+ path.gsub('/','_').gsub(':','').gsub('*','').gsub('.','').camelize
100
100
  end
101
101
  end
102
102
  end
@@ -76,7 +76,7 @@ module Jets::Resource::ApiGateway
76
76
  private
77
77
  # Similar path_logical_id method in resource/route.rb
78
78
  def path_logical_id(path)
79
- path.gsub('/','_').gsub(':','').gsub('*','').gsub('-','_').camelize
79
+ path.gsub('/','_').gsub(':','').gsub('*','').gsub('-','_').gsub('.','_').camelize
80
80
  end
81
81
  end
82
82
  end
@@ -8,12 +8,46 @@ class Jets::Resource::ApiGateway::RestApi
8
8
  return default unless stack_exists?(parent_stack_name) && api_gateway_exists?
9
9
 
10
10
  if changed?
11
+ auto_replace_prompt
11
12
  new_id
12
13
  else
13
14
  current
14
15
  end
15
16
  end
16
17
 
18
+ def auto_replace_prompt
19
+ return if ENV['JETS_API_AUTO_REPLACE']
20
+ return unless ARGV[0] == "deploy"
21
+ case Jets.config.api.auto_replace
22
+ when nil
23
+ puts message.routes_changed
24
+ puts message.custom_domain
25
+ print "Would you like to continue the deployment? (y/N) "
26
+ answer = get_answer
27
+ exit 1 unless answer =~ /^y/
28
+ when false
29
+ puts message.routes_changed
30
+ puts message.auto_replace_disabled
31
+ exit 1
32
+ end
33
+ end
34
+
35
+ def message
36
+ Message.new
37
+ end
38
+ memoize :message
39
+
40
+ TIMEOUT_PERIOD = 120
41
+ def get_answer
42
+ Timeout::timeout(TIMEOUT_PERIOD) do
43
+ $stdin.gets
44
+ end
45
+ rescue Timeout::Error => e
46
+ puts "#{e.class}: #{e.message}".color(:red)
47
+ puts "Deployment timeout after #{TIMEOUT_PERIOD}s. Waited too long answer. Exiting."
48
+ exit 1
49
+ end
50
+
17
51
  def changed?
18
52
  change_detection = ChangeDetection.new
19
53
  change_detection.changed?
@@ -0,0 +1,49 @@
1
+ class Jets::Resource::ApiGateway::RestApi::LogicalId
2
+ class Message
3
+ def routes_changed
4
+ <<~EOL
5
+ Routes Change Detection: Jets has detected that a new brand API Gateway is required to be deployed.
6
+ IMPORTANT: This will result in the API Gateway endpoint changing.
7
+ EOL
8
+ end
9
+
10
+ def custom_domain
11
+ api = Jets::Resource::ApiGateway::DomainName.new
12
+ domain_name = api.domain_name
13
+ if domain_name
14
+ <<~EOL
15
+ It looks like you have already set up a custom domain.
16
+ The domain name: #{domain_name}
17
+
18
+ So you should be good to go as the custom domain will be updated with the new API Gateway endpoint.
19
+ To avoid this prompt in the future, you can configure:
20
+
21
+ config/application.rb
22
+
23
+ config.api.auto_replace = true
24
+
25
+ More info: custom domain docs: https://rubyonjets.com/docs/routing/custom-domain/
26
+ EOL
27
+ else
28
+ "Please set up a custom domain https://rubyonjets.com/docs/routing/custom-domain/"
29
+ end
30
+ end
31
+
32
+ def auto_replace_disabled
33
+ <<~EOL
34
+ It looks like `config.api.auto_replace = false`. IE:
35
+
36
+ config/application.rb
37
+
38
+ config.api.auto_replace = false
39
+
40
+ The deploy will not continue. See:
41
+
42
+ * https://rubyonjets.com/docs/app-config/reference/
43
+ * https://rubyonjets.com/docs/routing/custom-domain/
44
+
45
+ EOL
46
+ end
47
+ end
48
+ end
49
+