jets 2.3.19 → 3.0.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 (88) 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 +28 -2
  6. data/README.md +2 -2
  7. data/backers.md +1 -0
  8. data/jets.gemspec +11 -10
  9. data/lib/jets.rb +9 -13
  10. data/lib/jets/application.rb +9 -2
  11. data/lib/jets/application/defaults.rb +17 -15
  12. data/lib/jets/autoloaders.rb +15 -1
  13. data/lib/jets/booter.rb +3 -3
  14. data/lib/jets/builders/code_builder.rb +16 -15
  15. data/lib/jets/builders/gem_replacer.rb +3 -16
  16. data/lib/jets/builders/lambda_layer.rb +4 -5
  17. data/lib/jets/builders/ruby_packager.rb +18 -42
  18. data/lib/jets/builders/tidy.rb +1 -2
  19. data/lib/jets/bundle.rb +6 -0
  20. data/lib/jets/cfn/builders/api_gateway_builder.rb +61 -7
  21. data/lib/jets/cfn/ship.rb +2 -1
  22. data/lib/jets/cli.rb +6 -1
  23. data/lib/jets/commands/base.rb +1 -1
  24. data/lib/jets/commands/call.rb +14 -1
  25. data/lib/jets/commands/clean.rb +1 -1
  26. data/lib/jets/commands/clean/base.rb +1 -1
  27. data/lib/jets/commands/configure.rb +51 -0
  28. data/lib/jets/commands/delete.rb +2 -2
  29. data/lib/jets/commands/gems.rb +2 -10
  30. data/lib/jets/commands/help/call.md +8 -0
  31. data/lib/jets/commands/help/gems/check.md +3 -5
  32. data/lib/jets/commands/main.rb +9 -1
  33. data/lib/jets/commands/new.rb +9 -1
  34. data/lib/jets/commands/sequence.rb +6 -0
  35. data/lib/jets/commands/templates/skeleton/Gemfile.tt +1 -1
  36. data/lib/jets/commands/templates/skeleton/config/application.rb.tt +1 -1
  37. data/lib/jets/commands/templates/skeleton/public/index.html.tt +1 -1
  38. data/lib/jets/commands/url.rb +1 -0
  39. data/lib/jets/controller/base.rb +14 -4
  40. data/lib/jets/controller/middleware/main.rb +2 -1
  41. data/lib/jets/controller/params.rb +26 -4
  42. data/lib/jets/controller/rack/env.rb +18 -1
  43. data/lib/jets/controller/rendering.rb +5 -2
  44. data/lib/jets/controller/rendering/rack_renderer.rb +7 -1
  45. data/lib/jets/core.rb +12 -4
  46. data/lib/jets/dotenv/ssm.rb +18 -4
  47. data/lib/jets/generator.rb +2 -3
  48. data/lib/jets/internal/app/functions/jets/base_path.rb +10 -149
  49. data/lib/jets/internal/app/functions/jets/base_path_mapping.rb +81 -0
  50. data/lib/jets/internal/app/shared/functions/jets/s3_bucket_config.rb +14 -24
  51. data/lib/jets/resource/api_gateway/base_path/function.rb +6 -1
  52. data/lib/jets/resource/api_gateway/deployment.rb +2 -0
  53. data/lib/jets/resource/api_gateway/rest_api/logical_id.rb +34 -0
  54. data/lib/jets/resource/api_gateway/rest_api/logical_id/message.rb +49 -0
  55. data/lib/jets/resource/child_stack/api_deployment.rb +2 -0
  56. data/lib/jets/resource/lambda/function.rb +1 -1
  57. data/lib/jets/router/dsl.rb +7 -1
  58. data/lib/jets/router/method_creator/code.rb +1 -1
  59. data/lib/jets/router/scope.rb +7 -3
  60. data/lib/jets/spec_helpers/controllers.rb +10 -3
  61. data/lib/jets/spec_helpers/controllers/request.rb +12 -5
  62. data/lib/jets/stack/main/dsl/lambda.rb +1 -1
  63. data/lib/jets/turbo.rb +1 -0
  64. data/lib/jets/version.rb +1 -1
  65. metadata +51 -58
  66. data/vendor/cfn-status/CHANGELOG.md +0 -14
  67. data/vendor/cfn-status/Gemfile +0 -4
  68. data/vendor/cfn-status/LICENSE.txt +0 -21
  69. data/vendor/cfn-status/README.md +0 -56
  70. data/vendor/cfn-status/Rakefile +0 -6
  71. data/vendor/cfn-status/bin/console +0 -14
  72. data/vendor/cfn-status/bin/setup +0 -8
  73. data/vendor/cfn-status/cfn-status.gemspec +0 -30
  74. data/vendor/cfn-status/lib/cfn-status.rb +0 -1
  75. data/vendor/cfn-status/lib/cfn_status.rb +0 -245
  76. data/vendor/cfn-status/lib/cfn_status/aws_service.rb +0 -51
  77. data/vendor/cfn-status/lib/cfn_status/version.rb +0 -3
  78. data/vendor/cfn-status/spec/fixtures/cfn/pages/fresh/describe_stack_events-1.json +0 -1103
  79. data/vendor/cfn-status/spec/fixtures/cfn/pages/fresh/describe_stack_events-2.json +0 -1104
  80. data/vendor/cfn-status/spec/fixtures/cfn/pages/fresh/describe_stack_events-3.json +0 -1103
  81. data/vendor/cfn-status/spec/fixtures/cfn/pages/updating/describe_stack_events-1.json +0 -1103
  82. data/vendor/cfn-status/spec/fixtures/cfn/pages/updating/describe_stack_events-2.json +0 -1104
  83. data/vendor/cfn-status/spec/fixtures/cfn/pages/updating/describe_stack_events-3.json +0 -1103
  84. data/vendor/cfn-status/spec/fixtures/cfn/stack-events-complete.json +0 -1080
  85. data/vendor/cfn-status/spec/fixtures/cfn/stack-events-in-progress.json +0 -1080
  86. data/vendor/cfn-status/spec/fixtures/cfn/stack-events-update-rollback-complete.json +0 -1086
  87. data/vendor/cfn-status/spec/lib/cfn_status_spec.rb +0 -153
  88. data/vendor/cfn-status/spec/spec_helper.rb +0 -14
@@ -53,6 +53,14 @@ Jets figures out what functions to call by evaluating the app code and finds if
53
53
 
54
54
  jets call admin-related_pages_controller-index --no-guess
55
55
 
56
+ If you want to call a function which runs too long time, you can set `read_timeout`.
57
+
58
+ jets call some_long_job-index --read_timeout 900
59
+
60
+ And you can set `retry_limit`. If you don't want to retry you can set 0.
61
+
62
+ jets call some_long_job-index --retry_limit 0
63
+
56
64
  ## Local mode
57
65
 
58
66
  Instead of calling AWS lambda remote, you can also have `jets call` use the code directly on your machine. To enable this, use the `--local` flag. Example:
@@ -1,8 +1,6 @@
1
- You can configure additional gem sources in config/application.rb:
1
+ Check if pre-built Lambda gems are available from the gems source. You can configure the gem in config/application.rb:
2
2
 
3
3
  # Sources for check for pre-compiled Lambda gems. Checks the list in order.
4
4
  Jets.application.configure do
5
- config.gems.sources = [
6
- "https://gems2.lambdagems.com"
7
- ]
8
- end
5
+ config.gems.source = "https://api.serverlessgems.com/api/v1"
6
+ end
@@ -12,6 +12,12 @@ module Jets::Commands
12
12
  Build.new(options).run
13
13
  end
14
14
 
15
+ desc "configure [TOKEN]", "configure token and updates ~/.jets/config.yml"
16
+ long_desc Help.text(:configure)
17
+ def configure(token=nil)
18
+ Configure.new(options.merge(token: token)).run
19
+ end
20
+
15
21
  desc "deploy [environment]", "Builds and deploys project to AWS Lambda"
16
22
  long_desc Help.text(:deploy)
17
23
  # Note the environment is here to trick the Thor parser to allowing an
@@ -23,7 +29,7 @@ module Jets::Commands
23
29
 
24
30
  desc "delete", "Delete the Jets project and all its resources"
25
31
  long_desc Help.text(:delete)
26
- option :sure, type: :boolean, desc: "Skip are you sure prompt."
32
+ option :yes, aliases: %w[y], type: :boolean, desc: "Skip are you sure prompt."
27
33
  option :wait, type: :boolean, default: true, desc: "Wait for stack deletion to complete."
28
34
  # Note the environment is here to trick the Thor parser to allowing an
29
35
  # environment parameter. It is not actually set here. It is set earlier
@@ -83,6 +89,8 @@ module Jets::Commands
83
89
  option :lambda_proxy, type: :boolean, default: true, desc: "Enables automatic Lambda proxy transformation of the event payload"
84
90
  option :guess, type: :boolean, default: true, desc: "Enables guess mode. Uses inference to allows use of all dashes to specify functions. Guess mode verifies that the function exists in the code base."
85
91
  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."
92
+ option :retry_limit, type: :numeric, default: nil, desc: "Retry count of invoking function. It work with remote call"
93
+ option :read_timeout, type: :numeric, default: nil, desc: " The number of seconds to wait for response data. It work with remote call"
86
94
  def call(function_name, payload='')
87
95
  # Printing to stdout can mangle up the response when piping
88
96
  # the value to jq. For example:
@@ -89,6 +89,14 @@ module Jets::Commands
89
89
  run(command)
90
90
  end
91
91
 
92
+ def update_package_json
93
+ path = "package.json"
94
+ return unless File.exist?(path)
95
+ data = JSON.load(IO.read(path))
96
+ data["private"] = true
97
+ IO.write(path, JSON.pretty_generate(data))
98
+ end
99
+
92
100
  # bootstrap is dependent on webpacker, options[:bootstrap] is used
93
101
  # in webpacker_install.
94
102
  def bootstrap_install
@@ -106,7 +114,7 @@ JS
106
114
  after = "const { environment } = require('@rails/webpacker')\n"
107
115
  insert_into_file("config/webpack/environment.js", jquery, after: after)
108
116
 
109
- run("yarn add bootstrap@4.0.0-beta jquery popper.js postcss-cssnext")
117
+ run("yarn add bootstrap jquery popper.js postcss-cssnext")
110
118
  end
111
119
 
112
120
  def git_init
@@ -10,6 +10,12 @@ class Jets::Commands::Sequence < Thor::Group
10
10
  end
11
11
 
12
12
  private
13
+ def jets_minor_version
14
+ md = Jets::VERSION.match(/(\d+)\.(\d+)\.\d+/)
15
+ major, minor = md[1], md[2]
16
+ [major, minor, '0'].join('.')
17
+ end
18
+
13
19
  def clone_project
14
20
  unless git_installed?
15
21
  abort "Unable to detect git installation on your system. Git needs to be installed in order to use the --repo option."
@@ -1,6 +1,6 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gem "jets"
3
+ gem "jets", "~> <%= jets_minor_version %>"
4
4
 
5
5
  <% if @webpacker -%>
6
6
  # Include jetpacker if you are building html pages
@@ -63,7 +63,7 @@ Jets.application.configure do
63
63
  # By default logger needs to log to $stderr for CloudWatch to receive Lambda messages, but for
64
64
  # local testing environment you may want to log these messages to 'test.log' file to keep your
65
65
  # testing suite output readable.
66
- # config.logger = Jets::Logger.new($strerr)
66
+ # config.logger = Jets::Logger.new($stderr)
67
67
 
68
68
  <% if @options[:mode] == 'api' -%>
69
69
  config.controllers.default_protect_from_forgery = false
@@ -84,7 +84,7 @@
84
84
  </div>
85
85
  <p class="version">
86
86
  <strong>Jets version:</strong> <%= Jets.version %><br />
87
- <strong>Ruby version:</strong> <%= Jets::RUBY_VERSION %>
87
+ <strong>Ruby version:</strong> <%= RUBY_VERSION %>
88
88
  </p>
89
89
  </div>
90
90
  </body>
@@ -44,6 +44,7 @@ module Jets::Commands
44
44
  # domain_name is a method on the Jets::Resource::ApiGateway::Domain instance
45
45
  url = "https://#{domain_name.domain_name}"
46
46
  puts "Custom Domain: #{url}"
47
+ puts "App Domain: #{Jets.config.app.domain}" if Jets.config.app.domain
47
48
  end
48
49
 
49
50
  def endpoint_unavailable?
@@ -76,7 +76,7 @@ class Jets::Controller
76
76
  #
77
77
  def dispatch!
78
78
  t1 = Time.now
79
- log_info_start
79
+ log_start
80
80
 
81
81
  begin
82
82
  if run_before_actions(break_if: -> { @rendered })
@@ -95,13 +95,13 @@ class Jets::Controller
95
95
 
96
96
  took = Time.now - t1
97
97
  status = triplet[0]
98
- Jets.logger.info "Completed Status Code #{status} in #{took}s"
98
+ log_finish(status: status, took: took)
99
99
  triplet # status, headers, body
100
100
  end
101
101
 
102
- def log_info_start
102
+ # Documented interface method, careful not to rename
103
+ def log_start
103
104
  # JSON.dump makes logging look pretty in CloudWatch logs because it keeps it on 1 line
104
-
105
105
  ip = request.ip
106
106
  Jets.logger.info "Started #{@event['httpMethod']} \"#{@event['path']}\" for #{ip} at #{Time.now}"
107
107
  Jets.logger.info "Processing #{self.class.name}##{@meth}"
@@ -109,6 +109,12 @@ class Jets::Controller
109
109
  Jets.logger.info " Parameters: #{JSON.dump(filtered_parameters.to_h)}"
110
110
  end
111
111
 
112
+ # Documented interface method, careful not to rename
113
+ def log_finish(options={})
114
+ status, took = options[:status], options[:took]
115
+ Jets.logger.info "Completed Status Code #{status} in #{took}s"
116
+ end
117
+
112
118
  def event_log
113
119
  display_event = @event.dup
114
120
 
@@ -141,6 +147,10 @@ class Jets::Controller
141
147
  paths
142
148
  end
143
149
 
150
+ def controller_name
151
+ self.class.to_s.underscore
152
+ end
153
+
144
154
  def action_name
145
155
  @meth
146
156
  end
@@ -40,7 +40,8 @@ module Jets::Controller::Middleware
40
40
  end
41
41
 
42
42
  def jets_host
43
- default = "#{@env['rack.url_scheme']}://#{@env['HTTP_HOST']}"
43
+ protocol = @event.dig('headers', 'X-Forwarded-Proto') || @env['rack.url_scheme']
44
+ default = "#{protocol}://#{@env['HTTP_HOST']}"
44
45
  Jets.config.helpers.host || default
45
46
  end
46
47
 
@@ -14,11 +14,9 @@ class Jets::Controller
14
14
  # 2. query string parameters
15
15
  # 3. body parameters
16
16
  def params(raw: false, path_parameters: true, body_parameters: true)
17
- path_params = event["pathParameters"] || {}
18
-
19
17
  params = {}
20
18
  params = params.deep_merge(body_params) if body_parameters
21
- params = params.deep_merge(query_parameters) # always
19
+ params = params.deep_merge(unescape_recursively(query_params)) # always
22
20
  params = params.deep_merge(path_params) if path_parameters
23
21
 
24
22
  if raw
@@ -29,13 +27,36 @@ class Jets::Controller
29
27
  end
30
28
  end
31
29
 
30
+ def unescape_recursively(obj)
31
+ case obj
32
+ when Hash then obj.map { |k, v| [k, unescape_recursively(v)] }.to_h
33
+ when Array then obj.map { |v| unescape_recursively(v) }
34
+ else CGI.unescape(obj.to_s)
35
+ end
36
+ end
37
+
32
38
  def filtered_parameters(**kwargs)
33
39
  parameter_filter.filter params(**kwargs, raw: true) # Always filter raw hash
34
40
  end
35
41
 
36
- def query_parameters
42
+ def path_params
43
+ path_params = event["pathParameters"] || {}
44
+ path_params = path_params.map { |k, path| [k, CGI.unescape(path)] }.to_h
45
+ end
46
+ alias_method :path_parameters, :path_params
47
+
48
+ def query_params
37
49
  event["queryStringParameters"] || {}
38
50
  end
51
+ alias_method :query_parameters, :query_params
52
+
53
+ def request_params
54
+ {
55
+ controller: controller_name,
56
+ action: action_name,
57
+ }
58
+ end
59
+ alias_method :request_parameters, :request_params
39
60
 
40
61
  def body_params
41
62
  body = event['isBase64Encoded'] ? base64_decode(event["body"]) : event["body"]
@@ -58,6 +79,7 @@ class Jets::Controller
58
79
  {} # fallback to empty Hash
59
80
  end
60
81
  memoize :body_params
82
+ alias_method :body_parameters, :body_params
61
83
 
62
84
  private
63
85
 
@@ -13,7 +13,8 @@ module Jets::Controller::Rack
13
13
  options = {}
14
14
  options = add_top_level(options)
15
15
  options = add_http_headers(options)
16
- path = @event['path'] || '/' # always set by API Gateway but might not be when testing shim, so setting it to make testing easier
16
+ path = path_with_base_path || @event['path'] || '/' # always set by API Gateway but might not be when testing shim, so setting it to make testing easier
17
+
17
18
  env = Rack::MockRequest.env_for(path, options)
18
19
  if @options[:adapter]
19
20
  env['adapter.event'] = @event
@@ -23,6 +24,22 @@ module Jets::Controller::Rack
23
24
  end
24
25
 
25
26
  private
27
+ def path_with_base_path
28
+ resource = @event['resource']
29
+ pathParameters = @event['pathParameters']
30
+
31
+ if(!pathParameters.nil? and !resource.nil?)
32
+ resource = pathParameters.reduce(resource) {|resource, parameter|
33
+ key, value = parameter
34
+ key = key.eql?("catchall") ? "{#{key}+}" : "{#{key}}"
35
+ resource = resource.gsub(key, value)
36
+ resource
37
+ }
38
+ end
39
+
40
+ resource
41
+ end
42
+
26
43
  def add_top_level(options)
27
44
  map = {
28
45
  'CONTENT_TYPE' => content_type,
@@ -71,9 +71,12 @@ class Jets::Controller
71
71
  add_stage_name(url)
72
72
  end
73
73
 
74
+ # Actual host can be headers["origin"] when cloudfront is in front.
75
+ # Remember to set custom header "origin" header in cloudfront distribution.
76
+ # Can also override with Jets.config.app.domain.
77
+ # The actual_host value is used by redirect_to.
74
78
  def actual_host
75
- # actually host is in headers["origin"] when cloudfront is in front
76
- headers["origin"] || headers["host"]
79
+ Jets.config.app.domain || headers["origin"] || headers["host"]
77
80
  end
78
81
 
79
82
  end
@@ -63,6 +63,12 @@ module Jets::Controller::Rendering
63
63
 
64
64
  # Note @options[:method] uses @options vs options on purpose
65
65
  @options[:method] = event["httpMethod"].downcase if event["httpMethod"]
66
+
67
+ # This is how we pass parameters to actionpack. IE: params to the view.
68
+ # This is because renderer_options is actually the env that is passed to the rack request.
69
+ options.merge!("action_dispatch.request.path_parameters" => @controller.path_parameters)
70
+ options.merge!("action_dispatch.request.query_parameters" => @controller.query_parameters)
71
+ options.merge!("action_dispatch.request.request_parameters" => @controller.request_parameters)
66
72
  options
67
73
  end
68
74
 
@@ -98,7 +104,7 @@ module Jets::Controller::Rendering
98
104
 
99
105
  # PostsController => "posts" is the namespace
100
106
  def template_namespace
101
- @controller.class.to_s.sub('Controller','').underscore.pluralize
107
+ @controller.class.to_s.sub('Controller','').underscore
102
108
  end
103
109
 
104
110
  # Takes headers and adds HTTP_ to front of the keys because that is what rack
data/lib/jets/core.rb CHANGED
@@ -139,11 +139,19 @@ module Jets::Core
139
139
  rack.wait_for_socket
140
140
  end
141
141
 
142
- def default_gems_source
143
- "https://gems2.lambdagems.com"
144
- end
145
-
146
142
  def override_lambda_ruby_runtime
147
143
  require "jets/overrides/lambda"
148
144
  end
145
+
146
+ def ruby_folder
147
+ RUBY_VERSION.split('.')[0..1].join('.') + '.0'
148
+ end
149
+
150
+ # used to configure internal lambda functions
151
+ # current ruby runtime that user is running
152
+ # IE: ruby2.5 ruby2.7
153
+ def ruby_runtime
154
+ version = RUBY_VERSION.split('.')[0..1].join('.')
155
+ "ruby#{version}"
156
+ end
149
157
  end
@@ -2,7 +2,7 @@ require 'aws-sdk-ssm'
2
2
 
3
3
  class Jets::Dotenv
4
4
  class Ssm
5
- SSM_VARIABLE_REGEXP = /^ssm:(.*)/
5
+ SSM_VARIABLE_REGEXP = /^ssm:(.*)/i
6
6
 
7
7
  def initialize(variables={})
8
8
  @variables = variables
@@ -13,6 +13,8 @@ class Jets::Dotenv
13
13
  interpolated_variables = @variables.map do |key, value|
14
14
  if value[SSM_VARIABLE_REGEXP]
15
15
  value = fetch_ssm_value(key, $1)
16
+ elsif value == "SSM"
17
+ value = fetch_ssm_value(key, "SSM")
16
18
  end
17
19
 
18
20
  [key, value]
@@ -26,7 +28,10 @@ class Jets::Dotenv
26
28
  interpolated_variables.to_h.sort_by { |k,_| k }.to_h # success
27
29
  else
28
30
  message = "Error loading .env variables. No matching SSM parameters found for:\n".color(:red)
29
- message += @missing.map { |k,v,n| " #{k}=ssm:#{v} # ssm name: #{n}"}.join("\n")
31
+ message += @missing.map do |k,v,n|
32
+ value = v == "SSM" ? v : "ssm:#{v}"
33
+ " #{k}=#{value} # ssm name: #{n}"
34
+ end.join("\n")
30
35
  abort message
31
36
  end
32
37
  end
@@ -34,8 +39,7 @@ class Jets::Dotenv
34
39
  def fetch_ssm_value(key, value)
35
40
  return "fake-ssm-value" if ENV['JETS_BUILD_NO_INTERNET']
36
41
 
37
- name = value.start_with?("/") ? value :
38
- "/#{Jets.config.project_name}/#{Jets.env}/#{value}"
42
+ name = ssm_name(key, value)
39
43
  response = ssm.get_parameter(name: name, with_decryption: true)
40
44
  response.parameter.value
41
45
  rescue Aws::SSM::Errors::ParameterNotFound
@@ -43,6 +47,16 @@ class Jets::Dotenv
43
47
  ''
44
48
  end
45
49
 
50
+ def ssm_name(key, value)
51
+ if value == "SSM"
52
+ "/#{Jets.config.project_name}/#{Jets.env}/#{key}"
53
+ else
54
+ value.start_with?("/") ?
55
+ value :
56
+ "/#{Jets.config.project_name}/#{Jets.env}/#{value}"
57
+ end
58
+ end
59
+
46
60
  def ssm
47
61
  @ssm ||= Aws::SSM::Client.new
48
62
  end
@@ -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,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