jets 3.1.0 → 4.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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/CHANGELOG.md +55 -0
  4. data/README.md +1 -5
  5. data/backers.md +0 -5
  6. data/jets.gemspec +4 -4
  7. data/lib/jets/application/defaults.rb +7 -2
  8. data/lib/jets/application.rb +6 -7
  9. data/lib/jets/aws_info.rb +2 -2
  10. data/lib/jets/aws_services.rb +8 -0
  11. data/lib/jets/booter.rb +64 -5
  12. data/lib/jets/builders/code_builder.rb +35 -21
  13. data/lib/jets/builders/gem_replacer.rb +2 -7
  14. data/lib/jets/builders/ruby_packager.rb +37 -4
  15. data/lib/jets/cfn/builders/api_deployment_builder.rb +1 -1
  16. data/lib/jets/cfn/builders/api_gateway_builder.rb +31 -27
  17. data/lib/jets/cfn/builders/api_resources_builder.rb +1 -1
  18. data/lib/jets/cfn/builders/authorizer_builder.rb +1 -1
  19. data/lib/jets/cfn/builders/base_child_builder.rb +20 -1
  20. data/lib/jets/cfn/builders/interface.rb +19 -0
  21. data/lib/jets/cfn/builders/page_builder.rb +80 -0
  22. data/lib/jets/cfn/builders/parent_builder.rb +22 -3
  23. data/lib/jets/cfn/builders/shared_builder.rb +1 -1
  24. data/lib/jets/cfn/built_template.rb +1 -1
  25. data/lib/jets/cfn/ship.rb +9 -2
  26. data/lib/jets/cfn/status.rb +1 -1
  27. data/lib/jets/cfn/upload.rb +2 -2
  28. data/lib/jets/cli.rb +12 -5
  29. data/lib/jets/commands/call/base_guesser.rb +1 -1
  30. data/lib/jets/commands/clean/log.rb +3 -3
  31. data/lib/jets/commands/configure.rb +1 -1
  32. data/lib/jets/commands/console.rb +7 -1
  33. data/lib/jets/commands/delete.rb +1 -1
  34. data/lib/jets/commands/deploy.rb +2 -2
  35. data/lib/jets/commands/main.rb +6 -3
  36. data/lib/jets/commands/stack_info.rb +1 -1
  37. data/lib/jets/commands/templates/skeleton/Gemfile.tt +1 -1
  38. data/lib/jets/commands/templates/skeleton/config/application.rb.tt +2 -1
  39. data/lib/jets/commands/url.rb +1 -1
  40. data/lib/jets/core.rb +14 -2
  41. data/lib/jets/core_ext/file.rb +9 -0
  42. data/lib/jets/dotenv.rb +2 -2
  43. data/lib/jets/erb.rb +1 -1
  44. data/lib/jets/inflections.rb +1 -1
  45. data/lib/jets/internal/app/controllers/jets/bare_controller.rb +1 -1
  46. data/lib/jets/internal/app/functions/jets/base_path.rb +93 -3
  47. data/lib/jets/internal/app/functions/jets/base_path_mapping.rb +79 -8
  48. data/lib/jets/internal/app/jobs/jets/preheat_job.rb +6 -2
  49. data/lib/jets/job/base.rb +2 -0
  50. data/lib/jets/job/helpers/sns_event_helper.rb +8 -0
  51. data/lib/jets/job/helpers/sqs_event_helper.rb +8 -0
  52. data/lib/jets/lambda/dsl.rb +7 -1
  53. data/lib/jets/{naming.rb → names.rb} +3 -3
  54. data/lib/jets/preheat.rb +9 -6
  55. data/lib/jets/resource/api_gateway/base_path/role.rb +1 -1
  56. data/lib/jets/resource/api_gateway/deployment.rb +2 -2
  57. data/lib/jets/resource/api_gateway/rest_api/logical_id/message.rb +13 -3
  58. data/lib/jets/resource/api_gateway/rest_api/logical_id.rb +1 -1
  59. data/lib/jets/resource/api_gateway/rest_api/routes/change/base.rb +11 -40
  60. data/lib/jets/resource/api_gateway/rest_api/routes/change/media_types.rb +1 -1
  61. data/lib/jets/resource/api_gateway/rest_api/routes/change/page.rb +2 -2
  62. data/lib/jets/resource/api_gateway/rest_api/routes/change.rb +1 -1
  63. data/lib/jets/resource/api_gateway/rest_api.rb +12 -2
  64. data/lib/jets/resource/child_stack/api_deployment.rb +1 -1
  65. data/lib/jets/resource/child_stack/api_resource/page.rb +1 -1
  66. data/lib/jets/resource/child_stack/api_resource.rb +1 -1
  67. data/lib/jets/resource/child_stack/app_class.rb +1 -1
  68. data/lib/jets/resource/child_stack/shared.rb +1 -1
  69. data/lib/jets/resource/iam/base_role_definition.rb +0 -5
  70. data/lib/jets/resource/iam/policy.rb +31 -0
  71. data/lib/jets/resource/lambda/function/environment.rb +6 -3
  72. data/lib/jets/resource/lambda/function.rb +3 -3
  73. data/lib/jets/router/route.rb +18 -2
  74. data/lib/jets/router/state.rb +47 -0
  75. data/lib/jets/router.rb +12 -0
  76. data/lib/jets/stack/main/dsl/lambda.rb +1 -0
  77. data/lib/jets/tmp_loader.rb +1 -1
  78. data/lib/jets/turbo/database_yaml.rb +1 -1
  79. data/lib/jets/util/yamler.rb +16 -0
  80. data/lib/jets/version.rb +1 -1
  81. data/lib/jets.rb +2 -0
  82. metadata +26 -20
  83. data/.python-version +0 -1
  84. data/.ruby-version +0 -1
@@ -1,13 +1,49 @@
1
- require 'bundler/setup'
1
+ begin
2
+ require 'bundler/setup'
3
+ # When require bundler/setup fails, AWS Lambda won't be able to load base_path_mapping
4
+ # So we'll require base_path_mapping within begin/rescue block so that it does not also
5
+ # fail the entire lambda function.
6
+ require 'jets/internal/app/functions/jets/base_path_mapping'
7
+ rescue Exception => e
8
+ # Note: rescue LoadError is not enough in AWS Lambda environment
9
+ # Actual exceptions:
10
+ # require bundler/setup: Ruby exception "Bundler::GemNotFound"
11
+ # require base_path_mappnig: AWS Lambda reported error "errorType": "Init<LoadError>"
12
+ # Will use a generic rescue Exception though in case error changes in the future.
13
+ puts "WARN: #{e.class} #{e.message}"
14
+ puts <<~EOL
15
+ Could not require bundler/setup.
16
+ This can happen for weird timeout missing error. Example:
17
+
18
+ Could not find timeout-0.3.2 in locally installed gems
19
+
20
+ Happens when the gem command is out-of-date on old versions of ruby 2.7.
21
+ See: https://community.boltops.com/t/could-not-find-timeout-0-3-1-in-any-of-the-sources/996
22
+ EOL
23
+ end
2
24
  require 'cfn_response'
3
- require 'jets/internal/app/functions/jets/base_path_mapping'
4
25
 
5
26
  STAGE_NAME = "<%= @stage_name %>"
6
27
 
7
28
  def lambda_handler(event:, context:)
8
29
  cfn = CfnResponse.new(event, context)
9
30
  cfn.response do
10
- mapping = BasePathMapping.new(event, STAGE_NAME)
31
+ # Super edge case: mapping is nil when require bundler/setup fails
32
+ begin
33
+ mapping = BasePathMapping.new(event, STAGE_NAME)
34
+ rescue NameError => e
35
+ puts "ERROR: #{e.class} #{e.message}"
36
+ puts error_message
37
+ end
38
+
39
+ # This is the "second pass" of CloudFormation when it tries to delete the BasePathMapping during rollback
40
+ if mapping.nil? && event['RequestType'] == "Delete"
41
+ cfn.success # so that CloudFormation can continue the delete process from a rollback
42
+ delay
43
+ return
44
+ end
45
+
46
+ # Normal behavior when mapping is not nil when bundler/setup loads successfully
11
47
  case event['RequestType']
12
48
  when "Create", "Update"
13
49
  mapping.update
@@ -16,3 +52,57 @@ def lambda_handler(event:, context:)
16
52
  end
17
53
  end
18
54
  end
55
+
56
+ def delay
57
+ puts "Delaying 60 seconds to allow user some time to see lambda function logs."
58
+ 60.times do
59
+ puts Time.now
60
+ sleep 1
61
+ end
62
+ end
63
+
64
+ def error_message
65
+ <<~EOL
66
+ This is ultimately the result of require bundler/setup failing to load.
67
+ On the CloudFormation first pass, the BasePathMapping fails to CREATE.
68
+ CloudFormation does a rollback and tries to delete the BasePathMapping.
69
+
70
+ Jets will send a success response to CloudFormation so it can continue and delete
71
+ BasePathMapping on the rollback. Otherwise, CloudFormation ends up in the terminal
72
+ UPDATE_FAILED state and the CloudFormation console provides 3 options:
73
+
74
+ 1) retry 2) update 3) rollback.
75
+
76
+ The only option that seems to work is rollback to get it out of UPDATE_FAILED to
77
+ UPDATE_ROLLBACK_COMPLETE. But then, if we `jets deploy` again without fixing the
78
+ require bundler/setup issue, we'll end back up in the UPDATE_FAILED state.
79
+
80
+ Will handle this error so we can continue the stack because we do not want it to fail
81
+ and never be able to send the CfnResponse. Then we have to wait hours for a CloudFormation timeout.
82
+ Sometimes deleting the APP-dev-ApiDeployment20230518230443-EXAMPLEY8YQP0 stack
83
+ allows the cloudformation stacks to continue, but usually, it'll only just slightly speed up the rollback.
84
+
85
+ Some examples of actual rollback times:
86
+
87
+ When left alone, the rollback takes about 2.5 hours.
88
+
89
+ 2023-05-19 05:39:25 User Initiated
90
+ 2023-05-19 08:01:48 UPDATE_ROLLBACK_COMPLETE
91
+
92
+ When deleting the APP-dev-ApiDeployment20230518230443-EXAMPLEY8YQP0 stack, it takes about 1.5 hours.
93
+
94
+ 2023-05-19 06:25:41 User Initiated
95
+ 2023-05-19 07:47:03 UPDATE_ROLLBACK_COMPLETE
96
+
97
+ Rescuing and handling the error here allows the CloudFormation stack to continue and finish the rollback process.
98
+ It takes the rollback time down to about 3 minutes. Example:
99
+
100
+ 2023-05-19 16:34:19 User Initiated
101
+ 2023-05-19 16:37:34 UPDATE_ROLLBACK_COMPLETE
102
+
103
+ Note: The first cloudformation CREATE pass sends FAILED Status to CloudFormation,
104
+ and the second cloudformation DELETE pass sends SUCCESS Status to CloudFormation.
105
+
106
+ Related: https://community.boltops.com/t/could-not-find-timeout-0-3-1-in-any-of-the-sources/996
107
+ EOL
108
+ end
@@ -28,8 +28,28 @@ class BasePathMapping
28
28
  # Cannot use update_base_path_mapping to update the base_mapping because it doesnt
29
29
  # allow us to change the rest_api_id. So we delete and create.
30
30
  def update
31
- delete(true)
32
- create
31
+ puts "BasePathMapping update"
32
+ if rest_api_changed?
33
+ delete(true)
34
+ create
35
+ else
36
+ puts "BasePathMapping update: rest_api_id #{rest_api_id} did not change. Skipping."
37
+ end
38
+
39
+ puts "BasePathMapping update complete"
40
+ end
41
+
42
+ def rest_api_changed?
43
+ puts "BasePathMapping checking if rest_api_id changed"
44
+ mapping = current_base_path_mapping
45
+ return true unless mapping
46
+ mapping.rest_api_id != rest_api_id
47
+ end
48
+
49
+ def current_base_path_mapping
50
+ resp = apigateway.get_base_path_mapping(base_path: "(none)", domain_name: domain_name)
51
+ rescue Aws::APIGateway::Errors::NotFoundException
52
+ return nil
33
53
  end
34
54
 
35
55
  # Dont delete the newly created base path mapping unless this is an operation
@@ -39,10 +59,14 @@ class BasePathMapping
39
59
  end
40
60
 
41
61
  def delete(fail_silently=false)
42
- apigateway.delete_base_path_mapping(
62
+ puts "BasePathMapping delete"
63
+ options = {
43
64
  domain_name: domain_name, # required
44
65
  base_path: base_path.empty? ? '(none)' : base_path,
45
- )
66
+ }
67
+ puts "BasePathMapping delete options #{options.inspect}"
68
+ apigateway.delete_base_path_mapping(options)
69
+ puts "BasePathMapping delete complete"
46
70
  # https://github.com/tongueroo/jets/issues/255
47
71
  # Used to return: Aws::APIGateway::Errors::NotFoundException
48
72
  # Now returns: Aws::APIGateway::Errors::InternalFailure
@@ -52,12 +76,41 @@ class BasePathMapping
52
76
  end
53
77
 
54
78
  def create
55
- apigateway.create_base_path_mapping(
79
+ puts "BasePathMapping create"
80
+ options = {
56
81
  domain_name: domain_name, # required
57
82
  base_path: base_path,
58
83
  rest_api_id: rest_api_id, # required
59
84
  stage: @stage_name,
60
- )
85
+ }
86
+ puts "BasePathMapping create options #{options.inspect}"
87
+ apigateway.create_base_path_mapping(options)
88
+ puts "BasePathMapping create complete"
89
+ rescue Aws::APIGateway::Errors::ServiceError => e
90
+ puts "ERROR: #{e.class}: #{e.message}"
91
+ puts "BasePathMapping create failed"
92
+ if e.message.include?("Invalid domain name identifier specified")
93
+ puts <<~EOL
94
+ This super edge case error seems to happen when the cloudformation stack does a rollback
95
+ because the BasePathMapping custom resource fails. This has happened with a strange combination of
96
+ ruby 2.7 and the timeout gem not being picked up in the AWS Lambda runtime environment
97
+ Specifically, when jets deploy was used with a rubygems install that is out-of-date.
98
+ See: https://community.boltops.com/t/could-not-find-timeout-0-3-1-in-any-of-the-sources/996
99
+
100
+ The new base path mapping is not created correctly and the old base path mapping is not properly deleted.
101
+ The old ghost base mapping interferes with the new base path mapping.
102
+ The workaround solution seems to require removing all the config.domain settings and deploying again. Example:
103
+
104
+ config/application.rb
105
+
106
+ config.domain.cert_arn = "arn:aws:acm:us-west-2:111111111111:certificate/EXAMPLE1-a3de-4fe7-b72e-4cc153c5303e"
107
+ config.domain.hosted_zone_name = "example.com"
108
+
109
+ Comment out those settings, deploy, then uncomment and deploy again.
110
+ If there's a better workaround, please let us know.
111
+ EOL
112
+ end
113
+ raise(e)
61
114
  end
62
115
 
63
116
  def deployment_stack
@@ -91,10 +144,28 @@ class BasePathMapping
91
144
  end
92
145
 
93
146
  def apigateway
94
- @apigateway ||= Aws::APIGateway::Client.new
147
+ @apigateway ||= Aws::APIGateway::Client.new(aws_options)
95
148
  end
96
149
 
97
150
  def cfn
98
- @cfn ||= Aws::CloudFormation::Client.new
151
+ @cfn ||= Aws::CloudFormation::Client.new(aws_options)
99
152
  end
153
+
154
+ def aws_options
155
+ options = {
156
+ retry_limit: 7, # default: 3
157
+ retry_base_delay: 0.6, # default: 0.3
158
+ }
159
+ options.merge!(
160
+ log_level: :debug,
161
+ logger: Logger.new($stdout),
162
+ ) if ENV['JETS_DEBUG_AWS_SDK']
163
+ options
164
+ end
165
+ end
166
+
167
+ if __FILE__ == $0
168
+ event = JSON.load(File.read(ARGV[0]))
169
+ context = nil # stub out
170
+ BasePathMapping.new(event, context).update
100
171
  end
@@ -12,13 +12,17 @@ class Jets::PreheatJob < ApplicationJob
12
12
  sid: "Statement1",
13
13
  action: ["logs:*"],
14
14
  effect: "Allow",
15
- resource: "arn:aws:logs:#{Jets.aws.region}:#{Jets.aws.account}:log-group:#{Jets.config.project_namespace}-*",
15
+ resource: [
16
+ sub("arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${WarmLambdaFunction}"),
17
+ ]
16
18
  },
17
19
  {
18
20
  sid: "Statement2",
19
21
  action: ["lambda:InvokeFunction", "lambda:InvokeAsync"],
20
22
  effect: "Allow",
21
- resource: "arn:aws:lambda:#{Jets.aws.region}:#{Jets.aws.account}:function:#{Jets.config.project_namespace}-*",
23
+ resource: [
24
+ sub("arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${WarmLambdaFunction}")
25
+ ]
22
26
  }
23
27
  )
24
28
 
data/lib/jets/job/base.rb CHANGED
@@ -13,6 +13,8 @@ module Jets::Job
13
13
  include Helpers::KinesisEventHelper
14
14
  include Helpers::LogEventHelper
15
15
  include Helpers::S3EventHelper
16
+ include Helpers::SnsEventHelper
17
+ include Helpers::SqsEventHelper
16
18
 
17
19
  # Tracks bucket each time an s3_event is declared
18
20
  # Map of bucket_name => stack_name (nested part)
@@ -0,0 +1,8 @@
1
+ module Jets::Job::Helpers
2
+ module SnsEventHelper
3
+ def sns_event_payload
4
+ message = event&.dig("Records", 0, "Sns", "Message")
5
+ @sns_event_payload ||= ActiveSupport::HashWithIndifferentAccess.new(JSON.load(message))
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Jets::Job::Helpers
2
+ module SqsEventHelper
3
+ def sqs_event_payload
4
+ message = event&.dig("Records", 0, "body")
5
+ @sqs_event_payload ||= ActiveSupport::HashWithIndifferentAccess.new(JSON.parse(message))
6
+ end
7
+ end
8
+ end
@@ -57,6 +57,7 @@ module Jets::Lambda::Dsl
57
57
  PROPERTIES = %W[
58
58
  dead_letter_config
59
59
  description
60
+ ephemeral_storage
60
61
  handler
61
62
  kms_key_arn
62
63
  memory_size
@@ -236,6 +237,10 @@ module Jets::Lambda::Dsl
236
237
  "!Ref #{name.to_s.camelize}"
237
238
  end
238
239
 
240
+ def sub(value)
241
+ "!Sub #{value.to_s.camelize}"
242
+ end
243
+
239
244
  # meth is a Symbol
240
245
  def method_added(meth)
241
246
  return if %w[initialize method_missing].include?(meth.to_s)
@@ -316,7 +321,8 @@ module Jets::Lambda::Dsl
316
321
  #
317
322
  # Do not include tasks from the direct subclasses of Jets::Lambda::Functions
318
323
  # because those classes are abstract. Dont want those methods to be included.
319
- def find_all_tasks(public: true)
324
+ def find_all_tasks(options={})
325
+ public = options[:public].nil? ? true : options[:public]
320
326
  klass = self
321
327
  direct_subclasses = Jets::Lambda::Functions.subclasses
322
328
  lookup = []
@@ -1,7 +1,7 @@
1
- # This class groups the naming in one place.
2
- # Some naming is for CloudFormation
1
+ # This class groups the names in one place.
2
+ # Some names are for CloudFormation
3
3
  # Some are for the Build process
4
- class Jets::Naming
4
+ class Jets::Names
5
5
  # Mainly used by build.rb
6
6
  class << self
7
7
  extend Memoist
data/lib/jets/preheat.rb CHANGED
@@ -91,12 +91,15 @@ module Jets
91
91
  # ...
92
92
  # ]
93
93
  def all_functions
94
- classes.map do |klass|
95
- tasks = klass.tasks.select { |t| t.lang == :ruby } # only prewarm ruby functions
96
- tasks.map do |task|
97
- meth = task.meth
98
- underscored = klass.to_s.underscore.gsub('/','-')
99
- "#{underscored}-#{meth}" # function_name
94
+ parent_stack = cfn.describe_stack_resources(stack_name: Jets::Names.parent_stack_name)
95
+ parent_resources = parent_stack.stack_resources.select do |resource|
96
+ resource.logical_resource_id =~ /Controller$/ # only controller functions
97
+ end
98
+ physical_resource_ids = parent_resources.map(&:physical_resource_id)
99
+ resources = physical_resource_ids.inject([]) do |acc, physical_resource_id|
100
+ stack_resources = cfn.describe_stack_resources(stack_name: physical_resource_id).stack_resources
101
+ stack_resources.each do |stack_resource|
102
+ acc << stack_resource if stack_resource.logical_resource_id.ends_with?('LambdaFunction') # only functions
100
103
  end
101
104
  end.flatten.uniq.compact
102
105
  end
@@ -54,7 +54,7 @@ module Jets::Resource::ApiGateway::BasePath
54
54
 
55
55
  # Duplicated in rest_api/change_detection.rb, base_path/role.rb, rest_api/routes.rb
56
56
  def rest_api_id
57
- stack_name = Jets::Naming.parent_stack_name
57
+ stack_name = Jets::Names.parent_stack_name
58
58
  return "RestApi" unless stack_exists?(stack_name)
59
59
 
60
60
  stack = cfn.describe_stacks(stack_name: stack_name).stacks.first
@@ -33,7 +33,7 @@ module Jets::Resource::ApiGateway
33
33
  end
34
34
 
35
35
  def depends_on
36
- expression = "#{Jets::Naming.template_path_prefix}-*_controller*"
36
+ expression = "#{Jets::Names.template_path_prefix}-*_controller*"
37
37
  controller_logical_ids = []
38
38
  Dir.glob(expression).each do |path|
39
39
  next unless File.file?(path)
@@ -57,7 +57,7 @@ module Jets::Resource::ApiGateway
57
57
 
58
58
  def self.stage_name
59
59
  # Stage name only allows a-zA-Z0-9_
60
- [Jets.config.short_env, Jets.config.env_extra].compact.join('_').gsub('-','_')
60
+ [Jets.config.short_env, Jets.config.extra].compact.join('_').gsub('-','_')
61
61
  end
62
62
 
63
63
  def timestamp
@@ -8,8 +8,7 @@ class Jets::Resource::ApiGateway::RestApi::LogicalId
8
8
  end
9
9
 
10
10
  def custom_domain
11
- api = Jets::Resource::ApiGateway::DomainName.new
12
- domain_name = api.domain_name
11
+ domain_name = Jets.config.domain.name
13
12
  if domain_name
14
13
  <<~EOL
15
14
  It looks like you have already set up a custom domain.
@@ -25,7 +24,18 @@ class Jets::Resource::ApiGateway::RestApi::LogicalId
25
24
  More info: custom domain docs: https://rubyonjets.com/docs/routing/custom-domain/
26
25
  EOL
27
26
  else
28
- "Please set up a custom domain https://rubyonjets.com/docs/routing/custom-domain/"
27
+ <<~EOL
28
+ To avoid this prompt in the future, you can configure:
29
+
30
+ config/application.rb
31
+
32
+ config.api.auto_replace = true
33
+
34
+ However, you should also set up a custom domain for a "stable" endpoint.
35
+
36
+ https://rubyonjets.com/docs/routing/custom-domain/
37
+
38
+ EOL
29
39
  end
30
40
  end
31
41
 
@@ -88,7 +88,7 @@ class Jets::Resource::ApiGateway::RestApi
88
88
  end
89
89
 
90
90
  def parent_stack_name
91
- Jets::Naming.parent_stack_name
91
+ Jets::Names.parent_stack_name
92
92
  end
93
93
 
94
94
  def default
@@ -7,51 +7,22 @@ class Jets::Resource::ApiGateway::RestApi::Routes::Change
7
7
  new.changed?
8
8
  end
9
9
 
10
- # Build up deployed routes from the existing CloudFormation resources.
10
+ # Recreate routes from previously deployed stored state in s3
11
11
  def deployed_routes
12
- routes = []
13
-
14
- resources, position = [], true
15
- while position
16
- position = nil if position == true # start of loop
17
- resp = apigateway.get_resources(
18
- rest_api_id: rest_api_id,
19
- position: position,
20
- limit: 500, # default: 25 max: 500
12
+ state = Jets::Router::State.new
13
+ data = state.load("routes")
14
+ return [] if data.nil?
15
+
16
+ data.map do |item|
17
+ Jets::Router::Route.new(
18
+ path: item['path'],
19
+ method: item['options']['method'],
20
+ to: item['to'],
21
21
  )
22
- resources += resp.items
23
- position = resp.position
24
- end
25
-
26
- resources.each do |resource|
27
- resource_methods = resource.resource_methods
28
- next if resource_methods.nil?
29
-
30
- resource_methods.each do |http_verb, resource_method|
31
- # puts "#{http_verb} #{resource.path} | resource.id #{resource.id}"
32
- # puts to(resource.id, http_verb)
33
-
34
- # Test changing config.cors and CloudFormation does an in-place update
35
- # on the resource. So no need to do bluegreen deployments for OPTIONS.
36
- next if http_verb == "OPTIONS"
37
-
38
- path = recreate_path(resource.path)
39
- method = http_verb.downcase.to_sym
40
- to = to(resource.id, http_verb)
41
- route = Jets::Router::Route.new(path: path, method: method, to: to)
42
- routes << route
43
- end
44
22
  end
45
- routes
46
23
  end
47
24
  memoize :deployed_routes
48
25
 
49
- def recreate_path(path)
50
- path = path.gsub(%r{^/},'')
51
- path = path.gsub(/{([^}]*)\+}/, '*\1')
52
- path.gsub(/{([^}]*)}/, ':\1')
53
- end
54
-
55
26
  def to(resource_id, http_method)
56
27
  uri = method_uri(resource_id, http_method)
57
28
  recreate_to(uri) unless uri.nil?
@@ -115,7 +86,7 @@ class Jets::Resource::ApiGateway::RestApi::Routes::Change
115
86
 
116
87
  # Duplicated in rest_api/change_detection.rb, base_path/role.rb, rest_api/routes.rb
117
88
  def rest_api_id
118
- stack_name = Jets::Naming.parent_stack_name
89
+ stack_name = Jets::Names.parent_stack_name
119
90
  return "RestApi" unless stack_exists?(stack_name)
120
91
 
121
92
  stack = cfn.describe_stacks(stack_name: stack_name).stacks.first
@@ -30,7 +30,7 @@ class Jets::Resource::ApiGateway::RestApi::Routes::Change
30
30
  end
31
31
 
32
32
  def parent_stack_name
33
- Jets::Naming.parent_stack_name
33
+ Jets::Names.parent_stack_name
34
34
  end
35
35
  end
36
36
  end
@@ -33,7 +33,7 @@ class Jets::Resource::ApiGateway::RestApi::Routes::Change
33
33
  # logical id to page map
34
34
  # Important: In Cfn::Builders::ApiGatewayBuilder, the add_gateway_routes and ApiResourcesBuilder needs to run
35
35
  # before the parent add_gateway_rest_api method.
36
- def local_logical_ids_map(path_expression="#{Jets::Naming.template_path_prefix}-api-resources-*.yml")
36
+ def local_logical_ids_map(path_expression="#{Jets::Names.template_path_prefix}-api-resources-*.yml")
37
37
  logical_ids = {} # logical id => page number
38
38
 
39
39
  Dir.glob(path_expression).each do |path|
@@ -85,7 +85,7 @@ class Jets::Resource::ApiGateway::RestApi::Routes::Change
85
85
  end
86
86
 
87
87
  def parent_resources
88
- resp = cfn.describe_stack_resources(stack_name: Jets::Naming.parent_stack_name)
88
+ resp = cfn.describe_stack_resources(stack_name: Jets::Names.parent_stack_name)
89
89
  resp.stack_resources
90
90
  end
91
91
  memoize :parent_resources
@@ -10,7 +10,7 @@ class Jets::Resource::ApiGateway::RestApi::Routes
10
10
  end
11
11
 
12
12
  def parent_stack_exists?
13
- stack_exists?(Jets::Naming.parent_stack_name)
13
+ stack_exists?(Jets::Names.parent_stack_name)
14
14
  end
15
15
  end
16
16
  end
@@ -2,9 +2,10 @@ module Jets::Resource::ApiGateway
2
2
  class RestApi < Jets::Resource::Base
3
3
  def definition
4
4
  properties = {
5
- name: Jets::Naming.gateway_api_name,
5
+ name: Jets::Names.gateway_api_name,
6
6
  endpoint_configuration: { types: endpoint_types }
7
7
  }
8
+ properties[:endpoint_configuration][:vpc_endpoint_ids] = vpce_ids if vpce_ids
8
9
  properties[:binary_media_types] = binary_media_types if binary_media_types
9
10
  properties[:policy] = endpoint_policy if endpoint_policy
10
11
 
@@ -60,5 +61,14 @@ module Jets::Resource::ApiGateway
60
61
 
61
62
  endpoint_policy
62
63
  end
64
+
65
+ private
66
+
67
+ def vpce_ids
68
+ ids = Jets.config.api.vpc_endpoint_ids
69
+ return nil if ids.nil? || ids.empty?
70
+
71
+ ids
72
+ end
63
73
  end
64
- end
74
+ end
@@ -31,7 +31,7 @@ module Jets::Resource::ChildStack
31
31
  end
32
32
 
33
33
  def depends_on
34
- expression = "#{Jets::Naming.template_path_prefix}-*_controller*"
34
+ expression = "#{Jets::Names.template_path_prefix}-*_controller*"
35
35
  controller_logical_ids = []
36
36
  Dir.glob(expression).each do |path|
37
37
  next unless File.file?(path)
@@ -3,7 +3,7 @@ class Jets::Resource::ChildStack::ApiResource
3
3
  # Returns: logical id of ApiResource Page
4
4
  class Page
5
5
  def self.logical_id(parameter)
6
- expression = "#{Jets::Naming.template_path_prefix}-api-resources-*"
6
+ expression = "#{Jets::Names.template_path_prefix}-api-resources-*"
7
7
  # IE: path: #{Jets.build_root}/templates/demo-dev-2-api-resources-1.yml"
8
8
  template_paths = Dir.glob(expression).sort.to_a
9
9
  found_template = template_paths.detect do |path|
@@ -28,7 +28,7 @@ module Jets::Resource::ChildStack
28
28
  # Since dont have all the info required.
29
29
  # Read the template back to find the parameters required.
30
30
  # Actually might be easier to rationalize this approach.
31
- template_path = Jets::Naming.api_resources_template_path(@page)
31
+ template_path = Jets::Names.api_resources_template_path(@page)
32
32
  template = Jets::Cfn::BuiltTemplate.get(template_path)
33
33
  template['Parameters'].keys.each do |p|
34
34
  case p
@@ -125,7 +125,7 @@ module Jets::Resource::ChildStack
125
125
  end
126
126
 
127
127
  def current_app_class
128
- templates_prefix = "#{Jets::Naming.template_path_prefix}-app-"
128
+ templates_prefix = "#{Jets::Names.template_path_prefix}-app-"
129
129
  @path.sub(templates_prefix, '')
130
130
  .sub(/\.yml$/,'')
131
131
  .gsub('-','/')
@@ -63,7 +63,7 @@ module Jets::Resource::ChildStack
63
63
  # IE: app/resource.rb => Resource
64
64
  # Returns Resource class object in the example
65
65
  def current_shared_class
66
- templates_prefix = "#{Jets::Naming.template_path_prefix}-shared-"
66
+ templates_prefix = "#{Jets::Names.template_path_prefix}-shared-"
67
67
  @path.sub(templates_prefix, '')
68
68
  .sub(/\.yml$/,'')
69
69
  .gsub('-','/')
@@ -24,11 +24,6 @@ module Jets::Resource::Iam
24
24
  }
25
25
  }
26
26
 
27
- definition[logical_id][:properties][:policies] = [
28
- policy_name: "#{policy_name[0..127]}", # required, limited to 128-chars
29
- policy_document: policy_document,
30
- ] unless policy_document['Statement'].empty?
31
-
32
27
  unless managed_policy_arns.empty?
33
28
  definition[logical_id][:properties][:managed_policy_arns] = managed_policy_arns
34
29
  end
@@ -0,0 +1,31 @@
1
+ module Jets::Resource::Iam
2
+ class Policy < Jets::Resource::Base
3
+ def initialize(role)
4
+ @role = role
5
+ end
6
+ delegate :policy_document, :policy_name, :role_logical_id, :replacements, to: :@role
7
+
8
+ def policy_logical_id
9
+ role_logical_id.sub(/role$/, "policy")
10
+ end
11
+
12
+ def definition
13
+ logical_id = policy_logical_id
14
+
15
+ # Do not assign pretty role_name because long controller names might hit the 64-char
16
+ # limit. Also, IAM roles are global, so assigning role names prevents cross region deploys.
17
+ definition = {
18
+ logical_id => {
19
+ type: "AWS::IAM::Policy",
20
+ properties: {
21
+ roles: [Ref: role_logical_id.camelize],
22
+ policy_name: "#{policy_name[0..127]}", # required, limited to 128-chars
23
+ policy_document: policy_document,
24
+ }
25
+ }
26
+ }
27
+
28
+ definition
29
+ end
30
+ end
31
+ end