cloud-mu 3.2.0 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
],
|