cloud-mu 2.0.0.pre.alpha9 → 2.0.0.pre.beta1
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/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
|