cloud-mu 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/bin/mu-adopt +12 -1
- data/bin/mu-load-config.rb +2 -1
- data/bin/mu-run-tests +14 -2
- data/cloud-mu.gemspec +3 -3
- data/modules/mu.rb +2 -2
- data/modules/mu/adoption.rb +5 -5
- data/modules/mu/cleanup.rb +47 -25
- data/modules/mu/cloud.rb +29 -1
- data/modules/mu/cloud/dnszone.rb +0 -2
- data/modules/mu/cloud/resource_base.rb +9 -3
- data/modules/mu/cloud/wrappers.rb +4 -0
- data/modules/mu/config.rb +1 -1
- data/modules/mu/config/bucket.rb +31 -2
- data/modules/mu/config/cache_cluster.rb +1 -1
- data/modules/mu/config/cdn.rb +100 -0
- data/modules/mu/config/container_cluster.rb +1 -1
- data/modules/mu/config/database.rb +1 -1
- data/modules/mu/config/dnszone.rb +4 -3
- data/modules/mu/config/endpoint.rb +1 -0
- data/modules/mu/config/function.rb +16 -7
- data/modules/mu/config/job.rb +89 -0
- data/modules/mu/config/notifier.rb +7 -18
- data/modules/mu/config/ref.rb +53 -7
- data/modules/mu/config/server.rb +1 -1
- data/modules/mu/config/vpc.rb +1 -0
- data/modules/mu/defaults/AWS.yaml +26 -26
- data/modules/mu/deploy.rb +13 -0
- data/modules/mu/master.rb +21 -0
- data/modules/mu/mommacat.rb +1 -0
- data/modules/mu/mommacat/daemon.rb +13 -7
- data/modules/mu/providers/aws.rb +115 -16
- data/modules/mu/providers/aws/alarm.rb +2 -2
- data/modules/mu/providers/aws/bucket.rb +274 -40
- data/modules/mu/providers/aws/cache_cluster.rb +4 -4
- data/modules/mu/providers/aws/cdn.rb +782 -0
- data/modules/mu/providers/aws/collection.rb +2 -2
- data/modules/mu/providers/aws/container_cluster.rb +57 -37
- data/modules/mu/providers/aws/database.rb +11 -11
- data/modules/mu/providers/aws/dnszone.rb +24 -7
- data/modules/mu/providers/aws/endpoint.rb +535 -50
- data/modules/mu/providers/aws/firewall_rule.rb +6 -3
- data/modules/mu/providers/aws/folder.rb +1 -1
- data/modules/mu/providers/aws/function.rb +288 -125
- data/modules/mu/providers/aws/group.rb +9 -7
- data/modules/mu/providers/aws/habitat.rb +2 -2
- data/modules/mu/providers/aws/job.rb +466 -0
- data/modules/mu/providers/aws/loadbalancer.rb +9 -8
- data/modules/mu/providers/aws/log.rb +3 -3
- data/modules/mu/providers/aws/msg_queue.rb +12 -3
- data/modules/mu/providers/aws/nosqldb.rb +96 -5
- data/modules/mu/providers/aws/notifier.rb +135 -63
- data/modules/mu/providers/aws/role.rb +51 -37
- data/modules/mu/providers/aws/search_domain.rb +165 -29
- data/modules/mu/providers/aws/server.rb +12 -9
- data/modules/mu/providers/aws/server_pool.rb +26 -13
- data/modules/mu/providers/aws/storage_pool.rb +2 -2
- data/modules/mu/providers/aws/user.rb +4 -4
- data/modules/mu/providers/aws/userdata/linux.erb +5 -4
- data/modules/mu/providers/aws/vpc.rb +3 -3
- data/modules/mu/providers/azure/server.rb +2 -1
- data/modules/mu/providers/google.rb +1 -0
- data/modules/mu/providers/google/bucket.rb +1 -1
- data/modules/mu/providers/google/container_cluster.rb +1 -1
- data/modules/mu/providers/google/database.rb +1 -1
- data/modules/mu/providers/google/firewall_rule.rb +1 -1
- data/modules/mu/providers/google/folder.rb +1 -1
- data/modules/mu/providers/google/function.rb +1 -1
- data/modules/mu/providers/google/group.rb +1 -1
- data/modules/mu/providers/google/habitat.rb +1 -1
- data/modules/mu/providers/google/loadbalancer.rb +1 -1
- data/modules/mu/providers/google/role.rb +4 -2
- data/modules/mu/providers/google/server.rb +1 -1
- data/modules/mu/providers/google/server_pool.rb +1 -1
- data/modules/mu/providers/google/user.rb +1 -1
- data/modules/mu/providers/google/vpc.rb +1 -1
- data/modules/tests/aws-jobs-functions.yaml +46 -0
- data/modules/tests/centos6.yaml +4 -0
- data/modules/tests/centos7.yaml +4 -0
- data/modules/tests/ecs.yaml +2 -2
- data/modules/tests/eks.yaml +1 -1
- data/modules/tests/functions/node-function/lambda_function.js +10 -0
- data/modules/tests/functions/python-function/lambda_function.py +12 -0
- data/modules/tests/microservice_app.yaml +288 -0
- data/modules/tests/rds.yaml +5 -5
- data/modules/tests/regrooms/rds.yaml +5 -5
- data/modules/tests/server-with-scrub-muisms.yaml +1 -1
- data/modules/tests/super_complex_bok.yml +2 -2
- data/modules/tests/super_simple_bok.yml +2 -2
- metadata +12 -4
|
@@ -242,7 +242,7 @@ module MU
|
|
|
242
242
|
# @param region [String]: The cloud provider region
|
|
243
243
|
# @param wait [Boolean]: Block on the removal of this stack; AWS deletion will continue in the background otherwise if false.
|
|
244
244
|
# @return [void]
|
|
245
|
-
def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, wait: false, credentials: nil, flags: {})
|
|
245
|
+
def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, wait: false, credentials: nil, flags: {})
|
|
246
246
|
MU.log "AWS::Collection.cleanup: need to support flags['known']", MU::DEBUG, details: flags
|
|
247
247
|
MU.log "Placeholder: AWS Collection artifacts do not support tags, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: ignoremaster
|
|
248
248
|
|
|
@@ -251,7 +251,7 @@ module MU
|
|
|
251
251
|
resp.stacks.each { |stack|
|
|
252
252
|
ok = false
|
|
253
253
|
stack.tags.each { |tag|
|
|
254
|
-
ok = true if (tag.key == "MU-ID") and tag.value ==
|
|
254
|
+
ok = true if (tag.key == "MU-ID") and tag.value == deploy_id
|
|
255
255
|
}
|
|
256
256
|
if ok
|
|
257
257
|
MU.log "Deleting CloudFormation stack #{stack.stack_name})"
|
|
@@ -287,6 +287,7 @@ MU.log c.name, MU::NOTICE, details: t
|
|
|
287
287
|
# @return [OpenStruct]
|
|
288
288
|
def cloud_desc(use_cache: true)
|
|
289
289
|
return @cloud_desc_cache if @cloud_desc_cache and use_cache
|
|
290
|
+
return nil if !@cloud_id
|
|
290
291
|
@cloud_desc_cache = if @config['flavor'] == "EKS" or
|
|
291
292
|
(@config['flavor'] == "Fargate" and !@config['containers'])
|
|
292
293
|
resp = MU::Cloud::AWS.eks(region: @config['region'], credentials: @config['credentials']).describe_cluster(
|
|
@@ -326,7 +327,7 @@ MU.log c.name, MU::NOTICE, details: t
|
|
|
326
327
|
end
|
|
327
328
|
|
|
328
329
|
@@eks_versions = {}
|
|
329
|
-
@@
|
|
330
|
+
@@eks_version_semaphores = {}
|
|
330
331
|
# Use the AWS SSM API to fetch the current version of the Amazon Linux
|
|
331
332
|
# ECS-optimized AMI, so we can use it as a default AMI for ECS deploys.
|
|
332
333
|
# @param flavor [String]: ECS or EKS
|
|
@@ -339,24 +340,22 @@ MU.log c.name, MU::NOTICE, details: t
|
|
|
339
340
|
names: ["/aws/service/#{flavor.downcase}/optimized-ami/amazon-linux/recommended"]
|
|
340
341
|
)
|
|
341
342
|
else
|
|
342
|
-
@@
|
|
343
|
+
@@eks_version_semaphores[region] ||= Mutex.new
|
|
344
|
+
|
|
345
|
+
@@eks_version_semaphores[region].synchronize {
|
|
343
346
|
if !@@eks_versions[region]
|
|
344
347
|
@@eks_versions[region] ||= []
|
|
345
348
|
versions = {}
|
|
346
|
-
resp =
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
)
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
versions[Regexp.last_match[1]] = true
|
|
357
|
-
}
|
|
358
|
-
next_token = resp.next_token
|
|
359
|
-
end while !next_token.nil?
|
|
349
|
+
resp = MU::Cloud::AWS.ssm(region: region).get_parameters_by_path(
|
|
350
|
+
path: "/aws/service/#{flavor.downcase}/optimized-ami",
|
|
351
|
+
recursive: true,
|
|
352
|
+
max_results: 10 # as high as it goes, ugh
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
resp.parameters.each { |p|
|
|
356
|
+
p.name.match(/\/aws\/service\/eks\/optimized-ami\/([^\/]+?)\//)
|
|
357
|
+
versions[Regexp.last_match[1]] = true
|
|
358
|
+
}
|
|
360
359
|
@@eks_versions[region] = versions.keys.sort { |a, b| MU.version_sort(a, b) }
|
|
361
360
|
end
|
|
362
361
|
}
|
|
@@ -376,16 +375,31 @@ MU.log c.name, MU::NOTICE, details: t
|
|
|
376
375
|
nil
|
|
377
376
|
end
|
|
378
377
|
|
|
378
|
+
@@supported_eks_region_cache = []
|
|
379
|
+
@@eks_region_semaphore = Mutex.new
|
|
380
|
+
|
|
379
381
|
# Return the list of regions where we know EKS is supported.
|
|
380
|
-
def self.EKSRegions(credentials = nil
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
382
|
+
def self.EKSRegions(credentials = nil)
|
|
383
|
+
@@eks_region_semaphore.synchronize {
|
|
384
|
+
if @@supported_eks_region_cache and !@@supported_eks_region_cache.empty?
|
|
385
|
+
return @@supported_eks_region_cache
|
|
386
|
+
end
|
|
387
|
+
start = Time.now
|
|
388
|
+
# the SSM API is painfully slow for large result sets, so thread
|
|
389
|
+
# these and do them in parallel
|
|
390
|
+
@@supported_eks_region_cache = []
|
|
391
|
+
region_threads = []
|
|
392
|
+
MU::Cloud::AWS.listRegions(credentials: credentials).each { |region|
|
|
393
|
+
region_threads << Thread.new(region) { |r|
|
|
394
|
+
r_start = Time.now
|
|
395
|
+
ami = getStandardImage("EKS", r)
|
|
396
|
+
@@supported_eks_region_cache << r if ami
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
region_threads.each { |t| t.join }
|
|
387
400
|
|
|
388
|
-
|
|
401
|
+
@@supported_eks_region_cache
|
|
402
|
+
}
|
|
389
403
|
end
|
|
390
404
|
|
|
391
405
|
# Does this resource type exist as a global (cloud-wide) artifact, or
|
|
@@ -406,30 +420,32 @@ MU.log c.name, MU::NOTICE, details: t
|
|
|
406
420
|
# @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
|
|
407
421
|
# @param region [String]: The cloud provider region
|
|
408
422
|
# @return [void]
|
|
409
|
-
def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
|
423
|
+
def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
|
410
424
|
MU.log "AWS::ContainerCluster.cleanup: need to support flags['known']", MU::DEBUG, details: flags
|
|
411
425
|
MU.log "Placeholder: AWS ContainerCluster artifacts do not support tags, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: ignoremaster
|
|
412
426
|
|
|
413
|
-
purge_ecs_clusters(noop: noop, region: region, credentials: credentials)
|
|
427
|
+
purge_ecs_clusters(noop: noop, region: region, credentials: credentials, deploy_id: deploy_id)
|
|
414
428
|
|
|
415
|
-
purge_eks_clusters(noop: noop, region: region, credentials: credentials)
|
|
429
|
+
purge_eks_clusters(noop: noop, region: region, credentials: credentials, deploy_id: deploy_id)
|
|
416
430
|
|
|
417
431
|
end
|
|
418
432
|
|
|
419
|
-
def self.purge_eks_clusters(noop: false, region: MU.curRegion, credentials: nil)
|
|
420
|
-
return if !MU::Cloud::AWS::ContainerCluster.EKSRegions(credentials, region: region).include?(region)
|
|
433
|
+
def self.purge_eks_clusters(noop: false, region: MU.curRegion, credentials: nil, deploy_id: MU.deploy_id)
|
|
421
434
|
resp = begin
|
|
422
435
|
MU::Cloud::AWS.eks(credentials: credentials, region: region).list_clusters
|
|
423
436
|
rescue Aws::EKS::Errors::AccessDeniedException
|
|
424
437
|
# EKS isn't actually live in this region, even though SSM lists
|
|
425
438
|
# base images for it
|
|
439
|
+
if @@supported_eks_region_cache
|
|
440
|
+
@@supported_eks_region_cache.delete(region)
|
|
441
|
+
end
|
|
426
442
|
return
|
|
427
443
|
end
|
|
428
444
|
|
|
429
445
|
return if !resp or !resp.clusters
|
|
430
446
|
|
|
431
447
|
resp.clusters.each { |cluster|
|
|
432
|
-
if cluster.match(/^#{
|
|
448
|
+
if cluster.match(/^#{deploy_id}-/)
|
|
433
449
|
|
|
434
450
|
desc = MU::Cloud::AWS.eks(credentials: credentials, region: region).describe_cluster(
|
|
435
451
|
name: cluster
|
|
@@ -473,13 +489,14 @@ MU.log c.name, MU::NOTICE, details: t
|
|
|
473
489
|
end
|
|
474
490
|
private_class_method :purge_eks_clusters
|
|
475
491
|
|
|
476
|
-
def self.purge_ecs_clusters(noop: false, region: MU.curRegion, credentials: nil)
|
|
492
|
+
def self.purge_ecs_clusters(noop: false, region: MU.curRegion, credentials: nil, deploy_id: MU.deploy_id)
|
|
493
|
+
start = Time.now
|
|
477
494
|
resp = MU::Cloud::AWS.ecs(credentials: credentials, region: region).list_clusters
|
|
478
495
|
|
|
479
496
|
return if !resp or !resp.cluster_arns or resp.cluster_arns.empty?
|
|
480
497
|
|
|
481
498
|
resp.cluster_arns.each { |arn|
|
|
482
|
-
if arn.match(/:cluster\/(#{
|
|
499
|
+
if arn.match(/:cluster\/(#{deploy_id}[^:]+)$/)
|
|
483
500
|
cluster = Regexp.last_match[1]
|
|
484
501
|
|
|
485
502
|
svc_resp = MU::Cloud::AWS.ecs(region: region, credentials: credentials).list_services(
|
|
@@ -525,7 +542,7 @@ MU.log c.name, MU::NOTICE, details: t
|
|
|
525
542
|
}
|
|
526
543
|
|
|
527
544
|
tasks = MU::Cloud::AWS.ecs(region: region, credentials: credentials).list_task_definitions(
|
|
528
|
-
family_prefix:
|
|
545
|
+
family_prefix: deploy_id
|
|
529
546
|
)
|
|
530
547
|
|
|
531
548
|
if tasks and tasks.task_definition_arns
|
|
@@ -1221,12 +1238,12 @@ start = Time.now
|
|
|
1221
1238
|
|
|
1222
1239
|
cluster["flavor"] = "EKS" if cluster["flavor"].match(/^Kubernetes$/i)
|
|
1223
1240
|
|
|
1224
|
-
if cluster["flavor"] == "ECS" and cluster["kubernetes"] and !MU::Cloud::AWS.isGovCloud?(cluster["region"]) and !cluster["containers"] and MU::Cloud::AWS::ContainerCluster.EKSRegions(cluster['credentials']
|
|
1241
|
+
if cluster["flavor"] == "ECS" and cluster["kubernetes"] and !MU::Cloud::AWS.isGovCloud?(cluster["region"]) and !cluster["containers"] and MU::Cloud::AWS::ContainerCluster.EKSRegions(cluster['credentials']).include?(cluster['region'])
|
|
1225
1242
|
cluster["flavor"] = "EKS"
|
|
1226
1243
|
MU.log "Setting flavor of ContainerCluster '#{cluster['name']}' to EKS ('kubernetes' stanza was specified)", MU::NOTICE
|
|
1227
1244
|
end
|
|
1228
1245
|
|
|
1229
|
-
if cluster["flavor"] == "EKS" and !MU::Cloud::AWS::ContainerCluster.EKSRegions(cluster['credentials']
|
|
1246
|
+
if cluster["flavor"] == "EKS" and !MU::Cloud::AWS::ContainerCluster.EKSRegions(cluster['credentials']).include?(cluster['region'])
|
|
1230
1247
|
MU.log "EKS is only available in some regions", MU::ERR, details: MU::Cloud::AWS::ContainerCluster.EKSRegions
|
|
1231
1248
|
ok = false
|
|
1232
1249
|
end
|
|
@@ -1484,7 +1501,8 @@ start = Time.now
|
|
|
1484
1501
|
worker_pool[k] = cluster[k]
|
|
1485
1502
|
end
|
|
1486
1503
|
}
|
|
1487
|
-
|
|
1504
|
+
else
|
|
1505
|
+
worker_pool["groom"] = false # don't meddle with ECS workers unnecessarily
|
|
1488
1506
|
end
|
|
1489
1507
|
|
|
1490
1508
|
configurator.insertKitten(worker_pool, "server_pools")
|
|
@@ -1731,7 +1749,7 @@ start = Time.now
|
|
|
1731
1749
|
@deploy.findLitterMate(type: "server_pools", name: @config["name"]+"workers")
|
|
1732
1750
|
end
|
|
1733
1751
|
serverpool.listNodes.each { |mynode|
|
|
1734
|
-
resources = resource_lookup[
|
|
1752
|
+
resources = resource_lookup[mynode.cloud_desc.instance_type]
|
|
1735
1753
|
threads << Thread.new(mynode) { |node|
|
|
1736
1754
|
ident_doc = nil
|
|
1737
1755
|
ident_doc_sig = nil
|
|
@@ -1932,6 +1950,8 @@ start = Time.now
|
|
|
1932
1950
|
task_params[:network_mode] = "awsvpc"
|
|
1933
1951
|
task_params[:cpu] = cpu_total.to_i.to_s
|
|
1934
1952
|
task_params[:memory] = mem_total.to_i.to_s
|
|
1953
|
+
elsif @config['vpc']
|
|
1954
|
+
task_params[:network_mode] = "awsvpc"
|
|
1935
1955
|
end
|
|
1936
1956
|
|
|
1937
1957
|
MU.log "Registering task definition #{service_name} with #{container_definitions.size.to_s} containers"
|
|
@@ -680,7 +680,7 @@ dependencies
|
|
|
680
680
|
# Return the metadata for this ContainerCluster
|
|
681
681
|
# @return [Hash]
|
|
682
682
|
def notify
|
|
683
|
-
deploy_struct = MU.structToHash(cloud_desc)
|
|
683
|
+
deploy_struct = MU.structToHash(cloud_desc, stringify_keys: true)
|
|
684
684
|
deploy_struct['cloud_id'] = @cloud_id
|
|
685
685
|
deploy_struct["region"] ||= @config['region']
|
|
686
686
|
deploy_struct["db_name"] ||= @config['db_name']
|
|
@@ -761,7 +761,7 @@ dependencies
|
|
|
761
761
|
end
|
|
762
762
|
|
|
763
763
|
# @return [Array<Thread>]
|
|
764
|
-
def self.threaded_resource_purge(describe_method, list_method, id_method, arn_type, region, credentials, ignoremaster, known: [])
|
|
764
|
+
def self.threaded_resource_purge(describe_method, list_method, id_method, arn_type, region, credentials, ignoremaster, known: [], deploy_id: MU.deploy_id)
|
|
765
765
|
deletia = []
|
|
766
766
|
|
|
767
767
|
resp = MU::Cloud::AWS.rds(credentials: credentials, region: region).send(describe_method)
|
|
@@ -774,7 +774,7 @@ dependencies
|
|
|
774
774
|
next
|
|
775
775
|
end
|
|
776
776
|
|
|
777
|
-
if should_delete?(tags, resource.send(id_method), ignoremaster,
|
|
777
|
+
if should_delete?(tags, resource.send(id_method), ignoremaster, deploy_id, MU.mu_public_ip, known)
|
|
778
778
|
deletia << resource.send(id_method)
|
|
779
779
|
end
|
|
780
780
|
}
|
|
@@ -795,18 +795,18 @@ dependencies
|
|
|
795
795
|
# @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
|
|
796
796
|
# @param region [String]: The cloud provider region in which to operate
|
|
797
797
|
# @return [void]
|
|
798
|
-
def self.cleanup(noop: false, ignoremaster: false, credentials: nil, region: MU.curRegion, flags: {})
|
|
798
|
+
def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, credentials: nil, region: MU.curRegion, flags: {})
|
|
799
799
|
|
|
800
800
|
["instance", "cluster"].each { |type|
|
|
801
|
-
threaded_resource_purge("describe_db_#{type}s".to_sym, "db_#{type}s".to_sym, "db_#{type}_identifier".to_sym, (type == "instance" ? "db" : "cluster"), region, credentials, ignoremaster, known: flags['known']) { |id|
|
|
802
|
-
terminate_rds_instance(nil, noop: noop, skipsnapshots: flags["skipsnapshots"], region: region, deploy_id:
|
|
801
|
+
threaded_resource_purge("describe_db_#{type}s".to_sym, "db_#{type}s".to_sym, "db_#{type}_identifier".to_sym, (type == "instance" ? "db" : "cluster"), region, credentials, ignoremaster, known: flags['known'], deploy_id: deploy_id) { |id|
|
|
802
|
+
terminate_rds_instance(nil, noop: noop, skipsnapshots: flags["skipsnapshots"], region: region, deploy_id: deploy_id, cloud_id: id, mu_name: id.upcase, credentials: credentials, cluster: (type == "cluster"), known: flags['known'])
|
|
803
803
|
|
|
804
804
|
}.each { |t|
|
|
805
805
|
t.join
|
|
806
806
|
}
|
|
807
807
|
}
|
|
808
808
|
|
|
809
|
-
threads = threaded_resource_purge(:describe_db_subnet_groups, :db_subnet_groups, :db_subnet_group_name, "subgrp", region, credentials, ignoremaster, known: flags['known']) { |id|
|
|
809
|
+
threads = threaded_resource_purge(:describe_db_subnet_groups, :db_subnet_groups, :db_subnet_group_name, "subgrp", region, credentials, ignoremaster, known: flags['known'], deploy_id: deploy_id) { |id|
|
|
810
810
|
MU.log "Deleting RDS subnet group #{id}"
|
|
811
811
|
MU.retrier([Aws::RDS::Errors::InvalidDBSubnetGroupStateFault], wait: 30, max: 5, ignoreme: [Aws::RDS::Errors::DBSubnetGroupNotFoundFault]) {
|
|
812
812
|
MU::Cloud::AWS.rds(region: region).delete_db_subnet_group(db_subnet_group_name: id) if !noop
|
|
@@ -814,7 +814,7 @@ dependencies
|
|
|
814
814
|
}
|
|
815
815
|
|
|
816
816
|
["db", "db_cluster"].each { |type|
|
|
817
|
-
threads.concat threaded_resource_purge("describe_#{type}_parameter_groups".to_sym, "#{type}_parameter_groups".to_sym, "#{type}_parameter_group_name".to_sym, (type == "db" ? "pg" : "cluster-pg"), region, credentials, ignoremaster, known: flags['known']) { |id|
|
|
817
|
+
threads.concat threaded_resource_purge("describe_#{type}_parameter_groups".to_sym, "#{type}_parameter_groups".to_sym, "#{type}_parameter_group_name".to_sym, (type == "db" ? "pg" : "cluster-pg"), region, credentials, ignoremaster, known: flags['known'], deploy_id: deploy_id) { |id|
|
|
818
818
|
MU.log "Deleting RDS #{type} parameter group #{id}"
|
|
819
819
|
MU.retrier([Aws::RDS::Errors::InvalidDBParameterGroupState], wait: 30, max: 5, ignoreme: [Aws::RDS::Errors::DBParameterGroupNotFound]) {
|
|
820
820
|
MU::Cloud::AWS.rds(region: region).send("delete_#{type}_parameter_group", { "#{type}_parameter_group_name".to_sym => id }) if !noop
|
|
@@ -1274,7 +1274,7 @@ dependencies
|
|
|
1274
1274
|
|
|
1275
1275
|
|
|
1276
1276
|
def add_cluster_node
|
|
1277
|
-
cluster = MU::Config::Ref.get(@config["member_of_cluster"]).kitten(@deploy
|
|
1277
|
+
cluster = MU::Config::Ref.get(@config["member_of_cluster"]).kitten(@deploy)
|
|
1278
1278
|
if cluster.nil? or cluster.cloud_id.nil?
|
|
1279
1279
|
raise MuError.new "Failed to resolve parent cluster of #{@mu_name}", details: @config["member_of_cluster"].to_h
|
|
1280
1280
|
end
|
|
@@ -1355,7 +1355,7 @@ dependencies
|
|
|
1355
1355
|
|
|
1356
1356
|
# creation_style = point_in_time
|
|
1357
1357
|
def create_point_in_time
|
|
1358
|
-
@config["source"].kitten(@deploy
|
|
1358
|
+
@config["source"].kitten(@deploy)
|
|
1359
1359
|
if !@config["source"].id
|
|
1360
1360
|
raise MuError.new "Database '#{@config['name']}' couldn't resolve cloud id for source database", details: @config["source"].to_h
|
|
1361
1361
|
end
|
|
@@ -1381,7 +1381,7 @@ dependencies
|
|
|
1381
1381
|
|
|
1382
1382
|
# creation_style = new, existing and read_replica_of is not nil
|
|
1383
1383
|
def create_read_replica
|
|
1384
|
-
@config["source"].kitten(@deploy
|
|
1384
|
+
@config["source"].kitten(@deploy)
|
|
1385
1385
|
if !@config["source"].id
|
|
1386
1386
|
raise MuError.new "Database '#{@config['name']}' couldn't resolve cloud id for source database", details: @config["source"].to_h
|
|
1387
1387
|
end
|
|
@@ -42,7 +42,7 @@ module MU
|
|
|
42
42
|
params = {
|
|
43
43
|
:name => @config['name'],
|
|
44
44
|
:hosted_zone_config => {
|
|
45
|
-
:comment =>
|
|
45
|
+
:comment => @deploy.deploy_id
|
|
46
46
|
},
|
|
47
47
|
:caller_reference => @deploy.getResourceName(@config['name'])
|
|
48
48
|
}
|
|
@@ -173,11 +173,29 @@ module MU
|
|
|
173
173
|
return resp.hosted_zone if @config["create_zone"]
|
|
174
174
|
end
|
|
175
175
|
|
|
176
|
+
# Resolve a record entry (as in {MU::Config::BasketofKittens::dnszones::records} to the full DNS name we would assign it
|
|
177
|
+
def self.recordToName(record)
|
|
178
|
+
shortname = record['name']
|
|
179
|
+
shortname += ".#{MU.environment.downcase}" if record["append_environment_name"]
|
|
180
|
+
|
|
181
|
+
zone = if record['zone'].has_key?("id")
|
|
182
|
+
MU::Cloud::DNSZone.find(cloud_id: record['zone']['id']).values.first
|
|
183
|
+
else
|
|
184
|
+
MU::Cloud::DNSZone.find(cloud_id: record['zone']['name']).values.first
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
if zone.nil?
|
|
188
|
+
raise MuError.new "Failed to locate Route53 DNS Zone", details: record['zone']
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
shortname+"."+zone.name.sub(/\.$/, '')
|
|
192
|
+
end
|
|
193
|
+
|
|
176
194
|
# Wrapper for {MU::Cloud::AWS::DNSZone.manageRecord}. Spawns threads to create all
|
|
177
195
|
# requested records in background and returns immediately.
|
|
178
196
|
# @param cfg [Array]: An array of parsed {MU::Config::BasketofKittens::dnszones::records} objects.
|
|
179
197
|
# @param target [String]: Optional target for the records to be created. Overrides targets embedded in cfg records.
|
|
180
|
-
def self.createRecordsFromConfig(cfg, target: nil)
|
|
198
|
+
def self.createRecordsFromConfig(cfg, target: nil, name_only: false)
|
|
181
199
|
return if cfg.nil?
|
|
182
200
|
record_threads = []
|
|
183
201
|
|
|
@@ -190,7 +208,6 @@ module MU
|
|
|
190
208
|
zone = MU::Cloud::DNSZone.find(cloud_id: record['zone']['name']).values.first
|
|
191
209
|
end
|
|
192
210
|
|
|
193
|
-
raise MuError, "Failed to locate Route53 DNS Zone for domain #{record['zone']['name']}" if zone.nil?
|
|
194
211
|
|
|
195
212
|
healthcheck_id = nil
|
|
196
213
|
record['target'] = target if !target.nil?
|
|
@@ -666,7 +683,7 @@ module MU
|
|
|
666
683
|
|
|
667
684
|
# Called by {MU::Cleanup}. Locates resources that were created by the
|
|
668
685
|
# currently-loaded deployment, and purges them.
|
|
669
|
-
def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
|
686
|
+
def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
|
670
687
|
MU.log "AWS::DNSZone.cleanup: need to support flags['known']", MU::DEBUG, details: flags
|
|
671
688
|
|
|
672
689
|
threads = []
|
|
@@ -679,7 +696,7 @@ module MU
|
|
|
679
696
|
muid_match = false
|
|
680
697
|
mumaster_match = false
|
|
681
698
|
tags.each { |tag|
|
|
682
|
-
muid_match = true if tag.key == "MU-ID" and tag.value ==
|
|
699
|
+
muid_match = true if tag.key == "MU-ID" and tag.value == deploy_id
|
|
683
700
|
mumaster_match = true if tag.key == "MU-MASTER-IP" and tag.value == MU.mu_public_ip
|
|
684
701
|
}
|
|
685
702
|
|
|
@@ -723,7 +740,7 @@ module MU
|
|
|
723
740
|
t.join
|
|
724
741
|
}
|
|
725
742
|
|
|
726
|
-
zones = MU::Cloud::DNSZone.find(deploy_id:
|
|
743
|
+
zones = MU::Cloud::DNSZone.find(deploy_id: deploy_id, region: region)
|
|
727
744
|
zones.values.each { |zone|
|
|
728
745
|
MU.log "Purging DNS Zone '#{zone.name}' (#{zone.id})"
|
|
729
746
|
if !noop
|
|
@@ -779,7 +796,7 @@ module MU
|
|
|
779
796
|
|
|
780
797
|
# TO DO: if we have more than one record it will retry the deletion multiple times and will throw Aws::Route53::Errors::InvalidChangeBatch / record not found even though the record was deleted
|
|
781
798
|
zone_rrsets.each { |record|
|
|
782
|
-
if record.name.match(
|
|
799
|
+
if record.name.match(deploy_id.downcase)
|
|
783
800
|
resource_records = []
|
|
784
801
|
record.resource_records.each { |rrecord|
|
|
785
802
|
resource_records << rrecord.value
|
|
@@ -13,22 +13,21 @@ module MU
|
|
|
13
13
|
|
|
14
14
|
# Called automatically by {MU::Deploy#createResources}
|
|
15
15
|
def create
|
|
16
|
-
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @
|
|
16
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).create_rest_api(
|
|
17
17
|
name: @mu_name,
|
|
18
18
|
description: @deploy.deploy_id,
|
|
19
19
|
endpoint_configuration: {
|
|
20
20
|
types: ["REGIONAL"] # XXX expose in BoK ["REGIONAL", "EDGE", "PRIVATE"]
|
|
21
|
-
}
|
|
21
|
+
},
|
|
22
|
+
tags: @tags
|
|
22
23
|
)
|
|
23
24
|
@cloud_id = resp.id
|
|
24
|
-
generate_methods
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
generate_methods(false)
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
# Create/update all of the methods declared for this endpoint
|
|
30
|
-
def generate_methods
|
|
31
|
-
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @
|
|
29
|
+
def generate_methods(integrations = true)
|
|
30
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_resources(
|
|
32
31
|
rest_api_id: @cloud_id,
|
|
33
32
|
)
|
|
34
33
|
root_resource = resp.items.first.id
|
|
@@ -37,24 +36,26 @@ module MU
|
|
|
37
36
|
@config['methods'].each { |m|
|
|
38
37
|
m["auth"] ||= m["iam_role"] ? "AWS_IAM" : "NONE"
|
|
39
38
|
|
|
40
|
-
method_arn = "arn:#{MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws"}:execute-api:#{@config["region"]}:#{MU::Cloud::AWS.credToAcct(@
|
|
39
|
+
method_arn = "arn:#{MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws"}:execute-api:#{@config["region"]}:#{MU::Cloud::AWS.credToAcct(@credentials)}:#{@cloud_id}/*/#{m['type']}/#{m['path']}"
|
|
40
|
+
path_part = ["", "/"].include?(m['path']) ? nil : m['path']
|
|
41
|
+
method_arn.sub!(/\/\/$/, '/')
|
|
41
42
|
|
|
42
|
-
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @
|
|
43
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_resources(
|
|
43
44
|
rest_api_id: @cloud_id
|
|
44
45
|
)
|
|
45
46
|
ext_resource = nil
|
|
46
47
|
resp.items.each { |resource|
|
|
47
|
-
if resource.path_part ==
|
|
48
|
+
if resource.path_part == path_part
|
|
48
49
|
ext_resource = resource.id
|
|
49
50
|
end
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
resp = if ext_resource
|
|
53
|
-
MU::Cloud::AWS.apig(region: @config['region'], credentials: @
|
|
54
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_resource(
|
|
54
55
|
rest_api_id: @cloud_id,
|
|
55
56
|
resource_id: ext_resource,
|
|
56
57
|
)
|
|
57
|
-
# MU::Cloud::AWS.apig(region: @config['region'], credentials: @
|
|
58
|
+
# MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).update_resource(
|
|
58
59
|
# rest_api_id: @cloud_id,
|
|
59
60
|
# resource_id: ext_resource,
|
|
60
61
|
# patch_operations: [
|
|
@@ -66,22 +67,22 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
66
67
|
# ]
|
|
67
68
|
# )
|
|
68
69
|
else
|
|
69
|
-
MU::Cloud::AWS.apig(region: @config['region'], credentials: @
|
|
70
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).create_resource(
|
|
70
71
|
rest_api_id: @cloud_id,
|
|
71
72
|
parent_id: root_resource,
|
|
72
|
-
path_part:
|
|
73
|
+
path_part: path_part
|
|
73
74
|
)
|
|
74
75
|
end
|
|
75
76
|
parent_id = resp.id
|
|
76
77
|
|
|
77
78
|
resp = begin
|
|
78
|
-
MU::Cloud::AWS.apig(region: @config['region'], credentials: @
|
|
79
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_method(
|
|
79
80
|
rest_api_id: @cloud_id,
|
|
80
81
|
resource_id: parent_id,
|
|
81
82
|
http_method: m['type']
|
|
82
83
|
)
|
|
83
84
|
rescue Aws::APIGateway::Errors::NotFoundException
|
|
84
|
-
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @
|
|
85
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).put_method(
|
|
85
86
|
rest_api_id: @cloud_id,
|
|
86
87
|
resource_id: parent_id,
|
|
87
88
|
authorization_type: m['auth'],
|
|
@@ -100,6 +101,7 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
100
101
|
}
|
|
101
102
|
if r['headers']
|
|
102
103
|
params[:response_parameters] = r['headers'].map { |h|
|
|
104
|
+
h['required'] ||= false
|
|
103
105
|
["method.response.header."+h['header'], h['required']]
|
|
104
106
|
}.to_h
|
|
105
107
|
end
|
|
@@ -109,13 +111,13 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
109
111
|
params[:response_models] = r['body'].map { |b| [b['content_type'], b['is_error'] ? "Error" : "Empty"] }.to_h
|
|
110
112
|
end
|
|
111
113
|
|
|
112
|
-
MU::Cloud::AWS.apig(region: @config['region'], credentials: @
|
|
114
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).put_method_response(params)
|
|
113
115
|
}
|
|
114
116
|
rescue Aws::APIGateway::Errors::ConflictException
|
|
115
117
|
# fine to ignore
|
|
116
118
|
end
|
|
117
119
|
|
|
118
|
-
if m['integrate_with']
|
|
120
|
+
if integrations and m['integrate_with']
|
|
119
121
|
# role_arn = if m['iam_role']
|
|
120
122
|
# if m['iam_role'].match(/^arn:/)
|
|
121
123
|
# m['iam_role']
|
|
@@ -127,13 +129,17 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
127
129
|
# end
|
|
128
130
|
|
|
129
131
|
function_obj = nil
|
|
132
|
+
aws_int_type = m['integrate_with']['proxy'] ? "AWS_PROXY" : "AWS"
|
|
130
133
|
|
|
131
134
|
uri, type = if m['integrate_with']['type'] == "aws_generic"
|
|
132
135
|
svc, action = m['integrate_with']['aws_generic_action'].split(/:/)
|
|
133
|
-
["arn:aws:apigateway:"+@config['region']+":#{svc}:action/#{action}",
|
|
134
|
-
elsif m['integrate_with']['type'] == "
|
|
135
|
-
function_obj =
|
|
136
|
-
[
|
|
136
|
+
["arn:aws:apigateway:"+@config['region']+":#{svc}:action/#{action}", aws_int_type]
|
|
137
|
+
elsif m['integrate_with']['type'] == "functions"
|
|
138
|
+
function_obj = nil
|
|
139
|
+
MU.retrier([], max: 5, wait: 9, loop_if: Proc.new { function_obj.nil? }) {
|
|
140
|
+
function_obj = @deploy.findLitterMate(name: m['integrate_with']['name'], type: "functions")
|
|
141
|
+
}
|
|
142
|
+
["arn:aws:apigateway:"+@config['region']+":lambda:path/2015-03-31/functions/"+function_obj.cloudobj.arn+"/invocations", aws_int_type]
|
|
137
143
|
elsif m['integrate_with']['type'] == "mock"
|
|
138
144
|
[nil, "MOCK"]
|
|
139
145
|
end
|
|
@@ -143,7 +149,8 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
143
149
|
:resource_id => parent_id,
|
|
144
150
|
:type => type, # XXX Lambda and Firehose can do AWS_PROXY
|
|
145
151
|
:content_handling => "CONVERT_TO_TEXT", # XXX expose in BoK
|
|
146
|
-
:http_method => m['type']
|
|
152
|
+
:http_method => m['type'],
|
|
153
|
+
:timeout_in_millis => m['timeout_in_millis']
|
|
147
154
|
# credentials: role_arn
|
|
148
155
|
}
|
|
149
156
|
params[:uri] = uri if uri
|
|
@@ -163,10 +170,15 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
163
170
|
params[:request_templates][rt['content_type']] = rt['template']
|
|
164
171
|
}
|
|
165
172
|
end
|
|
173
|
+
if m['integrate_with']['parameters']
|
|
174
|
+
params[:request_parameters] = Hash[m['integrate_with']['parameters'].map { |p|
|
|
175
|
+
["integration.request.#{p['type']}.#{p['name']}", p['value']]
|
|
176
|
+
}]
|
|
177
|
+
end
|
|
166
178
|
|
|
167
|
-
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @
|
|
179
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).put_integration(params)
|
|
168
180
|
|
|
169
|
-
if m['integrate_with']['type']
|
|
181
|
+
if m['integrate_with']['type'] =~ /^functions?$/
|
|
170
182
|
function_obj.addTrigger(method_arn, "apigateway", @config['name'])
|
|
171
183
|
end
|
|
172
184
|
|
|
@@ -176,7 +188,7 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
176
188
|
:resource_id => parent_id,
|
|
177
189
|
:http_method => m['type'],
|
|
178
190
|
:status_code => r['code'].to_s,
|
|
179
|
-
:selection_pattern => ""
|
|
191
|
+
:selection_pattern => ".*"
|
|
180
192
|
}
|
|
181
193
|
if r['headers']
|
|
182
194
|
params[:response_parameters] = r['headers'].map { |h|
|
|
@@ -184,7 +196,7 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
184
196
|
}.to_h
|
|
185
197
|
end
|
|
186
198
|
|
|
187
|
-
MU::Cloud::AWS.apig(region: @config['region'], credentials: @
|
|
199
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).put_integration_response(params)
|
|
188
200
|
|
|
189
201
|
}
|
|
190
202
|
|
|
@@ -197,24 +209,152 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
197
209
|
def groom
|
|
198
210
|
generate_methods
|
|
199
211
|
|
|
200
|
-
MU.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
212
|
+
deployment = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_deployments(
|
|
213
|
+
rest_api_id: @cloud_id
|
|
214
|
+
).items.sort { |a, b| a.created_date <=> b.created_date }.last
|
|
215
|
+
|
|
216
|
+
if !deployment
|
|
217
|
+
MU.log "Deploying API Gateway #{@config['name']} to #{@config['deploy_to']}"
|
|
218
|
+
deployment = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).create_deployment(
|
|
219
|
+
rest_api_id: @cloud_id,
|
|
220
|
+
stage_name: @config['deploy_to']
|
|
204
221
|
# cache_cluster_enabled: false,
|
|
205
222
|
# cache_cluster_size: 0.5,
|
|
206
|
-
|
|
223
|
+
)
|
|
224
|
+
end
|
|
207
225
|
# this automatically creates a stage with the same name, so we don't
|
|
208
226
|
# have to deal with that
|
|
209
227
|
|
|
210
|
-
|
|
228
|
+
my_hostname = @cloud_id+".execute-api."+@config['region']+".amazonaws.com"
|
|
229
|
+
my_url = "https://"+my_hostname+"/"+@config['deploy_to']
|
|
211
230
|
MU.log "API Endpoint #{@config['name']}: "+my_url, MU::SUMMARY
|
|
212
231
|
|
|
213
|
-
|
|
232
|
+
print_dns_alias = Proc.new { |rec|
|
|
233
|
+
rec['name'] ||= @mu_name.downcase
|
|
234
|
+
dnsname = MU::Cloud.resourceClass("AWS", "DNSZone").recordToName(rec)
|
|
235
|
+
dnsname
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
# if we have any placeholder DNS records that are intended to be
|
|
239
|
+
# filled out with our runtime @mu_name, do so, and add an alias if
|
|
240
|
+
# applicable
|
|
241
|
+
if @config['dns_records'] and !MU::Cloud::AWS.isGovCloud?
|
|
242
|
+
@config['dns_records'].each { |rec|
|
|
243
|
+
dnsname = print_dns_alias.call(rec)
|
|
244
|
+
MU.log "Alias for API Endpoint #{@config['name']}: https://"+dnsname+"/"+@config['deploy_to'], MU::SUMMARY
|
|
245
|
+
}
|
|
246
|
+
MU::Cloud.resourceClass("AWS", "DNSZone").createRecordsFromConfig(@config['dns_records'], target: my_hostname)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
if @config['domain_names']
|
|
250
|
+
@config['domain_names'].each { |dom|
|
|
251
|
+
dnsname = if dom['dns_record']
|
|
252
|
+
print_dns_alias.call(dom['dns_record'])
|
|
253
|
+
else
|
|
254
|
+
dom['unmanaged_name']
|
|
255
|
+
end
|
|
256
|
+
MU.log "Alias for API Endpoint #{@config['name']}: https://"+dnsname, MU::SUMMARY
|
|
257
|
+
|
|
258
|
+
certfield, dnsfield = if dom['endpoint_type'] == "EDGE"
|
|
259
|
+
[:certificate_arn, :distribution_domain_name]
|
|
260
|
+
else
|
|
261
|
+
[:regional_certificate_arn, :regional_domain_name]
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
dom_desc = begin
|
|
265
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_domain_name(domain_name: dnsname)
|
|
266
|
+
rescue ::Aws::APIGateway::Errors::NotFoundException
|
|
267
|
+
|
|
268
|
+
params = {
|
|
269
|
+
domain_name: dnsname,
|
|
270
|
+
endpoint_configuration: {
|
|
271
|
+
types: [dom['endpoint_type']]
|
|
272
|
+
},
|
|
273
|
+
security_policy: dom['security_policy'],
|
|
274
|
+
tags: @tags
|
|
275
|
+
}
|
|
276
|
+
if dom['certificate']
|
|
277
|
+
params[certfield] = dom['certificate']['id']
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
MU.log "Creating API Gateway Domain Name #{dnsname}", MU::NOTICE, details: params
|
|
281
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).create_domain_name(params)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
mappings = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_base_path_mappings(domain_name: dnsname, limit: 500).items
|
|
285
|
+
found = false
|
|
286
|
+
if mappings
|
|
287
|
+
mappings.each { |m|
|
|
288
|
+
if m.rest_api_id == @cloud_id and m.stage == @config['deploy_to']
|
|
289
|
+
found = true
|
|
290
|
+
break
|
|
291
|
+
end
|
|
292
|
+
}
|
|
293
|
+
end
|
|
294
|
+
if !found
|
|
295
|
+
MU.log "Mapping #{dnsname} to API Gateway #{@mu_name}"
|
|
296
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).create_base_path_mapping(
|
|
297
|
+
domain_name: dnsname,
|
|
298
|
+
rest_api_id: @cloud_id,
|
|
299
|
+
stage: @config['deploy_to']
|
|
300
|
+
)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
if dom['dns_record']
|
|
304
|
+
MU::Cloud.resourceClass("AWS", "DNSZone").createRecordsFromConfig([dom['dns_record']], target: dom_desc.send(dnsfield))
|
|
305
|
+
end
|
|
306
|
+
}
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# The creation of our deployment should have created a matching stage,
|
|
310
|
+
# which we're now going to mess with.
|
|
311
|
+
stage = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_stage(
|
|
312
|
+
rest_api_id: @cloud_id,
|
|
313
|
+
stage_name: @config['deploy_to']
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
if @config['access_logs'] and !stage.access_log_settings
|
|
317
|
+
log_ref = MU::Config::Ref.get(@config['access_logs'])
|
|
318
|
+
MU.log "Enabling API Gateway access logs to CloudWatch Log Group #{log_ref.cloud_id}"
|
|
319
|
+
stage = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).update_stage(
|
|
320
|
+
rest_api_id: @cloud_id,
|
|
321
|
+
stage_name: @config['deploy_to'],
|
|
322
|
+
patch_operations: [
|
|
323
|
+
{
|
|
324
|
+
op: "replace",
|
|
325
|
+
path: "/accessLogSettings/destinationArn",
|
|
326
|
+
value: log_ref.kitten.arn.sub(/:\*$/, '')
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
op: "replace",
|
|
330
|
+
path: "/accessLogSettings/format",
|
|
331
|
+
value: '$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] "$context.httpMethod $context.resourcePath $context.protocol" $context.status $context.responseLength $context.requestId'
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
op: "replace",
|
|
335
|
+
path: "/description",
|
|
336
|
+
value: @deploy.deploy_id
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
op: "replace",
|
|
340
|
+
path: "/*/*/logging/dataTrace",
|
|
341
|
+
value: "true"
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
op: "replace",
|
|
345
|
+
path: "/*/*/logging/loglevel",
|
|
346
|
+
value: "INFO"
|
|
347
|
+
}
|
|
348
|
+
]
|
|
349
|
+
)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
# resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).create_authorizer(
|
|
214
354
|
# rest_api_id: @cloud_id,
|
|
215
355
|
# )
|
|
216
356
|
|
|
217
|
-
# resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @
|
|
357
|
+
# resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).create_vpc_link(
|
|
218
358
|
# )
|
|
219
359
|
|
|
220
360
|
end
|
|
@@ -223,7 +363,8 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
223
363
|
# @return [Struct]
|
|
224
364
|
def cloud_desc(use_cache: true)
|
|
225
365
|
return @cloud_desc_cache if @cloud_desc_cache and use_cache
|
|
226
|
-
|
|
366
|
+
return nil if !@cloud_id
|
|
367
|
+
@cloud_desc_cache = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_rest_api(
|
|
227
368
|
rest_api_id: @cloud_id
|
|
228
369
|
)
|
|
229
370
|
@cloud_desc_cache
|
|
@@ -232,7 +373,10 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
232
373
|
# Return the metadata for this API
|
|
233
374
|
# @return [Hash]
|
|
234
375
|
def notify
|
|
235
|
-
|
|
376
|
+
return nil if !@cloud_id or !cloud_desc(use_cache: false)
|
|
377
|
+
deploy_struct = MU.structToHash(cloud_desc, stringify_keys: true)
|
|
378
|
+
deploy_struct['url'] = "https://"+@cloud_id+".execute-api."+@config['region']+".amazonaws.com"
|
|
379
|
+
deploy_struct['url'] += "/"+@config['deploy_to'] if @config['deploy_to']
|
|
236
380
|
# XXX stages and whatnot
|
|
237
381
|
return deploy_struct
|
|
238
382
|
end
|
|
@@ -242,15 +386,45 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
242
386
|
# @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
|
|
243
387
|
# @param region [String]: The cloud provider region
|
|
244
388
|
# @return [void]
|
|
245
|
-
def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
|
389
|
+
def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
|
246
390
|
MU.log "AWS::Endpoint.cleanup: need to support flags['known']", MU::DEBUG, details: flags
|
|
247
391
|
MU.log "Placeholder: AWS Endpoint artifacts do not support tags, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: ignoremaster
|
|
248
392
|
|
|
393
|
+
resp = MU::Cloud::AWS.apig(region: region, credentials: credentials).get_domain_names(limit: 500)
|
|
394
|
+
if resp and resp.items
|
|
395
|
+
resp.items.each { |d|
|
|
396
|
+
next if !d.tags
|
|
397
|
+
if d.tags["MU-ID"] == deploy_id and
|
|
398
|
+
(ignoremaster or d.tags["MU-MASTER-IP"] == MU.mu_public_ip)
|
|
399
|
+
mappings = MU::Cloud::AWS.apig(region: region, credentials: credentials).get_base_path_mappings(domain_name: d.domain_name, limit: 500).items
|
|
400
|
+
mappings.each { |m|
|
|
401
|
+
MU.log "Deleting API Gateway Domain Name mapping #{d.domain_name} => #{m.rest_api_id} path #{m.base_path}"
|
|
402
|
+
if !noop
|
|
403
|
+
MU::Cloud::AWS.apig(region: region, credentials: credentials).delete_base_path_mapping(domain_name: d.domain_name, base_path: m.base_path)
|
|
404
|
+
end
|
|
405
|
+
}
|
|
406
|
+
MU.log "Deleting API Gateway Domain Name #{d.domain_name}"
|
|
407
|
+
if !noop
|
|
408
|
+
MU::Cloud::AWS.apig(region: region, credentials: credentials).delete_domain_name(domain_name: d.domain_name)
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
}
|
|
412
|
+
end
|
|
413
|
+
|
|
249
414
|
resp = MU::Cloud::AWS.apig(region: region, credentials: credentials).get_rest_apis
|
|
250
415
|
if resp and resp.items
|
|
251
416
|
resp.items.each { |api|
|
|
252
417
|
# The stupid things don't have tags
|
|
253
|
-
if api.description ==
|
|
418
|
+
if api.description == deploy_id
|
|
419
|
+
logs = MU::Cloud.resourceClass("AWS", "Log").find(region: region, credentials: credentials)
|
|
420
|
+
logs.each_pair { |log_id, log_desc|
|
|
421
|
+
if log_id =~ /^API-Gateway-Execution-Logs_#{api.id}\//
|
|
422
|
+
MU.log "Deleting CloudWatch Log Group #{log_id}"
|
|
423
|
+
if !noop
|
|
424
|
+
MU::Cloud::AWS.cloudwatchlogs(region: region, credentials: credentials).delete_log_group(log_group_name: log_id)
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
}
|
|
254
428
|
MU.log "Deleting API Gateway #{api.name} (#{api.id})"
|
|
255
429
|
if !noop
|
|
256
430
|
MU::Cloud::AWS.apig(region: region, credentials: credentials).delete_rest_api(
|
|
@@ -260,6 +434,7 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
260
434
|
end
|
|
261
435
|
}
|
|
262
436
|
end
|
|
437
|
+
|
|
263
438
|
end
|
|
264
439
|
|
|
265
440
|
# Locate an existing API.
|
|
@@ -283,16 +458,214 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
283
458
|
found
|
|
284
459
|
end
|
|
285
460
|
|
|
461
|
+
# Reverse-map our cloud description into a runnable config hash.
|
|
462
|
+
# We assume that any values we have in +@config+ are placeholders, and
|
|
463
|
+
# calculate our own accordingly based on what's live in the cloud.
|
|
464
|
+
def toKitten(**_args)
|
|
465
|
+
bok = {
|
|
466
|
+
"cloud" => "AWS",
|
|
467
|
+
"credentials" => @credentials,
|
|
468
|
+
"cloud_id" => @cloud_id,
|
|
469
|
+
"region" => @config['region']
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if !cloud_desc
|
|
473
|
+
MU.log "toKitten failed to load a cloud_desc from #{@cloud_id}", MU::ERR, details: @config
|
|
474
|
+
return nil
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
bok['name'] = cloud_desc.name
|
|
478
|
+
|
|
479
|
+
resources = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_resources(
|
|
480
|
+
rest_api_id: @cloud_id,
|
|
481
|
+
).items
|
|
482
|
+
|
|
483
|
+
resources.each { |r|
|
|
484
|
+
next if !r.respond_to?(:resource_methods) or r.resource_methods.nil?
|
|
485
|
+
r.resource_methods.each_pair { |http_type, m|
|
|
486
|
+
bok['methods'] ||= []
|
|
487
|
+
method = {}
|
|
488
|
+
m_desc = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_method(
|
|
489
|
+
rest_api_id: @cloud_id,
|
|
490
|
+
resource_id: r.id,
|
|
491
|
+
http_method: http_type
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
method['type'] = http_type
|
|
495
|
+
method['path'] = r.path_part || r.path
|
|
496
|
+
if m_desc.method_responses
|
|
497
|
+
m_desc.method_responses.each_pair { |code, resp_desc|
|
|
498
|
+
method['responses'] ||= []
|
|
499
|
+
resp = { "code" => code.to_i }
|
|
500
|
+
if resp_desc.response_parameters
|
|
501
|
+
resp_desc.response_parameters.each_pair { |hdr, reqd|
|
|
502
|
+
resp['headers'] ||= []
|
|
503
|
+
if hdr.match(/^method\.response\.header\.(.*)/)
|
|
504
|
+
resp['headers'] << {
|
|
505
|
+
"header" => Regexp.last_match[1],
|
|
506
|
+
"required" => reqd
|
|
507
|
+
}
|
|
508
|
+
else
|
|
509
|
+
MU.log "I don't know what to do with APIG response parameter #{hdr}", MU::ERR, details: resp_desc
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
}
|
|
513
|
+
end
|
|
514
|
+
if resp_desc.response_models
|
|
515
|
+
resp_desc.response_models.each_pair { |content_type, body|
|
|
516
|
+
resp['body'] ||= []
|
|
517
|
+
resp['body'] << {
|
|
518
|
+
"content_type" => content_type,
|
|
519
|
+
"is_error" => (body == "Error")
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
end
|
|
524
|
+
method['responses'] << resp
|
|
525
|
+
|
|
526
|
+
}
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
if m_desc.method_integration
|
|
530
|
+
if ["AWS", "AWS_PROXY"].include?(m_desc.method_integration.type)
|
|
531
|
+
if m_desc.method_integration.uri.match(/:lambda:path\/\d{4}-\d{2}-\d{2}\/functions\/arn:.*?:function:(.*?)\/invocations$/)
|
|
532
|
+
method['integrate_with'] = MU::Config::Ref.get(
|
|
533
|
+
id: Regexp.last_match[1],
|
|
534
|
+
type: "functions",
|
|
535
|
+
cloud: "AWS",
|
|
536
|
+
integration_http_method: m_desc.method_integration.http_method
|
|
537
|
+
)
|
|
538
|
+
elsif m_desc.method_integration.uri.match(/#{@config['region']}:([^:]+):action\/(.*)/)
|
|
539
|
+
method['integrate_with'] = {
|
|
540
|
+
"type" => "aws_generic",
|
|
541
|
+
"integration_http_method" => m_desc.method_integration.http_method,
|
|
542
|
+
"aws_generic_action" => Regexp.last_match[1]+":"+Regexp.last_match[2]
|
|
543
|
+
}
|
|
544
|
+
else
|
|
545
|
+
MU.log "I don't know what to do with #{m_desc.method_integration.uri}", MU::ERR
|
|
546
|
+
end
|
|
547
|
+
if m_desc.method_integration.http_method
|
|
548
|
+
method['integrate_with']['backend_http_method'] = m_desc.method_integration.http_method
|
|
549
|
+
end
|
|
550
|
+
method['proxy'] = true if m_desc.method_integration.type == "AWS_PROXY"
|
|
551
|
+
elsif m_desc.method_integration.type == "MOCK"
|
|
552
|
+
method['integrate_with'] = {
|
|
553
|
+
"type" => "mock"
|
|
554
|
+
}
|
|
555
|
+
else
|
|
556
|
+
MU.log "I don't know what to do with this integration", MU::ERR, details: m_desc.method_integration
|
|
557
|
+
next
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
if m_desc.method_integration.passthrough_behavior
|
|
561
|
+
method['integrate_with']['passthrough_behavior'] = m_desc.method_integration.passthrough_behavior
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
if m_desc.method_integration.request_templates and
|
|
565
|
+
!m_desc.method_integration.request_templates.empty?
|
|
566
|
+
method['integrate_with']['request_templates'] = m_desc.method_integration.request_templates.keys.map { |rt_content_type, template|
|
|
567
|
+
{ "content_type" => rt_content_type, "template" => template }
|
|
568
|
+
}
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
if m_desc.method_integration.request_parameters
|
|
572
|
+
m_desc.method_integration.request_parameters.each_pair { |k, v|
|
|
573
|
+
if !k.match(/^integration\.request\.(header|querystring|path)\.(.*)/)
|
|
574
|
+
MU.log "Don't know how to handle integration request parameter '#{k}', skipping", MU::WARN
|
|
575
|
+
next
|
|
576
|
+
end
|
|
577
|
+
if Regexp.last_match[1] == "header" and
|
|
578
|
+
Regexp.last_match[2] == "X-Amz-Invocation-Type" and
|
|
579
|
+
v == "'Event'"
|
|
580
|
+
method['integrate_with']['async'] = true
|
|
581
|
+
else
|
|
582
|
+
method['integrate_with']['parameters'] ||= []
|
|
583
|
+
method['integrate_with']['parameters'] << {
|
|
584
|
+
"type" => Regexp.last_match[1],
|
|
585
|
+
"name" => Regexp.last_match[2],
|
|
586
|
+
"value" => v
|
|
587
|
+
}
|
|
588
|
+
end
|
|
589
|
+
}
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
bok['methods'] << method
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
deployment = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_deployments(
|
|
598
|
+
rest_api_id: @cloud_id
|
|
599
|
+
).items.sort { |a, b| a.created_date <=> b.created_date }.last
|
|
600
|
+
stages = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_stages(
|
|
601
|
+
rest_api_id: @cloud_id,
|
|
602
|
+
deployment_id: deployment.id
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
# XXX we only support a single stage right now, which is a dumb
|
|
606
|
+
# limitation
|
|
607
|
+
stage = stages.item.first
|
|
608
|
+
if stage
|
|
609
|
+
bok['deploy_to'] = stage.stage_name
|
|
610
|
+
if stage.access_log_settings
|
|
611
|
+
bok['log_requests'] = true
|
|
612
|
+
bok['access_logs'] = MU::Config::Ref.get(
|
|
613
|
+
id: stage.access_log_settings.destination_arn.sub(/.*?:([^:]+)$/, '\1'),
|
|
614
|
+
credentials: @credentials,
|
|
615
|
+
region: @config['region'],
|
|
616
|
+
type: "logs",
|
|
617
|
+
cloud: "AWS"
|
|
618
|
+
)
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
bok
|
|
624
|
+
end
|
|
625
|
+
|
|
286
626
|
# Cloud-specific configuration properties.
|
|
287
627
|
# @param _config [MU::Config]: The calling MU::Config object
|
|
288
628
|
# @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
|
|
289
629
|
def self.schema(_config)
|
|
290
630
|
toplevel_required = []
|
|
291
631
|
schema = {
|
|
632
|
+
"domain_names" => {
|
|
633
|
+
"type" => "array",
|
|
634
|
+
"items" => {
|
|
635
|
+
"description" => "Configure optional Custom Domain Names to map to this API endpoint.",
|
|
636
|
+
"type" => "object",
|
|
637
|
+
"properties" => {
|
|
638
|
+
"certificate" => MU::Config::Ref.schema(type: "certificate", desc: "An existing IAM or ACM SSL certificate to bind to this alternate name endpoint.", omit_fields: ["cloud", "tag", "deploy_id"]),
|
|
639
|
+
"dns_record" => MU::Config::DNSZone.records_primitive(need_target: false, default_type: "CNAME", need_zone: true, embedded_type: "endpoint")["items"],
|
|
640
|
+
"unmanaged_name" => {
|
|
641
|
+
"type" => "string",
|
|
642
|
+
"description" => "If +dns_record+ is not specified, we will map this string as a domain name and assume that an external DNS record will be created pointing to us at a later time."
|
|
643
|
+
},
|
|
644
|
+
"endpoint_type" => {
|
|
645
|
+
"type" => "string",
|
|
646
|
+
"description" => "The type of endpoint to create with this domain name.",
|
|
647
|
+
"default" => "REGIONAL",
|
|
648
|
+
"enum" => ["REGIONAL", "EDGE", "PRIVATE"]
|
|
649
|
+
},
|
|
650
|
+
"security_policy" => {
|
|
651
|
+
"type" => "string",
|
|
652
|
+
"default" => "TLS_1_2",
|
|
653
|
+
"enum" => ["TLS_1_0", "TLS_1_2"],
|
|
654
|
+
"description" => "Acceptable TLS cipher suites. +TLS_1_2+ is strongly recommended."
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
},
|
|
292
659
|
"deploy_to" => {
|
|
293
660
|
"type" => "string",
|
|
294
661
|
"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."
|
|
295
662
|
},
|
|
663
|
+
"log_requests" => {
|
|
664
|
+
"type" => "boolean",
|
|
665
|
+
"description" => "Log custom access requests to CloudWatch Logs to the log group specified by +access_logs+, as well as enabling built-in CloudWatch Logs at +INFO+ level. If +access_logs+ is unspecified, a reasonable group will be created automatically.",
|
|
666
|
+
"default" => true
|
|
667
|
+
},
|
|
668
|
+
"access_logs" => MU::Config::Ref.schema(type: "logs", desc: "A pre-existing or sibling Mu Cloudwatch Log group reference. If +log_requests+ is specified and this is not, a log group will be generated automatically. Setting this parameter explicitly automatically enables +log_requests+."),
|
|
296
669
|
"methods" => {
|
|
297
670
|
"items" => {
|
|
298
671
|
"type" => "object",
|
|
@@ -303,16 +676,48 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
303
676
|
"type" => "object",
|
|
304
677
|
"description" => "Specify what application backend to invoke under this path/method combination",
|
|
305
678
|
"properties" => {
|
|
679
|
+
"async" => {
|
|
680
|
+
"type" => "boolean",
|
|
681
|
+
"default" => false,
|
|
682
|
+
"description" => "For non-proxy Lambda integrations, adds a static +X-Amz-Invocation-Type+ with value +'Event'+ to invoke the function asynchronously. See also https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-integration-async.html"
|
|
683
|
+
},
|
|
684
|
+
"parameters" => {
|
|
685
|
+
"type" => "array",
|
|
686
|
+
"items" => {
|
|
687
|
+
"description" => "One or headers, paths, or query string parameters to pass as request parameters to our back end. See also: https://docs.aws.amazon.com/apigateway/latest/developerguide/request-response-data-mappings.html",
|
|
688
|
+
"type" => "object",
|
|
689
|
+
"properties" => {
|
|
690
|
+
"name" => {
|
|
691
|
+
"type" => "string",
|
|
692
|
+
"description" => "A valid and unique integration request parameter name."
|
|
693
|
+
},
|
|
694
|
+
"value" => {
|
|
695
|
+
"type" => "string",
|
|
696
|
+
"description" => "The name of a method request parameter, or a static value contained in single quotes (+'foo'+)."
|
|
697
|
+
},
|
|
698
|
+
"type" => {
|
|
699
|
+
"type" => "string",
|
|
700
|
+
"description" => "Which HTTP artifact to use when presenting the parameter to the back end. ",
|
|
701
|
+
"enum" => ["header", "querystring", "path"]
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
},
|
|
306
706
|
"proxy" => {
|
|
307
707
|
"type" => "boolean",
|
|
308
708
|
"default" => false,
|
|
309
|
-
"description" => "
|
|
709
|
+
"description" => "Sets HTTP integrations to HTTP_PROXY and AWS/LAMBDA integrations to AWS_PROXY/LAMBDA_PROXY"
|
|
310
710
|
},
|
|
311
711
|
"backend_http_method" => {
|
|
312
712
|
"type" => "string",
|
|
313
713
|
"description" => "The HTTP method to use when contacting our integrated backend. If not specified, this will be set to match our front end.",
|
|
314
714
|
"enum" => ["GET", "POST", "PUT", "HEAD", "DELETE", "CONNECT", "OPTIONS", "TRACE"],
|
|
315
715
|
},
|
|
716
|
+
"timeout_in_millis" => {
|
|
717
|
+
"type" => "integer",
|
|
718
|
+
"description" => "Custom timeout between +50+ and +29,000+ milliseconds.",
|
|
719
|
+
"default" => 29000
|
|
720
|
+
},
|
|
316
721
|
"url" => {
|
|
317
722
|
"type" => "string",
|
|
318
723
|
"description" => "For HTTP or HTTP_PROXY integrations, this should be a fully-qualified URL"
|
|
@@ -380,14 +785,13 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
380
785
|
"description" => "A Mu resource name, for integrations with a sibling resource (e.g. a Function)"
|
|
381
786
|
},
|
|
382
787
|
"cors" => {
|
|
383
|
-
"type" => "
|
|
384
|
-
"description" => "When enabled, this will create an +OPTIONS+ method under this path with request and response header mappings that implement Cross-Origin Resource Sharing",
|
|
385
|
-
"default" => true
|
|
788
|
+
"type" => "string",
|
|
789
|
+
"description" => "When enabled, this will create an +OPTIONS+ method under this path with request and response header mappings that implement Cross-Origin Resource Sharing, setting +Access-Control-Allow-Origin+ to the specified value.",
|
|
386
790
|
},
|
|
387
791
|
"type" => {
|
|
388
792
|
"type" => "string",
|
|
389
793
|
"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.",
|
|
390
|
-
"enum" => ["aws_generic"].concat(MU::Cloud.resource_types.values.map { |t| t[:
|
|
794
|
+
"enum" => ["aws_generic"].concat(MU::Cloud.resource_types.values.map { |t| t[:cfg_plural] }.sort)
|
|
391
795
|
},
|
|
392
796
|
"aws_generic_action" => {
|
|
393
797
|
"type" => "string",
|
|
@@ -456,7 +860,7 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
456
860
|
# Canonical Amazon Resource Number for this resource
|
|
457
861
|
# @return [String]
|
|
458
862
|
def arn
|
|
459
|
-
"arn:#{MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws"}:execute-api:#{@config["region"]}:#{MU::Cloud::AWS.credToAcct(@
|
|
863
|
+
"arn:#{MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws"}:execute-api:#{@config["region"]}:#{MU::Cloud::AWS.credToAcct(@credentials)}:#{@cloud_id}"
|
|
460
864
|
end
|
|
461
865
|
|
|
462
866
|
|
|
@@ -467,9 +871,89 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
467
871
|
def self.validateConfig(endpoint, configurator)
|
|
468
872
|
ok = true
|
|
469
873
|
|
|
874
|
+
if endpoint['log_requests'] and !endpoint['access_logs']
|
|
875
|
+
logdesc = {
|
|
876
|
+
"name" => endpoint['name']+"accesslogs",
|
|
877
|
+
}
|
|
878
|
+
logdesc["tags"] = endpoint["tags"] if endpoint['tags']
|
|
879
|
+
configurator.insertKitten(logdesc, "logs")
|
|
880
|
+
endpoint['access_logs'] = MU::Config::Ref.get(
|
|
881
|
+
name: endpoint['name']+"accesslogs",
|
|
882
|
+
type: "log",
|
|
883
|
+
cloud: "AWS",
|
|
884
|
+
credentials: endpoint['credentials'],
|
|
885
|
+
region: endpoint['region']
|
|
886
|
+
)
|
|
887
|
+
end
|
|
888
|
+
|
|
889
|
+
if endpoint['access_logs'] and endpoint["access_logs"]["name"]
|
|
890
|
+
endpoint['log_requests'] = true
|
|
891
|
+
MU::Config.addDependency(endpoint, endpoint["access_logs"]["name"], "log")
|
|
892
|
+
end
|
|
893
|
+
|
|
894
|
+
if endpoint['access_logs']
|
|
895
|
+
resp = MU::Cloud::AWS.apig(credentials: endpoint['credentials'], region: endpoint['region']).get_account
|
|
896
|
+
if !resp.cloudwatch_role_arn
|
|
897
|
+
MU.log "Endpoint '#{endpoint['name']}' is configured to use CloudWatch Logs, but the account-wide API Gateway log role is not configured", MU::ERR, details: "https://aws.amazon.com/premiumsupport/knowledge-center/api-gateway-cloudwatch-logs/"
|
|
898
|
+
ok = false
|
|
899
|
+
else
|
|
900
|
+
roles = MU::Cloud::AWS::Role.find(cloud_id: resp.cloudwatch_role_arn, credentials: endpoint['credentials'], region: endpoint['region'])
|
|
901
|
+
if roles.empty?
|
|
902
|
+
MU.log "Endpoint '#{endpoint['name']}' is configured to use CloudWatch Logs, but the configured account-wide API Gateway log role does not exist", MU::ERR, details: resp.cloudwatch_role_arn
|
|
903
|
+
ok = false
|
|
904
|
+
end
|
|
905
|
+
end
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
if endpoint['domain_names']
|
|
909
|
+
endpoint['domain_names'].each { |dom|
|
|
910
|
+
if dom['certificate']
|
|
911
|
+
cert_arn, cert_domains = MU::Cloud::AWS.resolveSSLCertificate(dom['certificate'], region: dom['region'], credentials: dom['credentials'])
|
|
912
|
+
if !cert_arn
|
|
913
|
+
MU.log "API Gateway #{endpoint['name']}: Failed to resolve SSL certificate in domain_name block", MU::ERR, details: dom
|
|
914
|
+
ok = false
|
|
915
|
+
end
|
|
916
|
+
end
|
|
917
|
+
if !dom['unmanaged_name'] and !dom['dns_record']
|
|
918
|
+
MU.log "API Gateway #{endpoint['name']}: Must specify either unmanaged_name or dns_record in domain_name block", MU::ERR, details: dom
|
|
919
|
+
ok = false
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
# Make at least an attempt to catch when we've specified the same
|
|
923
|
+
# DNS name to point to both the main gateway and this alternative
|
|
924
|
+
# endpoint, because that ish won't work. This check will miss if
|
|
925
|
+
# the end user specifies the zone in competing ways.
|
|
926
|
+
if dom['dns_record'] and endpoint['dns_records']
|
|
927
|
+
endpoint['dns_records'].each { |rec|
|
|
928
|
+
if rec['name'] == dom['dns_record']['name'] and
|
|
929
|
+
rec['zone'] == dom['dns_record']['zone']
|
|
930
|
+
MU.log "API Gateway #{endpoint['name']}: Cannot specify same entry in dns_records and domain_names", MU::ERR, details: rec
|
|
931
|
+
ok = false
|
|
932
|
+
end
|
|
933
|
+
}
|
|
934
|
+
end
|
|
935
|
+
}
|
|
936
|
+
end
|
|
937
|
+
|
|
470
938
|
append = []
|
|
471
939
|
endpoint['deploy_to'] ||= MU.environment || $environment || "dev"
|
|
472
940
|
endpoint['methods'].each { |m|
|
|
941
|
+
if m['integrate_with']['async']
|
|
942
|
+
if m['integrate_with']['type'] == "functions" and
|
|
943
|
+
m['integrate_with']['async']
|
|
944
|
+
m['integrate_with']['parameters'] ||= []
|
|
945
|
+
m['integrate_with']['parameters'] << {
|
|
946
|
+
"name" => "X-Amz-Invocation-Type",
|
|
947
|
+
"value" => "'Event'", # yes the single quotes are required
|
|
948
|
+
"type" => "header"
|
|
949
|
+
}
|
|
950
|
+
if m['integrate_with']['proxy']
|
|
951
|
+
MU.log "Cannot specify both of proxy and async for API Gateway method integration", MU::ERR
|
|
952
|
+
ok = false
|
|
953
|
+
end
|
|
954
|
+
end
|
|
955
|
+
end
|
|
956
|
+
|
|
473
957
|
if m['integrate_with'] and m['integrate_with']['name']
|
|
474
958
|
if m['integrate_with']['type'] != "aws_generic"
|
|
475
959
|
MU::Config.addDependency(endpoint, m['integrate_with']['name'], m['integrate_with']['type'])
|
|
@@ -486,15 +970,16 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
486
970
|
r['headers'] ||= []
|
|
487
971
|
r['headers'] << {
|
|
488
972
|
"header" => "Access-Control-Allow-Origin",
|
|
489
|
-
"value" =>
|
|
973
|
+
"value" => m['cors'],
|
|
490
974
|
"required" => true
|
|
491
975
|
}
|
|
492
976
|
r['headers'].uniq!
|
|
493
977
|
}
|
|
494
978
|
|
|
495
|
-
append << cors_option_integrations(m['path'])
|
|
979
|
+
append << cors_option_integrations(m['path'], m['cors'])
|
|
496
980
|
end
|
|
497
981
|
|
|
982
|
+
|
|
498
983
|
if !m['iam_role']
|
|
499
984
|
m['uri'] ||= "*" if m['integrate_with']['type'] == "aws_generic"
|
|
500
985
|
|
|
@@ -516,7 +1001,7 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
516
1001
|
"targets" => [{ "identifier" => m['uri'] }]
|
|
517
1002
|
}
|
|
518
1003
|
]
|
|
519
|
-
elsif m['integrate_with']['type'] == "
|
|
1004
|
+
elsif m['integrate_with']['type'] == "functions"
|
|
520
1005
|
roledesc["import"] = ["AWSLambdaBasicExecutionRole"]
|
|
521
1006
|
end
|
|
522
1007
|
configurator.insertKitten(roledesc, "roles")
|
|
@@ -534,7 +1019,7 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
534
1019
|
ok
|
|
535
1020
|
end
|
|
536
1021
|
|
|
537
|
-
def self.cors_option_integrations(path)
|
|
1022
|
+
def self.cors_option_integrations(path, origins)
|
|
538
1023
|
{
|
|
539
1024
|
"type" => "OPTIONS",
|
|
540
1025
|
"path" => path,
|
|
@@ -555,7 +1040,7 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
|
|
|
555
1040
|
},
|
|
556
1041
|
{
|
|
557
1042
|
"header" => "Access-Control-Allow-Origin",
|
|
558
|
-
"value" =>
|
|
1043
|
+
"value" => origins,
|
|
559
1044
|
"required" => true
|
|
560
1045
|
}
|
|
561
1046
|
],
|