jets 2.3.19 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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