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.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/CHANGELOG.md +55 -0
- data/README.md +1 -5
- data/backers.md +0 -5
- data/jets.gemspec +4 -4
- data/lib/jets/application/defaults.rb +7 -2
- data/lib/jets/application.rb +6 -7
- data/lib/jets/aws_info.rb +2 -2
- data/lib/jets/aws_services.rb +8 -0
- data/lib/jets/booter.rb +64 -5
- data/lib/jets/builders/code_builder.rb +35 -21
- data/lib/jets/builders/gem_replacer.rb +2 -7
- data/lib/jets/builders/ruby_packager.rb +37 -4
- data/lib/jets/cfn/builders/api_deployment_builder.rb +1 -1
- data/lib/jets/cfn/builders/api_gateway_builder.rb +31 -27
- data/lib/jets/cfn/builders/api_resources_builder.rb +1 -1
- data/lib/jets/cfn/builders/authorizer_builder.rb +1 -1
- data/lib/jets/cfn/builders/base_child_builder.rb +20 -1
- data/lib/jets/cfn/builders/interface.rb +19 -0
- data/lib/jets/cfn/builders/page_builder.rb +80 -0
- data/lib/jets/cfn/builders/parent_builder.rb +22 -3
- data/lib/jets/cfn/builders/shared_builder.rb +1 -1
- data/lib/jets/cfn/built_template.rb +1 -1
- data/lib/jets/cfn/ship.rb +9 -2
- data/lib/jets/cfn/status.rb +1 -1
- data/lib/jets/cfn/upload.rb +2 -2
- data/lib/jets/cli.rb +12 -5
- data/lib/jets/commands/call/base_guesser.rb +1 -1
- data/lib/jets/commands/clean/log.rb +3 -3
- data/lib/jets/commands/configure.rb +1 -1
- data/lib/jets/commands/console.rb +7 -1
- data/lib/jets/commands/delete.rb +1 -1
- data/lib/jets/commands/deploy.rb +2 -2
- data/lib/jets/commands/main.rb +6 -3
- data/lib/jets/commands/stack_info.rb +1 -1
- data/lib/jets/commands/templates/skeleton/Gemfile.tt +1 -1
- data/lib/jets/commands/templates/skeleton/config/application.rb.tt +2 -1
- data/lib/jets/commands/url.rb +1 -1
- data/lib/jets/core.rb +14 -2
- data/lib/jets/core_ext/file.rb +9 -0
- data/lib/jets/dotenv.rb +2 -2
- data/lib/jets/erb.rb +1 -1
- data/lib/jets/inflections.rb +1 -1
- data/lib/jets/internal/app/controllers/jets/bare_controller.rb +1 -1
- data/lib/jets/internal/app/functions/jets/base_path.rb +93 -3
- data/lib/jets/internal/app/functions/jets/base_path_mapping.rb +79 -8
- data/lib/jets/internal/app/jobs/jets/preheat_job.rb +6 -2
- data/lib/jets/job/base.rb +2 -0
- data/lib/jets/job/helpers/sns_event_helper.rb +8 -0
- data/lib/jets/job/helpers/sqs_event_helper.rb +8 -0
- data/lib/jets/lambda/dsl.rb +7 -1
- data/lib/jets/{naming.rb → names.rb} +3 -3
- data/lib/jets/preheat.rb +9 -6
- data/lib/jets/resource/api_gateway/base_path/role.rb +1 -1
- data/lib/jets/resource/api_gateway/deployment.rb +2 -2
- data/lib/jets/resource/api_gateway/rest_api/logical_id/message.rb +13 -3
- data/lib/jets/resource/api_gateway/rest_api/logical_id.rb +1 -1
- data/lib/jets/resource/api_gateway/rest_api/routes/change/base.rb +11 -40
- data/lib/jets/resource/api_gateway/rest_api/routes/change/media_types.rb +1 -1
- data/lib/jets/resource/api_gateway/rest_api/routes/change/page.rb +2 -2
- data/lib/jets/resource/api_gateway/rest_api/routes/change.rb +1 -1
- data/lib/jets/resource/api_gateway/rest_api.rb +12 -2
- data/lib/jets/resource/child_stack/api_deployment.rb +1 -1
- data/lib/jets/resource/child_stack/api_resource/page.rb +1 -1
- data/lib/jets/resource/child_stack/api_resource.rb +1 -1
- data/lib/jets/resource/child_stack/app_class.rb +1 -1
- data/lib/jets/resource/child_stack/shared.rb +1 -1
- data/lib/jets/resource/iam/base_role_definition.rb +0 -5
- data/lib/jets/resource/iam/policy.rb +31 -0
- data/lib/jets/resource/lambda/function/environment.rb +6 -3
- data/lib/jets/resource/lambda/function.rb +3 -3
- data/lib/jets/router/route.rb +18 -2
- data/lib/jets/router/state.rb +47 -0
- data/lib/jets/router.rb +12 -0
- data/lib/jets/stack/main/dsl/lambda.rb +1 -0
- data/lib/jets/tmp_loader.rb +1 -1
- data/lib/jets/turbo/database_yaml.rb +1 -1
- data/lib/jets/util/yamler.rb +16 -0
- data/lib/jets/version.rb +1 -1
- data/lib/jets.rb +2 -0
- metadata +26 -20
- data/.python-version +0 -1
- data/.ruby-version +0 -1
@@ -1,13 +1,49 @@
|
|
1
|
-
|
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
|
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
|
-
|
32
|
-
|
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
|
-
|
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
|
-
|
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:
|
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:
|
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)
|
data/lib/jets/lambda/dsl.rb
CHANGED
@@ -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(
|
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
|
2
|
-
# Some
|
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::
|
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
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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::
|
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::
|
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.
|
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
|
-
|
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
|
-
|
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
|
|
@@ -7,51 +7,22 @@ class Jets::Resource::ApiGateway::RestApi::Routes::Change
|
|
7
7
|
new.changed?
|
8
8
|
end
|
9
9
|
|
10
|
-
#
|
10
|
+
# Recreate routes from previously deployed stored state in s3
|
11
11
|
def deployed_routes
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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::
|
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
|
@@ -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::
|
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::
|
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
|
@@ -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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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
|