cloud-mu 2.0.0.pre.alpha9 → 2.0.0.pre.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Berksfile.lock +1 -1
- data/README.md +2 -0
- data/bin/mu-configure +2 -58
- data/bin/mu-gen-docs +29 -4
- data/bin/mu-load-config.rb +0 -1
- data/bin/mu-user-manage +4 -0
- data/cloud-mu.gemspec +2 -2
- data/cookbooks/mu-master/recipes/default.rb +3 -4
- data/cookbooks/mu-master/recipes/init.rb +3 -3
- data/cookbooks/mu-tools/files/default/Mu_CA.pem +15 -15
- data/cookbooks/mu-tools/libraries/helper.rb +1 -1
- data/cookbooks/mu-tools/recipes/eks.rb +3 -3
- data/cookbooks/mu-tools/recipes/set_local_fw.rb +1 -1
- data/cookbooks/mu-utility/recipes/remi.rb +1 -1
- data/cookbooks/nagios/libraries/base.rb +4 -4
- data/cookbooks/nagios/libraries/contact.rb +1 -1
- data/cookbooks/nagios/libraries/contactgroup.rb +1 -1
- data/cookbooks/nagios/libraries/host.rb +2 -2
- data/cookbooks/nagios/libraries/hostdependency.rb +3 -3
- data/cookbooks/nagios/libraries/hostescalation.rb +3 -3
- data/cookbooks/nagios/libraries/hostgroup.rb +2 -2
- data/cookbooks/nagios/libraries/nagios.rb +5 -5
- data/cookbooks/nagios/libraries/service.rb +3 -3
- data/cookbooks/nagios/libraries/servicedependency.rb +2 -2
- data/cookbooks/nagios/libraries/serviceescalation.rb +2 -2
- data/cookbooks/nagios/libraries/servicegroup.rb +2 -2
- data/cookbooks/nagios/libraries/timeperiod.rb +1 -1
- data/install/installer +1 -1
- data/modules/mu/cleanup.rb +1 -1
- data/modules/mu/cloud.rb +43 -1
- data/modules/mu/clouds/aws.rb +55 -35
- data/modules/mu/clouds/aws/bucket.rb +287 -0
- data/modules/mu/clouds/aws/database.rb +65 -11
- data/modules/mu/clouds/aws/endpoint.rb +592 -0
- data/modules/mu/clouds/aws/firewall_rule.rb +4 -0
- data/modules/mu/clouds/aws/function.rb +138 -93
- data/modules/mu/clouds/aws/nosqldb.rb +387 -0
- data/modules/mu/clouds/aws/role.rb +1 -1
- data/modules/mu/clouds/aws/server.rb +5 -5
- data/modules/mu/clouds/aws/server_pool.rb +60 -3
- data/modules/mu/clouds/azure.rb +0 -1
- data/modules/mu/clouds/google.rb +34 -12
- data/modules/mu/clouds/google/bucket.rb +179 -0
- data/modules/mu/config.rb +1 -1
- data/modules/mu/config/bucket.rb +69 -0
- data/modules/mu/config/bucket.yml +10 -0
- data/modules/mu/config/database.rb +1 -1
- data/modules/mu/config/endpoint.rb +71 -0
- data/modules/mu/config/function.rb +6 -0
- data/modules/mu/config/nosqldb.rb +49 -0
- data/modules/mu/config/nosqldb.yml +44 -0
- data/modules/mu/config/notifier.yml +2 -2
- data/modules/mu/config/vpc.rb +0 -1
- data/modules/mu/defaults/amazon_images.yaml +32 -30
- data/modules/mu/groomers/chef.rb +1 -1
- data/modules/mu/kittens.rb +2430 -1511
- data/modules/mu/master/ldap.rb +1 -1
- data/modules/tests/super_complex_bok.yml +7 -0
- data/modules/tests/super_simple_bok.yml +7 -0
- metadata +11 -2
@@ -0,0 +1,287 @@
|
|
1
|
+
# Copyright:: Copyright (c) 2019 eGlobalTech, Inc., all rights reserved
|
2
|
+
#
|
3
|
+
# Licensed under the BSD-3 license (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License in the root of the project or at
|
6
|
+
#
|
7
|
+
# http://egt-labs.com/mu/LICENSE.html
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module MU
|
16
|
+
class Cloud
|
17
|
+
class AWS
|
18
|
+
# Support for AWS S3
|
19
|
+
class Bucket < MU::Cloud::Bucket
|
20
|
+
@deploy = nil
|
21
|
+
@config = nil
|
22
|
+
|
23
|
+
@@region_cache = {}
|
24
|
+
@@region_cache_semaphore = Mutex.new
|
25
|
+
|
26
|
+
attr_reader :mu_name
|
27
|
+
attr_reader :config
|
28
|
+
attr_reader :cloud_id
|
29
|
+
|
30
|
+
# @param mommacat [MU::MommaCat]: A {MU::Mommacat} object containing the deploy of which this resource is/will be a member.
|
31
|
+
# @param kitten_cfg [Hash]: The fully parsed and resolved {MU::Config} resource descriptor as defined in {MU::Config::BasketofKittens::logs}
|
32
|
+
def initialize(mommacat: nil, kitten_cfg: nil, mu_name: nil, cloud_id: nil)
|
33
|
+
@deploy = mommacat
|
34
|
+
@config = MU::Config.manxify(kitten_cfg)
|
35
|
+
@cloud_id ||= cloud_id
|
36
|
+
@mu_name ||= @deploy.getResourceName(@config["name"])
|
37
|
+
end
|
38
|
+
|
39
|
+
# Called automatically by {MU::Deploy#createResources}
|
40
|
+
def create
|
41
|
+
bucket_name = @deploy.getResourceName(@config["name"], max_length: 63).downcase
|
42
|
+
MU::Cloud::AWS.s3(credentials: @config['credentials'], region: @config['region']).create_bucket(
|
43
|
+
acl: @config['acl'],
|
44
|
+
bucket: bucket_name
|
45
|
+
)
|
46
|
+
@cloud_id = bucket_name
|
47
|
+
|
48
|
+
@@region_cache_semaphore.synchronize {
|
49
|
+
@@region_cache[@cloud_id] ||= @config['region']
|
50
|
+
}
|
51
|
+
|
52
|
+
tagBucket if !@config['scrub_mu_isms']
|
53
|
+
end
|
54
|
+
|
55
|
+
# Apply tags to this bucket object
|
56
|
+
def tagBucket
|
57
|
+
tagset = []
|
58
|
+
|
59
|
+
MU::MommaCat.listStandardTags.each_pair { |key, value|
|
60
|
+
tagset << { :key => key, :value => value }
|
61
|
+
}
|
62
|
+
|
63
|
+
if @config['tags']
|
64
|
+
@config['tags'].each { |tag|
|
65
|
+
tagset << { :key => tag['key'], :value => tag['value'] }
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
if @config['optional_tags']
|
70
|
+
MU::MommaCat.listOptionalTags.each { |key, value|
|
71
|
+
tagset << { :key => key, :value => value }
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
MU::Cloud::AWS.s3(credentials: @config['credentials'], region: @config['region']).put_bucket_tagging(
|
76
|
+
bucket: @cloud_id,
|
77
|
+
tagging: {
|
78
|
+
tag_set: tagset
|
79
|
+
}
|
80
|
+
)
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
# Called automatically by {MU::Deploy#createResources}
|
85
|
+
def groom
|
86
|
+
@@region_cache_semaphore.synchronize {
|
87
|
+
@@region_cache[@cloud_id] ||= @config['region']
|
88
|
+
}
|
89
|
+
tagBucket if !@config['scrub_mu_isms']
|
90
|
+
|
91
|
+
current = cloud_desc
|
92
|
+
|
93
|
+
if @config['web'] and current["website"].nil?
|
94
|
+
MU.log "Enabling web service on S3 bucket #{@cloud_id}", MU::NOTICE
|
95
|
+
MU::Cloud::AWS.s3(credentials: @config['credentials'], region: @config['region']).put_bucket_website(
|
96
|
+
bucket: @cloud_id,
|
97
|
+
website_configuration: {
|
98
|
+
error_document: {
|
99
|
+
key: @config['web_error_object']
|
100
|
+
},
|
101
|
+
index_document: {
|
102
|
+
suffix: @config['web_index_object']
|
103
|
+
}
|
104
|
+
}
|
105
|
+
)
|
106
|
+
elsif !@config['web'] and !current["website"].nil?
|
107
|
+
MU.log "Disabling web service on S3 bucket #{@cloud_id}", MU::NOTICE
|
108
|
+
MU::Cloud::AWS.s3(credentials: @config['credentials'], region: @config['region']).delete_bucket_website(
|
109
|
+
bucket: @cloud_id
|
110
|
+
)
|
111
|
+
end
|
112
|
+
|
113
|
+
if @config['versioning'] and current["versioning"].status != "Enabled"
|
114
|
+
MU.log "Enabling versioning on S3 bucket #{@cloud_id}", MU::NOTICE
|
115
|
+
MU::Cloud::AWS.s3(credentials: @config['credentials'], region: @config['region']).put_bucket_versioning(
|
116
|
+
bucket: @cloud_id,
|
117
|
+
versioning_configuration: {
|
118
|
+
mfa_delete: "Disabled",
|
119
|
+
status: "Enabled"
|
120
|
+
}
|
121
|
+
)
|
122
|
+
elsif !@config['versioning'] and current["versioning"].status == "Enabled"
|
123
|
+
MU.log "Suspending versioning on S3 bucket #{@cloud_id}", MU::NOTICE
|
124
|
+
MU::Cloud::AWS.s3(credentials: @config['credentials'], region: @config['region']).put_bucket_versioning(
|
125
|
+
bucket: @cloud_id,
|
126
|
+
versioning_configuration: {
|
127
|
+
mfa_delete: "Disabled",
|
128
|
+
status: "Suspended"
|
129
|
+
}
|
130
|
+
)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Does this resource type exist as a global (cloud-wide) artifact, or
|
135
|
+
# is it localized to a region/zone?
|
136
|
+
# @return [Boolean]
|
137
|
+
def self.isGlobal?
|
138
|
+
false
|
139
|
+
end
|
140
|
+
|
141
|
+
# Remove all buckets associated with the currently loaded deployment.
|
142
|
+
# @param noop [Boolean]: If true, will only print what would be done
|
143
|
+
# @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
|
144
|
+
# @param region [String]: The cloud provider region
|
145
|
+
# @return [void]
|
146
|
+
def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
147
|
+
|
148
|
+
resp = MU::Cloud::AWS.s3(credentials: credentials, region: region).list_buckets
|
149
|
+
if resp and resp.buckets
|
150
|
+
resp.buckets.each { |bucket|
|
151
|
+
@@region_cache_semaphore.synchronize {
|
152
|
+
if @@region_cache[bucket.name]
|
153
|
+
next if @@region_cache[bucket.name] != region
|
154
|
+
else
|
155
|
+
location = MU::Cloud::AWS.s3(credentials: credentials, region: region).get_bucket_location(bucket: bucket.name).location_constraint
|
156
|
+
|
157
|
+
if location.nil? or location.empty?
|
158
|
+
@@region_cache[bucket.name] = region
|
159
|
+
else
|
160
|
+
@@region_cache[bucket.name] = location
|
161
|
+
end
|
162
|
+
end
|
163
|
+
}
|
164
|
+
|
165
|
+
if @@region_cache[bucket.name] != region
|
166
|
+
MU.log "#{bucket.name} is in #{@@region_cache[bucket.name]} but I'm checking from #{region}, skipping", MU::DEBUG
|
167
|
+
next
|
168
|
+
end
|
169
|
+
|
170
|
+
begin
|
171
|
+
tags = MU::Cloud::AWS.s3(credentials: credentials, region: region).get_bucket_tagging(bucket: bucket.name).tag_set
|
172
|
+
tags.each { |tag|
|
173
|
+
if tag.key == "MU-ID" and tag.value == MU.deploy_id
|
174
|
+
MU.log "Deleting S3 Bucket #{bucket.name}"
|
175
|
+
if !noop
|
176
|
+
MU::Cloud::AWS.s3(credentials: credentials, region: region).delete_bucket(bucket: bucket.name)
|
177
|
+
end
|
178
|
+
break
|
179
|
+
end
|
180
|
+
}
|
181
|
+
rescue Aws::S3::Errors::NoSuchTagSet, Aws::S3::Errors::PermanentRedirect
|
182
|
+
next
|
183
|
+
end
|
184
|
+
}
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Canonical Amazon Resource Number for this resource
|
189
|
+
# @return [String]
|
190
|
+
def arn
|
191
|
+
"arn:"+(MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws")+":s3:::"+@cloud_id
|
192
|
+
end
|
193
|
+
|
194
|
+
# Return the metadata for this user cofiguration
|
195
|
+
# @return [Hash]
|
196
|
+
def notify
|
197
|
+
desc = MU::Cloud::AWS::Bucket.describe_bucket(@cloud_id, credentials: @config['credentials'], region: @config['region'])
|
198
|
+
MU.structToHash(desc)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Locate an existing bucket.
|
202
|
+
# @param cloud_id [String]: The cloud provider's identifier for this resource.
|
203
|
+
# @param region [String]: The cloud provider region.
|
204
|
+
# @param flags [Hash]: Optional flags
|
205
|
+
# @return [OpenStruct]: The cloud provider's complete descriptions of matching bucket.
|
206
|
+
def self.find(cloud_id: nil, region: MU.curRegion, credentials: nil, flags: {})
|
207
|
+
found = {}
|
208
|
+
if cloud_id
|
209
|
+
found[cloud_id] = describe_bucket(cloud_id, minimal: true, credentials: credentials, region: region)
|
210
|
+
end
|
211
|
+
found
|
212
|
+
end
|
213
|
+
|
214
|
+
# Cloud-specific configuration properties.
|
215
|
+
# @param config [MU::Config]: The calling MU::Config object
|
216
|
+
# @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
|
217
|
+
def self.schema(config)
|
218
|
+
toplevel_required = []
|
219
|
+
schema = {
|
220
|
+
"acl" => {
|
221
|
+
"type" => "string",
|
222
|
+
"enum" => ["private", "public-read", "public-read-write", "authenticated-read"],
|
223
|
+
"default" => "private"
|
224
|
+
},
|
225
|
+
"storage_class" => {
|
226
|
+
"type" => "string",
|
227
|
+
"enum" => ["STANDARD", "REDUCED_REDUNDANCY", "STANDARD_IA", "ONEZONE_IA", "INTELLIGENT_TIERING", "GLACIER"],
|
228
|
+
"default" => "STANDARD"
|
229
|
+
}
|
230
|
+
}
|
231
|
+
[toplevel_required, schema]
|
232
|
+
end
|
233
|
+
|
234
|
+
# Cloud-specific pre-processing of {MU::Config::BasketofKittens::bucket}, bare and unvalidated.
|
235
|
+
|
236
|
+
# @param bucket [Hash]: The resource to process and validate
|
237
|
+
# @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
|
238
|
+
# @return [Boolean]: True if validation succeeded, False otherwise
|
239
|
+
def self.validateConfig(bucket, configurator)
|
240
|
+
ok = true
|
241
|
+
|
242
|
+
ok
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
# AWS doesn't really implement a useful describe_ method for S3 buckets;
|
248
|
+
# instead we run the million little individual API calls to construct
|
249
|
+
# an approximation for our uses
|
250
|
+
def self.describe_bucket(bucket, minimal: false, credentials: nil, region: nil)
|
251
|
+
@@region_cache = {}
|
252
|
+
@@region_cache_semaphore = Mutex.new
|
253
|
+
calls = if minimal
|
254
|
+
%w{encryption lifecycle lifecycle_configuration location logging policy replication tagging versioning website}
|
255
|
+
else
|
256
|
+
%w{accelerate_configuration acl cors encryption lifecycle lifecycle_configuration location logging notification notification_configuration policy policy_status replication request_payment tagging versioning website} # XXX analytics_configuration, inventory_configuration, metrics_configuration all require an id of some sort
|
257
|
+
end
|
258
|
+
|
259
|
+
desc = {}
|
260
|
+
|
261
|
+
calls.each { |method|
|
262
|
+
method_sym = ("get_bucket_"+method).to_sym
|
263
|
+
# "The horrors of this place claw at your mind"
|
264
|
+
begin
|
265
|
+
desc[method] = MU::Cloud::AWS.s3(credentials: credentials, region: region).method_missing(method_sym, {:bucket => bucket})
|
266
|
+
if method == "location"
|
267
|
+
@@region_cache_semaphore.synchronize {
|
268
|
+
if desc[method].location_constraint.nil? or desc[method].location_constraint.empty?
|
269
|
+
@@region_cache[bucket] = region
|
270
|
+
else
|
271
|
+
@@region_cache[bucket] = desc[method].location_constraint
|
272
|
+
end
|
273
|
+
}
|
274
|
+
end
|
275
|
+
|
276
|
+
rescue Aws::S3::Errors::NoSuchCORSConfiguration, Aws::S3::Errors::ServerSideEncryptionConfigurationNotFoundError, Aws::S3::Errors::NoSuchLifecycleConfiguration, Aws::S3::Errors::NoSuchBucketPolicy, Aws::S3::Errors::ReplicationConfigurationNotFoundError, Aws::S3::Errors::NoSuchTagSet, Aws::S3::Errors::NoSuchWebsiteConfiguration => e
|
277
|
+
desc[method] = nil
|
278
|
+
next
|
279
|
+
end
|
280
|
+
}
|
281
|
+
desc
|
282
|
+
end
|
283
|
+
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
@@ -364,18 +364,16 @@ module MU
|
|
364
364
|
end
|
365
365
|
elsif @config["creation_style"] == "new"
|
366
366
|
MU.log "Creating pristine database instance #{@config['identifier']} (#{@config['name']}) in #{@config['region']}"
|
367
|
-
puts @config['credentials']
|
368
|
-
pp config
|
369
367
|
resp = MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).create_db_instance(config)
|
370
368
|
end
|
371
369
|
rescue Aws::RDS::Errors::InvalidParameterValue => e
|
372
370
|
if attempts < 5
|
373
|
-
MU.log "Got #{e.inspect} creating #{@config['identifier']}, will retry a few times in case of transient errors.", MU::WARN
|
371
|
+
MU.log "Got #{e.inspect} creating #{@config['identifier']}, will retry a few times in case of transient errors.", MU::WARN, details: config
|
374
372
|
attempts += 1
|
375
373
|
sleep 10
|
376
374
|
retry
|
377
375
|
else
|
378
|
-
raise MuError, "Exhausted retries trying to create database instance #{@config['identifier']}: e.inspect"
|
376
|
+
raise MuError, "Exhausted retries trying to create database instance #{@config['identifier']}: #{e.inspect}"
|
379
377
|
end
|
380
378
|
end
|
381
379
|
|
@@ -497,6 +495,18 @@ pp config
|
|
497
495
|
}
|
498
496
|
cluster_config_struct[:port] = @config["port"] if @config["port"]
|
499
497
|
|
498
|
+
if @config['cluster_mode']
|
499
|
+
cluster_config_struct[:engine_mode] = @config['cluster_mode']
|
500
|
+
if @config['cluster_mode'] == "serverless"
|
501
|
+
cluster_config_struct[:scaling_configuration] = {
|
502
|
+
:auto_pause => @config['serverless_scaling']['auto_pause'],
|
503
|
+
:min_capacity => @config['serverless_scaling']['min_capacity'],
|
504
|
+
:max_capacity => @config['serverless_scaling']['max_capacity'],
|
505
|
+
:seconds_until_auto_pause => @config['serverless_scaling']['seconds_until_auto_pause']
|
506
|
+
}
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
500
510
|
if %w{existing_snapshot new_snapshot}.include?(@config["creation_style"])
|
501
511
|
cluster_config_struct[:snapshot_identifier] = @config["snapshot_id"]
|
502
512
|
cluster_config_struct[:engine] = @config["engine"]
|
@@ -537,12 +547,13 @@ pp config
|
|
537
547
|
end
|
538
548
|
rescue Aws::RDS::Errors::InvalidParameterValue => e
|
539
549
|
if attempts < 5
|
540
|
-
MU.log "Got #{e.inspect} while creating database cluster #{@config['identifier']}, will retry a few times in case of transient errors.", MU::WARN
|
550
|
+
MU.log "Got #{e.inspect} while creating database cluster #{@config['identifier']}, will retry a few times in case of transient errors.", MU::WARN, details: cluster_config_struct
|
541
551
|
attempts += 1
|
542
552
|
sleep 10
|
543
553
|
retry
|
544
554
|
else
|
545
|
-
|
555
|
+
MU.log "Exhausted retries trying to create database cluster #{@config['identifier']}", MU::ERR, details: e.inspect
|
556
|
+
raise MuError, "Exhausted retries trying to create database cluster #{@config['identifier']}"
|
546
557
|
end
|
547
558
|
end
|
548
559
|
|
@@ -1414,6 +1425,45 @@ pp config
|
|
1414
1425
|
schema = {
|
1415
1426
|
"db_parameter_group_parameters" => rds_parameters_primitive,
|
1416
1427
|
"cluster_parameter_group_parameters" => rds_parameters_primitive,
|
1428
|
+
"cluster_mode" => {
|
1429
|
+
"type" => "string",
|
1430
|
+
"description" => "The DB engine mode of the DB cluster",
|
1431
|
+
"enum" => ["provisioned", "serverless", "parallelquery", "global"],
|
1432
|
+
"default" => "provisioned"
|
1433
|
+
},
|
1434
|
+
"serverless_scaling" => {
|
1435
|
+
"type" => "object",
|
1436
|
+
"descriptions" => "Scaling configuration for a +serverless+ Aurora cluster",
|
1437
|
+
"default" => {
|
1438
|
+
"auto_pause" => false,
|
1439
|
+
"min_capacity" => 2,
|
1440
|
+
"max_capacity" => 2
|
1441
|
+
},
|
1442
|
+
"properties" => {
|
1443
|
+
"auto_pause" => {
|
1444
|
+
"type" => "boolean",
|
1445
|
+
"description" => "A value that specifies whether to allow or disallow automatic pause for an Aurora DB cluster in serverless DB engine mode",
|
1446
|
+
"default" => false
|
1447
|
+
},
|
1448
|
+
"min_capacity" => {
|
1449
|
+
"type" => "integer",
|
1450
|
+
"description" => "The minimum capacity for an Aurora DB cluster in serverless DB engine mode.",
|
1451
|
+
"default" => 2,
|
1452
|
+
"enum" => [2, 4, 8, 16, 32, 64, 128, 256]
|
1453
|
+
},
|
1454
|
+
"max_capacity" => {
|
1455
|
+
"type" => "integer",
|
1456
|
+
"description" => "The maximum capacity for an Aurora DB cluster in serverless DB engine mode.",
|
1457
|
+
"default" => 2,
|
1458
|
+
"enum" => [2, 4, 8, 16, 32, 64, 128, 256]
|
1459
|
+
},
|
1460
|
+
"seconds_until_auto_pause" => {
|
1461
|
+
"type" => "integer",
|
1462
|
+
"description" => "A DB cluster can be paused only when it's idle (it has no connections). If a DB cluster is paused for more than seven days, the DB cluster might be backed up with a snapshot. In this case, the DB cluster is restored when there is a request to connect to it.",
|
1463
|
+
"default" => 86400
|
1464
|
+
}
|
1465
|
+
}
|
1466
|
+
},
|
1417
1467
|
"license_model" => {
|
1418
1468
|
"type" => "string",
|
1419
1469
|
"enum" => ["license-included", "bring-your-own-license", "general-public-license", "postgresql-license"]
|
@@ -1452,7 +1502,11 @@ pp config
|
|
1452
1502
|
if db['create_cluster'] or db['engine'] == "aurora" or db["member_of_cluster"]
|
1453
1503
|
case db['engine']
|
1454
1504
|
when "mysql", "aurora", "aurora-mysql"
|
1455
|
-
db["
|
1505
|
+
if db["engine_version"] == "5.6" or db["cluster_mode"] == "serverless"
|
1506
|
+
db["engine"] = "aurora"
|
1507
|
+
else
|
1508
|
+
db["engine"] = "aurora-mysql"
|
1509
|
+
end
|
1456
1510
|
when "postgres", "postgresql", "postgresql-mysql"
|
1457
1511
|
db["engine"] = "aurora-postgresql"
|
1458
1512
|
else
|
@@ -1671,14 +1725,14 @@ pp config
|
|
1671
1725
|
end
|
1672
1726
|
|
1673
1727
|
# Cleanup the database vault
|
1674
|
-
|
1728
|
+
groomer =
|
1675
1729
|
if database_obj
|
1676
1730
|
database_obj.config.has_key?("groomer") ? database_obj.config["groomer"] : MU::Config.defaultGroomer
|
1677
1731
|
else
|
1678
1732
|
MU::Config.defaultGroomer
|
1679
1733
|
end
|
1680
1734
|
|
1681
|
-
groomclass = MU::Groomer.loadGroomer(
|
1735
|
+
groomclass = MU::Groomer.loadGroomer(groomer)
|
1682
1736
|
groomclass.deleteSecret(vault: db_id.upcase) if !noop
|
1683
1737
|
MU.log "#{db_id} has been terminated"
|
1684
1738
|
end
|
@@ -1761,14 +1815,14 @@ pp config
|
|
1761
1815
|
end
|
1762
1816
|
|
1763
1817
|
# Cleanup the cluster vault
|
1764
|
-
|
1818
|
+
groomer =
|
1765
1819
|
if cluster_obj
|
1766
1820
|
cluster_obj.config.has_key?("groomer") ? cluster_obj.config["groomer"] : MU::Config.defaultGroomer
|
1767
1821
|
else
|
1768
1822
|
MU::Config.defaultGroomer
|
1769
1823
|
end
|
1770
1824
|
|
1771
|
-
groomclass = MU::Groomer.loadGroomer(
|
1825
|
+
groomclass = MU::Groomer.loadGroomer(groomer)
|
1772
1826
|
groomclass.deleteSecret(vault: cluster_id.upcase) if !noop
|
1773
1827
|
|
1774
1828
|
MU.log "#{cluster_id} has been terminated"
|
@@ -0,0 +1,592 @@
|
|
1
|
+
module MU
|
2
|
+
class Cloud
|
3
|
+
class AWS
|
4
|
+
# An API as configured in {MU::Config::BasketofKittens::endpoints}
|
5
|
+
class Endpoint < MU::Cloud::Endpoint
|
6
|
+
@deploy = nil
|
7
|
+
@config = nil
|
8
|
+
attr_reader :mu_name
|
9
|
+
attr_reader :config
|
10
|
+
attr_reader :cloud_id
|
11
|
+
|
12
|
+
@cloudformation_data = {}
|
13
|
+
attr_reader :cloudformation_data
|
14
|
+
|
15
|
+
# @param mommacat [MU::MommaCat]: A {MU::Mommacat} object containing the deploy of which this resource is/will be a member.
|
16
|
+
# @param kitten_cfg [Hash]: The fully parsed and resolved {MU::Config} resource descriptor as defined in {MU::Config::BasketofKittens::endpoints}
|
17
|
+
def initialize(mommacat: nil, kitten_cfg: nil, mu_name: nil, cloud_id: nil)
|
18
|
+
@deploy = mommacat
|
19
|
+
@config = MU::Config.manxify(kitten_cfg)
|
20
|
+
@cloud_id ||= cloud_id
|
21
|
+
@mu_name ||= @deploy.getResourceName(@config["name"])
|
22
|
+
end
|
23
|
+
|
24
|
+
# Called automatically by {MU::Deploy#createResources}
|
25
|
+
def create
|
26
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).create_rest_api(
|
27
|
+
name: @mu_name,
|
28
|
+
description: @deploy.deploy_id,
|
29
|
+
endpoint_configuration: {
|
30
|
+
types: ["REGIONAL"] # XXX expose in BoK ["REGIONAL", "EDGE", "PRIVATE"]
|
31
|
+
}
|
32
|
+
)
|
33
|
+
@cloud_id = resp.id
|
34
|
+
generate_methods
|
35
|
+
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
# Create/update all of the methods declared for this endpoint
|
40
|
+
def generate_methods
|
41
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).get_resources(
|
42
|
+
rest_api_id: @cloud_id,
|
43
|
+
)
|
44
|
+
root_resource = resp.items.first.id
|
45
|
+
|
46
|
+
# TODO guard this crap so we don't touch it if there are no changes
|
47
|
+
@config['methods'].each { |m|
|
48
|
+
method_arn = "arn:#{MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws"}:execute-api:#{@config["region"]}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:#{@cloud_id}/*/#{m['type']}/#{m['path']}"
|
49
|
+
|
50
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).get_resources(
|
51
|
+
rest_api_id: @cloud_id
|
52
|
+
)
|
53
|
+
ext_resource = nil
|
54
|
+
resp.items.each { |resource|
|
55
|
+
if resource.path_part == m['path']
|
56
|
+
ext_resource = resource.id
|
57
|
+
end
|
58
|
+
}
|
59
|
+
|
60
|
+
resp = if ext_resource
|
61
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).get_resource(
|
62
|
+
rest_api_id: @cloud_id,
|
63
|
+
resource_id: ext_resource,
|
64
|
+
)
|
65
|
+
# MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).update_resource(
|
66
|
+
# rest_api_id: @cloud_id,
|
67
|
+
# resource_id: ext_resource,
|
68
|
+
# patch_operations: [
|
69
|
+
# {
|
70
|
+
# op: "replace",
|
71
|
+
# path: "XXX ??",
|
72
|
+
# value: m["path"]
|
73
|
+
# }
|
74
|
+
# ]
|
75
|
+
# )
|
76
|
+
else
|
77
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).create_resource(
|
78
|
+
rest_api_id: @cloud_id,
|
79
|
+
parent_id: root_resource,
|
80
|
+
path_part: m['path']
|
81
|
+
)
|
82
|
+
end
|
83
|
+
parent_id = resp.id
|
84
|
+
|
85
|
+
resp = begin
|
86
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).get_method(
|
87
|
+
rest_api_id: @cloud_id,
|
88
|
+
resource_id: parent_id,
|
89
|
+
http_method: m['type']
|
90
|
+
)
|
91
|
+
rescue Aws::APIGateway::Errors::NotFoundException
|
92
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).put_method(
|
93
|
+
rest_api_id: @cloud_id,
|
94
|
+
resource_id: parent_id,
|
95
|
+
authorization_type: m['auth'],
|
96
|
+
http_method: m['type']
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
# XXX effectively a placeholder default
|
101
|
+
begin
|
102
|
+
m['responses'].each { |r|
|
103
|
+
params = {
|
104
|
+
:rest_api_id => @cloud_id,
|
105
|
+
:resource_id => parent_id,
|
106
|
+
:http_method => m['type'],
|
107
|
+
:status_code => r['code'].to_s
|
108
|
+
}
|
109
|
+
if r['headers']
|
110
|
+
params[:response_parameters] = r['headers'].map { |h|
|
111
|
+
["method.response.header."+h['header'], h['required']]
|
112
|
+
}.to_h
|
113
|
+
end
|
114
|
+
|
115
|
+
if r['body']
|
116
|
+
# XXX I'm guessing we can also have arbirary user-defined models somehow, so is_error is probably inadequate to the demand of the times
|
117
|
+
params[:response_models] = r['body'].map { |b| [b['content_type'], b['is_error'] ? "Error" : "Empty"] }.to_h
|
118
|
+
end
|
119
|
+
|
120
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).put_method_response(params)
|
121
|
+
}
|
122
|
+
rescue Aws::APIGateway::Errors::ConflictException
|
123
|
+
# fine to ignore
|
124
|
+
end
|
125
|
+
|
126
|
+
if m['integrate_with']
|
127
|
+
role_arn = if m['iam_role']
|
128
|
+
if m['iam_role'].match(/^arn:/)
|
129
|
+
m['iam_role']
|
130
|
+
else
|
131
|
+
sib_role = @deploy.findLitterMate(name: m['iam_role'], type: "roles")
|
132
|
+
sib_role.cloudobj.arn
|
133
|
+
# XXX make this more like get_role_arn in Function, or just use Role.find?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
function_obj = nil
|
138
|
+
|
139
|
+
uri, type = if m['integrate_with']['type'] == "aws_generic"
|
140
|
+
svc, action = m['integrate_with']['aws_generic_action'].split(/:/)
|
141
|
+
["arn:aws:apigateway:"+@config['region']+":#{svc}:action/#{action}", "AWS"]
|
142
|
+
elsif m['integrate_with']['type'] == "function"
|
143
|
+
function_obj = @deploy.findLitterMate(name: m['integrate_with']['name'], type: "functions").cloudobj
|
144
|
+
["arn:aws:apigateway:"+@config['region']+":lambda:path/2015-03-31/functions/"+function_obj.arn+"/invocations", "AWS"]
|
145
|
+
elsif m['integrate_with']['type'] == "mock"
|
146
|
+
[nil, "MOCK"]
|
147
|
+
end
|
148
|
+
|
149
|
+
params = {
|
150
|
+
:rest_api_id => @cloud_id,
|
151
|
+
:resource_id => parent_id,
|
152
|
+
:type => type, # XXX Lambda and Firehose can do AWS_PROXY
|
153
|
+
:content_handling => "CONVERT_TO_TEXT", # XXX expose in BoK
|
154
|
+
:http_method => m['type']
|
155
|
+
# credentials: role_arn
|
156
|
+
}
|
157
|
+
params[:uri] = uri if uri
|
158
|
+
|
159
|
+
if m['integrate_with']['type'] != "mock"
|
160
|
+
params[:integration_http_method] = m['integrate_with']['backend_http_method']
|
161
|
+
else
|
162
|
+
params[:integration_http_method] = nil
|
163
|
+
end
|
164
|
+
|
165
|
+
if m['integrate_with']['passthrough_behavior']
|
166
|
+
params[:passthrough_behavior] = m['integrate_with']['passthrough_behavior']
|
167
|
+
end
|
168
|
+
if m['integrate_with']['request_templates']
|
169
|
+
params[:request_templates] = {}
|
170
|
+
m['integrate_with']['request_templates'].each { |rt|
|
171
|
+
params[:request_templates][rt['content_type']] = rt['template']
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).put_integration(params)
|
176
|
+
|
177
|
+
if m['integrate_with']['type'] == "function"
|
178
|
+
function_obj.addTrigger(method_arn, "apigateway", @config['name'])
|
179
|
+
end
|
180
|
+
|
181
|
+
m['responses'].each { |r|
|
182
|
+
params = {
|
183
|
+
:rest_api_id => @cloud_id,
|
184
|
+
:resource_id => parent_id,
|
185
|
+
:http_method => m['type'],
|
186
|
+
:status_code => r['code'].to_s,
|
187
|
+
:selection_pattern => ""
|
188
|
+
}
|
189
|
+
if r['headers']
|
190
|
+
params[:response_parameters] = r['headers'].map { |h|
|
191
|
+
["method.response.header."+h['header'], "'"+h['value']+"'"]
|
192
|
+
}.to_h
|
193
|
+
end
|
194
|
+
|
195
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).put_integration_response(params)
|
196
|
+
|
197
|
+
}
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
}
|
202
|
+
end
|
203
|
+
|
204
|
+
# Called automatically by {MU::Deploy#createResources}
|
205
|
+
def groom
|
206
|
+
generate_methods
|
207
|
+
|
208
|
+
MU.log "Deploying API Gateway #{@config['name']} to #{@config['deploy_to']}"
|
209
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).create_deployment(
|
210
|
+
rest_api_id: @cloud_id,
|
211
|
+
stage_name: @config['deploy_to']
|
212
|
+
# cache_cluster_enabled: false,
|
213
|
+
# cache_cluster_size: 0.5,
|
214
|
+
)
|
215
|
+
deployment_id = resp.id
|
216
|
+
# this automatically creates a stage with the same name, so we don't
|
217
|
+
# have to deal with that
|
218
|
+
|
219
|
+
my_url = "https://"+@cloud_id+".execute-api."+@config['region']+".amazonaws.com/"+@config['deploy_to']
|
220
|
+
MU.log "API Endpoint #{@config['name']}: "+my_url, MU::SUMMARY
|
221
|
+
|
222
|
+
# resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).create_authorizer(
|
223
|
+
# rest_api_id: @cloud_id,
|
224
|
+
# )
|
225
|
+
|
226
|
+
# resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).create_vpc_link(
|
227
|
+
# )
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
# @return [Struct]
|
232
|
+
def cloud_desc
|
233
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).get_rest_api(
|
234
|
+
rest_api_id: @cloud_id
|
235
|
+
)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Return the metadata for this API
|
239
|
+
# @return [Hash]
|
240
|
+
def notify
|
241
|
+
deploy_struct = MU.structToHash(cloud_desc)
|
242
|
+
# XXX stages and whatnot
|
243
|
+
return deploy_struct
|
244
|
+
end
|
245
|
+
|
246
|
+
# Remove all APIs associated with the currently loaded deployment.
|
247
|
+
# @param noop [Boolean]: If true, will only print what would be done
|
248
|
+
# @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
|
249
|
+
# @param region [String]: The cloud provider region
|
250
|
+
# @return [void]
|
251
|
+
def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
252
|
+
resp = MU::Cloud::AWS.apig(region: region, credentials: credentials).get_rest_apis
|
253
|
+
if resp and resp.items
|
254
|
+
resp.items.each { |api|
|
255
|
+
# The stupid things don't have tags
|
256
|
+
if api.description == MU.deploy_id
|
257
|
+
MU.log "Deleting API Gateway #{api.name} (#{api.id})"
|
258
|
+
if !noop
|
259
|
+
MU::Cloud::AWS.apig(region: region, credentials: credentials).delete_rest_api(
|
260
|
+
rest_api_id: api.id
|
261
|
+
)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
}
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Locate an existing API.
|
269
|
+
# @param cloud_id [String]: The cloud provider's identifier for this resource.
|
270
|
+
# @param region [String]: The cloud provider region.
|
271
|
+
# @param flags [Hash]: Optional flags
|
272
|
+
# @return [OpenStruct]: The cloud provider's complete descriptions of matching API.
|
273
|
+
def self.find(cloud_id: nil, region: MU.curRegion, credentials: nil, flags: {})
|
274
|
+
if cloud_id
|
275
|
+
return MU::Cloud::AWS.apig(region: region, credentials: credentials).get_rest_api(
|
276
|
+
rest_api_id: cloud_id
|
277
|
+
)
|
278
|
+
end
|
279
|
+
# resp = MU::Cloud::AWS.apig(region: region, credentials: credentials).get_rest_apis
|
280
|
+
# if resp and resp.items
|
281
|
+
# resp.items.each { |api|
|
282
|
+
# }
|
283
|
+
# end
|
284
|
+
nil
|
285
|
+
end
|
286
|
+
|
287
|
+
# Cloud-specific configuration properties.
|
288
|
+
# @param config [MU::Config]: The calling MU::Config object
|
289
|
+
# @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
|
290
|
+
def self.schema(config)
|
291
|
+
toplevel_required = []
|
292
|
+
schema = {
|
293
|
+
"deploy_to" => {
|
294
|
+
"type" => "string",
|
295
|
+
"description" => "The name of an environment under which to deploy our API. If not specified, will deploy to the name of the global Mu environment for this deployment."
|
296
|
+
},
|
297
|
+
"methods" => {
|
298
|
+
"items" => {
|
299
|
+
"type" => "object",
|
300
|
+
"description" => "Other cloud resources to integrate as a back end to this API Gateway",
|
301
|
+
"required" => ["integrate_with"],
|
302
|
+
"properties" => {
|
303
|
+
"integrate_with" => {
|
304
|
+
"type" => "object",
|
305
|
+
"description" => "Specify what application backend to invoke under this path/method combination",
|
306
|
+
"properties" => {
|
307
|
+
"proxy" => {
|
308
|
+
"type" => "boolean",
|
309
|
+
"default" => false,
|
310
|
+
"description" => "For HTTP or AWS integrations, specify whether the target is a proxy (((docs unclear, is that actually what this means?)))" # XXX is that actually what this means?
|
311
|
+
},
|
312
|
+
"backend_http_method" => {
|
313
|
+
"type" => "string",
|
314
|
+
"description" => "The HTTP method to use when contacting our integrated backend. If not specified, this will be set to match our front end.",
|
315
|
+
"enum" => ["GET", "POST", "PUT", "HEAD", "DELETE", "CONNECT", "OPTIONS", "TRACE"],
|
316
|
+
},
|
317
|
+
"url" => {
|
318
|
+
"type" => "string",
|
319
|
+
"description" => "For HTTP or HTTP_PROXY integrations, this should be a fully-qualified URL"
|
320
|
+
},
|
321
|
+
"responses"=> {
|
322
|
+
"type" => "array",
|
323
|
+
"items" => {
|
324
|
+
"type" => "object",
|
325
|
+
"description" => "Customize the response to the client for this method, by adding headers or transforming through a template. If not specified, we will default to returning an un-transformed HTTP 200 for this method.",
|
326
|
+
"properties" => {
|
327
|
+
"code" => {
|
328
|
+
"type" => "integer",
|
329
|
+
"description" => "The HTTP status code to return",
|
330
|
+
"default" => 200
|
331
|
+
},
|
332
|
+
"headers" => {
|
333
|
+
"type" => "array",
|
334
|
+
"items" => {
|
335
|
+
"description" => "One or more headers, used by the API Gateway integration response and filtered through the method response before returning to the client",
|
336
|
+
"type" => "object",
|
337
|
+
"properties" => {
|
338
|
+
"header" => {
|
339
|
+
"type" => "string",
|
340
|
+
"description" => "The name of a header to return, such as +Access-Control-Allow-Methods+"
|
341
|
+
},
|
342
|
+
"value" => {
|
343
|
+
"type" => "string",
|
344
|
+
"description" => "The string to map to this header (ex +GET,OPTIONS+)"
|
345
|
+
},
|
346
|
+
"required" => {
|
347
|
+
"type" => "boolean",
|
348
|
+
"description" => "Indicate whether this header is required in order to return a response",
|
349
|
+
"default" => true
|
350
|
+
}
|
351
|
+
}
|
352
|
+
}
|
353
|
+
},
|
354
|
+
"body" => {
|
355
|
+
"type" => "array",
|
356
|
+
"items" => {
|
357
|
+
"type" => "object",
|
358
|
+
"description" => "Model for the body of our backend integration's response",
|
359
|
+
"properties" => {
|
360
|
+
"content_type" => {
|
361
|
+
"type" => "string",
|
362
|
+
"description" => "An HTTP content type to match to a response, such as +application/json+."
|
363
|
+
},
|
364
|
+
"is_error" => {
|
365
|
+
"type" => "boolean",
|
366
|
+
"description" => "Whether this response should be considered an error",
|
367
|
+
"default" => false
|
368
|
+
}
|
369
|
+
}
|
370
|
+
}
|
371
|
+
}
|
372
|
+
}
|
373
|
+
}
|
374
|
+
},
|
375
|
+
"arn" => {
|
376
|
+
"type" => "string",
|
377
|
+
"description" => "For AWS or AWS_PROXY integrations with a compatible Amazon resource outside of Mu, a full-qualified ARN such as `arn:aws:apigateway:us-west-2:s3:action/GetObject&Bucket=`bucket&Key=key`"
|
378
|
+
},
|
379
|
+
"name" => {
|
380
|
+
"type" => "string",
|
381
|
+
"description" => "A Mu resource name, for integrations with a sibling resource (e.g. a Function)"
|
382
|
+
},
|
383
|
+
"cors" => {
|
384
|
+
"type" => "boolean",
|
385
|
+
"description" => "When enabled, this will create an +OPTIONS+ method under this path with request and response header mappings that implement Cross-Origin Resource Sharing",
|
386
|
+
"default" => true
|
387
|
+
},
|
388
|
+
"type" => {
|
389
|
+
"type" => "string",
|
390
|
+
"description" => "A Mu resource type, for integrations with a sibling resource (e.g. a function), or the string +aws_generic+, which we can use in combination with +aws_generic_action+ to integrate with arbitrary AWS services.",
|
391
|
+
"enum" => ["aws_generic"].concat(MU::Cloud.resource_types.values.map { |t| t[:cfg_name] }.sort)
|
392
|
+
},
|
393
|
+
"aws_generic_action" => {
|
394
|
+
"type" => "string",
|
395
|
+
"description" => "For use when +type+ is set to +aws_generic+, this should specify the action to be performed in the style of an IAM policy action, e.g. +acm:ListCertificates+ for this integration to return a list of Certificate Manager SSL certificates."
|
396
|
+
},
|
397
|
+
"deploy_id" => {
|
398
|
+
"type" => "string",
|
399
|
+
"description" => "A Mu deploy id (e.g. DEMO-DEV-2014111400-NG), for integrations with a sibling resource (e.g. a Function)"
|
400
|
+
},
|
401
|
+
"iam_role" => {
|
402
|
+
"type" => "string",
|
403
|
+
"description" => "The name of an IAM role used to grant usage of other AWS artifacts for this integration. If not specified, we will automatically generate an appropriate role."
|
404
|
+
},
|
405
|
+
"passthrough_behavior" => {
|
406
|
+
"type" => "string",
|
407
|
+
"description" => "Specifies the pass-through behavior for incoming requests based on the +Content-Type+ header in the request, and the available mapping templates specified in +request_templates+. +WHEN_NO_MATCH+ passes the request body for unmapped content types through to the integration back end without transformation. +WHEN_NO_TEMPLATES+ allows pass-through when the integration has NO content types mapped to templates. +NEVER+ rejects unmapped content types with an HTTP +415+.",
|
408
|
+
"enum" => ["WHEN_NO_MATCH", "WHEN_NO_TEMPLATES", "NEVER"],
|
409
|
+
"default" => "WHEN_NO_MATCH"
|
410
|
+
},
|
411
|
+
"request_templates" => {
|
412
|
+
"type" => "array",
|
413
|
+
"description" => "A JSON-encoded string which represents a map of Velocity templates that are applied on the request payload based on the value of the +Content-Type+ header sent by the client. The content type value is the key in this map, and the template (as a String) is the value.",
|
414
|
+
"items" => {
|
415
|
+
"type" => "object",
|
416
|
+
"description" => "A JSON-encoded string which represents a map of Velocity templates that are applied on the request payload based on the value of the +Content-Type+ header sent by the client. The content type value is the key in this map, and the template (as a String) is the value.",
|
417
|
+
"require" => ["content_type", "template"],
|
418
|
+
"properties" => {
|
419
|
+
"content_type" => {
|
420
|
+
"type" => "string",
|
421
|
+
"description" => "An HTTP content type to match with a template, such as +application/json+."
|
422
|
+
},
|
423
|
+
"template" => {
|
424
|
+
"type" => "string",
|
425
|
+
"description" => "A Velocity template to apply to our reques payload, encoded as a one-line string, like: "+'<tt>"#set($allParams = $input.params())\\n{\\n\\"url_data_json_encoded\\":\\"$input.params(\'url\')\\"\\n}"</tt>'
|
426
|
+
}
|
427
|
+
}
|
428
|
+
}
|
429
|
+
}
|
430
|
+
}
|
431
|
+
},
|
432
|
+
"auth" => {
|
433
|
+
"type" => "string",
|
434
|
+
"enum" => ["NONE", "CUSTOM", "AWS_IAM", "COGNITO_USER_POOLS"],
|
435
|
+
"default" => "NONE"
|
436
|
+
}
|
437
|
+
}
|
438
|
+
}
|
439
|
+
}
|
440
|
+
}
|
441
|
+
[toplevel_required, schema]
|
442
|
+
end
|
443
|
+
|
444
|
+
# Does this resource type exist as a global (cloud-wide) artifact, or
|
445
|
+
# is it localized to a region/zone?
|
446
|
+
# @return [Boolean]
|
447
|
+
def self.isGlobal?
|
448
|
+
false
|
449
|
+
end
|
450
|
+
|
451
|
+
# Canonical Amazon Resource Number for this resource
|
452
|
+
# @return [String]
|
453
|
+
def arn
|
454
|
+
"arn:#{MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws"}:execute-api:#{@config["region"]}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:#{@cloud_id}"
|
455
|
+
end
|
456
|
+
|
457
|
+
|
458
|
+
# Cloud-specific pre-processing of {MU::Config::BasketofKittens::endpoints}, bare and unvalidated.
|
459
|
+
# @param endpoint [Hash]: The resource to process and validate
|
460
|
+
# @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
|
461
|
+
# @return [Boolean]: True if validation succeeded, False otherwise
|
462
|
+
def self.validateConfig(endpoint, configurator)
|
463
|
+
ok = true
|
464
|
+
|
465
|
+
append = []
|
466
|
+
endpoint['deploy_to'] ||= MU.environment || $environment || "dev"
|
467
|
+
endpoint['methods'].each { |m|
|
468
|
+
if m['integrate_with'] and m['integrate_with']['name']
|
469
|
+
if m['integrate_with']['type'] != "aws_generic"
|
470
|
+
endpoint['dependencies'] ||= []
|
471
|
+
endpoint['dependencies'] << {
|
472
|
+
"type" => m['integrate_with']['type'],
|
473
|
+
"name" => m['integrate_with']['name']
|
474
|
+
}
|
475
|
+
end
|
476
|
+
|
477
|
+
m['integrate_with']['backend_http_method'] ||= m['type']
|
478
|
+
|
479
|
+
m['responses'] ||= [
|
480
|
+
"code" => 200
|
481
|
+
]
|
482
|
+
|
483
|
+
if m['cors']
|
484
|
+
m['responses'].each { |r|
|
485
|
+
r['headers'] ||= []
|
486
|
+
r['headers'] << {
|
487
|
+
"header" => "Access-Control-Allow-Origin",
|
488
|
+
"value" => "*",
|
489
|
+
"required" => true
|
490
|
+
}
|
491
|
+
r['headers'].uniq!
|
492
|
+
}
|
493
|
+
|
494
|
+
append << cors_option_integrations(m['path'])
|
495
|
+
end
|
496
|
+
|
497
|
+
if !m['iam_role']
|
498
|
+
m['uri'] ||= "*" if m['integrate_with']['type'] == "aws_generic"
|
499
|
+
|
500
|
+
roledesc = {
|
501
|
+
"name" => endpoint['name']+"-"+m['integrate_with']['name'],
|
502
|
+
"credentials" => endpoint['credentials'],
|
503
|
+
"can_assume" => [
|
504
|
+
{
|
505
|
+
"entity_id" => "apigateway.amazonaws.com",
|
506
|
+
"entity_type" => "service"
|
507
|
+
}
|
508
|
+
],
|
509
|
+
}
|
510
|
+
if m['integrate_with']['type'] == "aws_generic"
|
511
|
+
roledesc["policies"] = [
|
512
|
+
{
|
513
|
+
"name" => m['integrate_with']['aws_generic_action'].gsub(/[^a-z]/i, ""),
|
514
|
+
"permissions" => [m['integrate_with']['aws_generic_action']],
|
515
|
+
"targets" => [{ "identifier" => m['uri'] }]
|
516
|
+
}
|
517
|
+
]
|
518
|
+
elsif m['integrate_with']['type'] == "function"
|
519
|
+
roledesc["import"] = ["AWSLambdaBasicExecutionRole"]
|
520
|
+
end
|
521
|
+
configurator.insertKitten(roledesc, "roles")
|
522
|
+
|
523
|
+
endpoint['dependencies'] ||= []
|
524
|
+
m['iam_role'] = endpoint['name']+"-"+m['integrate_with']['name']
|
525
|
+
|
526
|
+
endpoint['dependencies'] << {
|
527
|
+
"type" => "role",
|
528
|
+
"name" => endpoint['name']+"-"+m['integrate_with']['name']
|
529
|
+
}
|
530
|
+
end
|
531
|
+
end
|
532
|
+
}
|
533
|
+
endpoint['methods'].concat(append.uniq) if endpoint['methods']
|
534
|
+
# if something_bad
|
535
|
+
# ok = false
|
536
|
+
# end
|
537
|
+
|
538
|
+
ok
|
539
|
+
end
|
540
|
+
|
541
|
+
private
|
542
|
+
|
543
|
+
def self.cors_option_integrations(path)
|
544
|
+
{
|
545
|
+
"type" => "OPTIONS",
|
546
|
+
"path" => path,
|
547
|
+
"auth" => "NONE",
|
548
|
+
"responses" => [
|
549
|
+
{
|
550
|
+
"code" => 200,
|
551
|
+
"headers" => [
|
552
|
+
{
|
553
|
+
"header" => "Access-Control-Allow-Headers",
|
554
|
+
"value" => "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
|
555
|
+
"required" => true
|
556
|
+
},
|
557
|
+
{
|
558
|
+
"header" => "Access-Control-Allow-Methods",
|
559
|
+
"value" => "GET,OPTIONS",
|
560
|
+
"required" => true
|
561
|
+
},
|
562
|
+
{
|
563
|
+
"header" => "Access-Control-Allow-Origin",
|
564
|
+
"value" => "*",
|
565
|
+
"required" => true
|
566
|
+
}
|
567
|
+
],
|
568
|
+
"body" => [
|
569
|
+
{
|
570
|
+
"content_type" => "application/json"
|
571
|
+
}
|
572
|
+
]
|
573
|
+
}
|
574
|
+
],
|
575
|
+
"integrate_with" => {
|
576
|
+
"type" => "mock",
|
577
|
+
"passthrough_behavior" => "WHEN_NO_MATCH",
|
578
|
+
"backend_http_method" => "OPTIONS",
|
579
|
+
"request_templates" => [
|
580
|
+
{
|
581
|
+
"content_type" => "application/json",
|
582
|
+
"template" => '{"statusCode": 200}'
|
583
|
+
}
|
584
|
+
]
|
585
|
+
}
|
586
|
+
}
|
587
|
+
end
|
588
|
+
|
589
|
+
end
|
590
|
+
end
|
591
|
+
end
|
592
|
+
end
|