cloud-mu 3.1.5 → 3.3.2
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 +5 -1
- data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
- data/ansible/roles/mu-windows/files/config.xml +76 -0
- data/ansible/roles/mu-windows/tasks/main.yml +16 -0
- data/bin/mu-adopt +16 -12
- data/bin/mu-azure-tests +57 -0
- data/bin/mu-cleanup +2 -4
- data/bin/mu-configure +52 -0
- data/bin/mu-deploy +3 -3
- data/bin/mu-findstray-tests +25 -0
- data/bin/mu-gen-docs +2 -4
- data/bin/mu-load-config.rb +2 -1
- data/bin/mu-node-manage +15 -16
- data/bin/mu-run-tests +37 -12
- data/cloud-mu.gemspec +3 -3
- data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
- data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
- data/cookbooks/mu-tools/libraries/helper.rb +1 -1
- data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
- data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
- data/cookbooks/mu-tools/recipes/eks.rb +2 -2
- data/cookbooks/mu-tools/recipes/windows-client.rb +25 -22
- data/extras/clean-stock-amis +25 -19
- data/extras/generate-stock-images +1 -0
- data/extras/image-generators/AWS/win2k12.yaml +2 -0
- data/extras/image-generators/AWS/win2k16.yaml +2 -0
- data/extras/image-generators/AWS/win2k19.yaml +2 -0
- data/modules/mommacat.ru +1 -1
- data/modules/mu.rb +86 -98
- data/modules/mu/adoption.rb +373 -58
- data/modules/mu/cleanup.rb +214 -303
- data/modules/mu/cloud.rb +128 -1733
- data/modules/mu/cloud/database.rb +49 -0
- data/modules/mu/cloud/dnszone.rb +44 -0
- data/modules/mu/cloud/machine_images.rb +212 -0
- data/modules/mu/cloud/providers.rb +81 -0
- data/modules/mu/cloud/resource_base.rb +929 -0
- data/modules/mu/cloud/server.rb +40 -0
- data/modules/mu/cloud/server_pool.rb +1 -0
- data/modules/mu/cloud/ssh_sessions.rb +228 -0
- data/modules/mu/cloud/winrm_sessions.rb +237 -0
- data/modules/mu/cloud/wrappers.rb +169 -0
- data/modules/mu/config.rb +123 -81
- data/modules/mu/config/alarm.rb +2 -6
- data/modules/mu/config/bucket.rb +32 -3
- data/modules/mu/config/cache_cluster.rb +2 -2
- data/modules/mu/config/cdn.rb +100 -0
- data/modules/mu/config/collection.rb +1 -1
- data/modules/mu/config/container_cluster.rb +7 -2
- data/modules/mu/config/database.rb +84 -105
- data/modules/mu/config/database.yml +1 -2
- data/modules/mu/config/dnszone.rb +5 -4
- data/modules/mu/config/doc_helpers.rb +5 -6
- data/modules/mu/config/endpoint.rb +2 -1
- data/modules/mu/config/firewall_rule.rb +3 -19
- data/modules/mu/config/folder.rb +1 -1
- data/modules/mu/config/function.rb +17 -8
- data/modules/mu/config/group.rb +1 -1
- data/modules/mu/config/habitat.rb +1 -1
- data/modules/mu/config/job.rb +89 -0
- data/modules/mu/config/loadbalancer.rb +57 -11
- data/modules/mu/config/log.rb +1 -1
- data/modules/mu/config/msg_queue.rb +1 -1
- data/modules/mu/config/nosqldb.rb +1 -1
- data/modules/mu/config/notifier.rb +8 -19
- data/modules/mu/config/ref.rb +92 -14
- data/modules/mu/config/role.rb +1 -1
- data/modules/mu/config/schema_helpers.rb +38 -37
- data/modules/mu/config/search_domain.rb +1 -1
- data/modules/mu/config/server.rb +12 -13
- data/modules/mu/config/server_pool.rb +3 -7
- data/modules/mu/config/storage_pool.rb +1 -1
- data/modules/mu/config/tail.rb +11 -0
- data/modules/mu/config/user.rb +1 -1
- data/modules/mu/config/vpc.rb +27 -23
- data/modules/mu/config/vpc.yml +0 -1
- data/modules/mu/defaults/AWS.yaml +90 -90
- data/modules/mu/defaults/Azure.yaml +1 -0
- data/modules/mu/defaults/Google.yaml +1 -0
- data/modules/mu/deploy.rb +34 -20
- data/modules/mu/groomer.rb +16 -1
- data/modules/mu/groomers/ansible.rb +69 -4
- data/modules/mu/groomers/chef.rb +51 -4
- data/modules/mu/logger.rb +120 -144
- data/modules/mu/master.rb +97 -4
- data/modules/mu/mommacat.rb +160 -874
- data/modules/mu/mommacat/daemon.rb +23 -14
- data/modules/mu/mommacat/naming.rb +110 -3
- data/modules/mu/mommacat/search.rb +497 -0
- data/modules/mu/mommacat/storage.rb +252 -194
- data/modules/mu/{clouds → providers}/README.md +1 -1
- data/modules/mu/{clouds → providers}/aws.rb +258 -57
- data/modules/mu/{clouds → providers}/aws/alarm.rb +3 -3
- data/modules/mu/{clouds → providers}/aws/bucket.rb +275 -41
- data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +14 -50
- data/modules/mu/providers/aws/cdn.rb +782 -0
- data/modules/mu/{clouds → providers}/aws/collection.rb +5 -5
- data/modules/mu/{clouds → providers}/aws/container_cluster.rb +95 -84
- data/modules/mu/providers/aws/database.rb +1744 -0
- data/modules/mu/{clouds → providers}/aws/dnszone.rb +26 -12
- data/modules/mu/providers/aws/endpoint.rb +1072 -0
- data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +39 -32
- data/modules/mu/{clouds → providers}/aws/folder.rb +1 -1
- data/modules/mu/{clouds → providers}/aws/function.rb +289 -134
- data/modules/mu/{clouds → providers}/aws/group.rb +18 -20
- data/modules/mu/{clouds → providers}/aws/habitat.rb +3 -3
- data/modules/mu/providers/aws/job.rb +466 -0
- data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +77 -47
- data/modules/mu/{clouds → providers}/aws/log.rb +5 -5
- data/modules/mu/{clouds → providers}/aws/msg_queue.rb +14 -11
- data/modules/mu/{clouds → providers}/aws/nosqldb.rb +96 -5
- data/modules/mu/{clouds → providers}/aws/notifier.rb +135 -63
- data/modules/mu/{clouds → providers}/aws/role.rb +76 -48
- data/modules/mu/{clouds → providers}/aws/search_domain.rb +172 -41
- data/modules/mu/{clouds → providers}/aws/server.rb +66 -98
- data/modules/mu/{clouds → providers}/aws/server_pool.rb +42 -60
- data/modules/mu/{clouds → providers}/aws/storage_pool.rb +21 -38
- data/modules/mu/{clouds → providers}/aws/user.rb +12 -16
- data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
- data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
- data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +0 -0
- data/modules/mu/{clouds → providers}/aws/vpc.rb +143 -74
- data/modules/mu/{clouds → providers}/aws/vpc_subnet.rb +0 -0
- data/modules/mu/{clouds → providers}/azure.rb +13 -0
- data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
- data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
- data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
- data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
- data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
- data/modules/mu/{clouds → providers}/azure/server.rb +32 -24
- data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
- data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
- data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
- data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
- data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
- data/modules/mu/{clouds → providers}/cloudformation.rb +10 -0
- data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
- data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
- data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
- data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
- data/modules/mu/{clouds → providers}/docker.rb +0 -0
- data/modules/mu/{clouds → providers}/google.rb +29 -6
- data/modules/mu/{clouds → providers}/google/bucket.rb +4 -4
- data/modules/mu/{clouds → providers}/google/container_cluster.rb +38 -20
- data/modules/mu/{clouds → providers}/google/database.rb +5 -12
- data/modules/mu/{clouds → providers}/google/firewall_rule.rb +5 -5
- data/modules/mu/{clouds → providers}/google/folder.rb +5 -9
- data/modules/mu/{clouds → providers}/google/function.rb +6 -6
- data/modules/mu/{clouds → providers}/google/group.rb +9 -17
- data/modules/mu/{clouds → providers}/google/habitat.rb +4 -8
- data/modules/mu/{clouds → providers}/google/loadbalancer.rb +5 -5
- data/modules/mu/{clouds → providers}/google/role.rb +50 -31
- data/modules/mu/{clouds → providers}/google/server.rb +41 -24
- data/modules/mu/{clouds → providers}/google/server_pool.rb +14 -14
- data/modules/mu/{clouds → providers}/google/user.rb +34 -24
- data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
- data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
- data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
- data/modules/mu/{clouds → providers}/google/vpc.rb +45 -14
- data/modules/tests/aws-jobs-functions.yaml +46 -0
- data/modules/tests/centos6.yaml +15 -0
- data/modules/tests/centos7.yaml +15 -0
- data/modules/tests/centos8.yaml +12 -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 +108 -0
- data/modules/tests/regrooms/rds.yaml +123 -0
- 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 +3 -5
- data/spec/mu/clouds/azure_spec.rb +2 -2
- metadata +122 -92
- data/modules/mu/clouds/aws/database.rb +0 -1974
- data/modules/mu/clouds/aws/endpoint.rb +0 -596
|
@@ -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?
|
|
@@ -345,7 +362,7 @@ module MU
|
|
|
345
362
|
rescue Aws::Route53::Errors::LastVPCAssociation => e
|
|
346
363
|
MU.log e.inspect, MU::WARN
|
|
347
364
|
rescue Aws::Route53::Errors::VPCAssociationNotFound
|
|
348
|
-
MU.log "VPC #{vpc_id} access to zone #{id} already revoked", MU::
|
|
365
|
+
MU.log "VPC #{vpc_id} access to zone #{id} already revoked", MU::NOTICE
|
|
349
366
|
end
|
|
350
367
|
end
|
|
351
368
|
end
|
|
@@ -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
|
|
@@ -825,10 +842,7 @@ module MU
|
|
|
825
842
|
end
|
|
826
843
|
|
|
827
844
|
if !record['mu_type'].nil?
|
|
828
|
-
zone[
|
|
829
|
-
"type" => record['mu_type'],
|
|
830
|
-
"name" => record['target']
|
|
831
|
-
}
|
|
845
|
+
MU::Config.addDependency(zone, record['target'], record['mu_type'])
|
|
832
846
|
end
|
|
833
847
|
|
|
834
848
|
if record.has_key?('healthchecks') && !record['healthchecks'].empty?
|
|
@@ -0,0 +1,1072 @@
|
|
|
1
|
+
module MU
|
|
2
|
+
class Cloud
|
|
3
|
+
class AWS
|
|
4
|
+
# An API as configured in {MU::Config::BasketofKittens::endpoints}
|
|
5
|
+
class Endpoint < MU::Cloud::Endpoint
|
|
6
|
+
|
|
7
|
+
# Initialize this cloud resource object. Calling +super+ will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like +@vpc+, for us.
|
|
8
|
+
# @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
|
|
9
|
+
def initialize(**args)
|
|
10
|
+
super
|
|
11
|
+
@mu_name ||= @deploy.getResourceName(@config["name"])
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Called automatically by {MU::Deploy#createResources}
|
|
15
|
+
def create
|
|
16
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).create_rest_api(
|
|
17
|
+
name: @mu_name,
|
|
18
|
+
description: @deploy.deploy_id,
|
|
19
|
+
endpoint_configuration: {
|
|
20
|
+
types: ["REGIONAL"] # XXX expose in BoK ["REGIONAL", "EDGE", "PRIVATE"]
|
|
21
|
+
},
|
|
22
|
+
tags: @tags
|
|
23
|
+
)
|
|
24
|
+
@cloud_id = resp.id
|
|
25
|
+
generate_methods(false)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Create/update all of the methods declared for this endpoint
|
|
29
|
+
def generate_methods(integrations = true)
|
|
30
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_resources(
|
|
31
|
+
rest_api_id: @cloud_id,
|
|
32
|
+
)
|
|
33
|
+
root_resource = resp.items.first.id
|
|
34
|
+
|
|
35
|
+
# TODO guard this crap so we don't touch it if there are no changes
|
|
36
|
+
@config['methods'].each { |m|
|
|
37
|
+
m["auth"] ||= m["iam_role"] ? "AWS_IAM" : "NONE"
|
|
38
|
+
|
|
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!(/\/\/$/, '/')
|
|
42
|
+
|
|
43
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_resources(
|
|
44
|
+
rest_api_id: @cloud_id
|
|
45
|
+
)
|
|
46
|
+
ext_resource = nil
|
|
47
|
+
resp.items.each { |resource|
|
|
48
|
+
if resource.path_part == path_part
|
|
49
|
+
ext_resource = resource.id
|
|
50
|
+
end
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
resp = if ext_resource
|
|
54
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_resource(
|
|
55
|
+
rest_api_id: @cloud_id,
|
|
56
|
+
resource_id: ext_resource,
|
|
57
|
+
)
|
|
58
|
+
# MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).update_resource(
|
|
59
|
+
# rest_api_id: @cloud_id,
|
|
60
|
+
# resource_id: ext_resource,
|
|
61
|
+
# patch_operations: [
|
|
62
|
+
# {
|
|
63
|
+
# op: "replace",
|
|
64
|
+
# path: "XXX ??",
|
|
65
|
+
# value: m["path"]
|
|
66
|
+
# }
|
|
67
|
+
# ]
|
|
68
|
+
# )
|
|
69
|
+
else
|
|
70
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).create_resource(
|
|
71
|
+
rest_api_id: @cloud_id,
|
|
72
|
+
parent_id: root_resource,
|
|
73
|
+
path_part: path_part
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
parent_id = resp.id
|
|
77
|
+
|
|
78
|
+
resp = begin
|
|
79
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_method(
|
|
80
|
+
rest_api_id: @cloud_id,
|
|
81
|
+
resource_id: parent_id,
|
|
82
|
+
http_method: m['type']
|
|
83
|
+
)
|
|
84
|
+
rescue Aws::APIGateway::Errors::NotFoundException
|
|
85
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).put_method(
|
|
86
|
+
rest_api_id: @cloud_id,
|
|
87
|
+
resource_id: parent_id,
|
|
88
|
+
authorization_type: m['auth'],
|
|
89
|
+
http_method: m['type']
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# XXX effectively a placeholder default
|
|
94
|
+
begin
|
|
95
|
+
m['responses'].each { |r|
|
|
96
|
+
params = {
|
|
97
|
+
:rest_api_id => @cloud_id,
|
|
98
|
+
:resource_id => parent_id,
|
|
99
|
+
:http_method => m['type'],
|
|
100
|
+
:status_code => r['code'].to_s
|
|
101
|
+
}
|
|
102
|
+
if r['headers']
|
|
103
|
+
params[:response_parameters] = r['headers'].map { |h|
|
|
104
|
+
h['required'] ||= false
|
|
105
|
+
["method.response.header."+h['header'], h['required']]
|
|
106
|
+
}.to_h
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if r['body']
|
|
110
|
+
# XXX I'm guessing we can also have arbirary user-defined models somehow, so is_error is probably inadequate to the demand of the times
|
|
111
|
+
params[:response_models] = r['body'].map { |b| [b['content_type'], b['is_error'] ? "Error" : "Empty"] }.to_h
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).put_method_response(params)
|
|
115
|
+
}
|
|
116
|
+
rescue Aws::APIGateway::Errors::ConflictException
|
|
117
|
+
# fine to ignore
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
if integrations and m['integrate_with']
|
|
121
|
+
# role_arn = if m['iam_role']
|
|
122
|
+
# if m['iam_role'].match(/^arn:/)
|
|
123
|
+
# m['iam_role']
|
|
124
|
+
# else
|
|
125
|
+
# sib_role = @deploy.findLitterMate(name: m['iam_role'], type: "roles")
|
|
126
|
+
# sib_role.cloudobj.arn
|
|
127
|
+
# XXX make this more like get_role_arn in Function, or just use Role.find?
|
|
128
|
+
# end
|
|
129
|
+
# end
|
|
130
|
+
|
|
131
|
+
function_obj = nil
|
|
132
|
+
aws_int_type = m['integrate_with']['proxy'] ? "AWS_PROXY" : "AWS"
|
|
133
|
+
|
|
134
|
+
uri, type = if m['integrate_with']['type'] == "aws_generic"
|
|
135
|
+
svc, action = m['integrate_with']['aws_generic_action'].split(/:/)
|
|
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]
|
|
143
|
+
elsif m['integrate_with']['type'] == "mock"
|
|
144
|
+
[nil, "MOCK"]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
params = {
|
|
148
|
+
:rest_api_id => @cloud_id,
|
|
149
|
+
:resource_id => parent_id,
|
|
150
|
+
:type => type, # XXX Lambda and Firehose can do AWS_PROXY
|
|
151
|
+
:content_handling => "CONVERT_TO_TEXT", # XXX expose in BoK
|
|
152
|
+
:http_method => m['type'],
|
|
153
|
+
:timeout_in_millis => m['timeout_in_millis']
|
|
154
|
+
# credentials: role_arn
|
|
155
|
+
}
|
|
156
|
+
params[:uri] = uri if uri
|
|
157
|
+
|
|
158
|
+
if m['integrate_with']['type'] != "mock"
|
|
159
|
+
params[:integration_http_method] = m['integrate_with']['backend_http_method']
|
|
160
|
+
else
|
|
161
|
+
params[:integration_http_method] = nil
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
if m['integrate_with']['passthrough_behavior']
|
|
165
|
+
params[:passthrough_behavior] = m['integrate_with']['passthrough_behavior']
|
|
166
|
+
end
|
|
167
|
+
if m['integrate_with']['request_templates']
|
|
168
|
+
params[:request_templates] = {}
|
|
169
|
+
m['integrate_with']['request_templates'].each { |rt|
|
|
170
|
+
params[:request_templates][rt['content_type']] = rt['template']
|
|
171
|
+
}
|
|
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
|
|
178
|
+
|
|
179
|
+
resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).put_integration(params)
|
|
180
|
+
|
|
181
|
+
if m['integrate_with']['type'] =~ /^functions?$/
|
|
182
|
+
function_obj.addTrigger(method_arn, "apigateway", @config['name'])
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
m['responses'].each { |r|
|
|
186
|
+
params = {
|
|
187
|
+
:rest_api_id => @cloud_id,
|
|
188
|
+
:resource_id => parent_id,
|
|
189
|
+
:http_method => m['type'],
|
|
190
|
+
:status_code => r['code'].to_s,
|
|
191
|
+
:selection_pattern => ".*"
|
|
192
|
+
}
|
|
193
|
+
if r['headers']
|
|
194
|
+
params[:response_parameters] = r['headers'].map { |h|
|
|
195
|
+
["method.response.header."+h['header'], "'"+h['value']+"'"]
|
|
196
|
+
}.to_h
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).put_integration_response(params)
|
|
200
|
+
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
}
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Called automatically by {MU::Deploy#createResources}
|
|
209
|
+
def groom
|
|
210
|
+
generate_methods
|
|
211
|
+
|
|
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']
|
|
221
|
+
# cache_cluster_enabled: false,
|
|
222
|
+
# cache_cluster_size: 0.5,
|
|
223
|
+
)
|
|
224
|
+
end
|
|
225
|
+
# this automatically creates a stage with the same name, so we don't
|
|
226
|
+
# have to deal with that
|
|
227
|
+
|
|
228
|
+
my_hostname = @cloud_id+".execute-api."+@config['region']+".amazonaws.com"
|
|
229
|
+
my_url = "https://"+my_hostname+"/"+@config['deploy_to']
|
|
230
|
+
MU.log "API Endpoint #{@config['name']}: "+my_url, MU::SUMMARY
|
|
231
|
+
|
|
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(
|
|
354
|
+
# rest_api_id: @cloud_id,
|
|
355
|
+
# )
|
|
356
|
+
|
|
357
|
+
# resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).create_vpc_link(
|
|
358
|
+
# )
|
|
359
|
+
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
@cloud_desc_cache = nil
|
|
363
|
+
# @return [Struct]
|
|
364
|
+
def cloud_desc(use_cache: true)
|
|
365
|
+
return @cloud_desc_cache if @cloud_desc_cache and use_cache
|
|
366
|
+
return nil if !@cloud_id
|
|
367
|
+
@cloud_desc_cache = MU::Cloud::AWS.apig(region: @config['region'], credentials: @credentials).get_rest_api(
|
|
368
|
+
rest_api_id: @cloud_id
|
|
369
|
+
)
|
|
370
|
+
@cloud_desc_cache
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Return the metadata for this API
|
|
374
|
+
# @return [Hash]
|
|
375
|
+
def notify
|
|
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']
|
|
380
|
+
# XXX stages and whatnot
|
|
381
|
+
return deploy_struct
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Remove all APIs associated with the currently loaded deployment.
|
|
385
|
+
# @param noop [Boolean]: If true, will only print what would be done
|
|
386
|
+
# @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
|
|
387
|
+
# @param region [String]: The cloud provider region
|
|
388
|
+
# @return [void]
|
|
389
|
+
def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
|
390
|
+
MU.log "AWS::Endpoint.cleanup: need to support flags['known']", MU::DEBUG, details: flags
|
|
391
|
+
MU.log "Placeholder: AWS Endpoint artifacts do not support tags, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: ignoremaster
|
|
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
|
+
|
|
414
|
+
resp = MU::Cloud::AWS.apig(region: region, credentials: credentials).get_rest_apis
|
|
415
|
+
if resp and resp.items
|
|
416
|
+
resp.items.each { |api|
|
|
417
|
+
# The stupid things don't have tags
|
|
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
|
+
}
|
|
428
|
+
MU.log "Deleting API Gateway #{api.name} (#{api.id})"
|
|
429
|
+
if !noop
|
|
430
|
+
MU::Cloud::AWS.apig(region: region, credentials: credentials).delete_rest_api(
|
|
431
|
+
rest_api_id: api.id
|
|
432
|
+
)
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
}
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# Locate an existing API.
|
|
441
|
+
# @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching APIs.
|
|
442
|
+
def self.find(**args)
|
|
443
|
+
found = {}
|
|
444
|
+
|
|
445
|
+
if args[:cloud_id]
|
|
446
|
+
found[args[:cloud_id]] = MU::Cloud::AWS.apig(region: args[:region], credentials: args[:credentials]).get_rest_api(
|
|
447
|
+
rest_api_id: args[:cloud_id]
|
|
448
|
+
)
|
|
449
|
+
else
|
|
450
|
+
resp = MU::Cloud::AWS.apig(region: args[:region], credentials: args[:credentials]).get_rest_apis
|
|
451
|
+
if resp and resp.items
|
|
452
|
+
resp.items.each { |api|
|
|
453
|
+
found[api.id] = api
|
|
454
|
+
}
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
found
|
|
459
|
+
end
|
|
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
|
+
|
|
626
|
+
# Cloud-specific configuration properties.
|
|
627
|
+
# @param _config [MU::Config]: The calling MU::Config object
|
|
628
|
+
# @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
|
|
629
|
+
def self.schema(_config)
|
|
630
|
+
toplevel_required = []
|
|
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
|
+
},
|
|
659
|
+
"deploy_to" => {
|
|
660
|
+
"type" => "string",
|
|
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."
|
|
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+."),
|
|
669
|
+
"methods" => {
|
|
670
|
+
"items" => {
|
|
671
|
+
"type" => "object",
|
|
672
|
+
"description" => "Other cloud resources to integrate as a back end to this API Gateway",
|
|
673
|
+
"required" => ["integrate_with"],
|
|
674
|
+
"properties" => {
|
|
675
|
+
"integrate_with" => {
|
|
676
|
+
"type" => "object",
|
|
677
|
+
"description" => "Specify what application backend to invoke under this path/method combination",
|
|
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
|
+
},
|
|
706
|
+
"proxy" => {
|
|
707
|
+
"type" => "boolean",
|
|
708
|
+
"default" => false,
|
|
709
|
+
"description" => "Sets HTTP integrations to HTTP_PROXY and AWS/LAMBDA integrations to AWS_PROXY/LAMBDA_PROXY"
|
|
710
|
+
},
|
|
711
|
+
"backend_http_method" => {
|
|
712
|
+
"type" => "string",
|
|
713
|
+
"description" => "The HTTP method to use when contacting our integrated backend. If not specified, this will be set to match our front end.",
|
|
714
|
+
"enum" => ["GET", "POST", "PUT", "HEAD", "DELETE", "CONNECT", "OPTIONS", "TRACE"],
|
|
715
|
+
},
|
|
716
|
+
"timeout_in_millis" => {
|
|
717
|
+
"type" => "integer",
|
|
718
|
+
"description" => "Custom timeout between +50+ and +29,000+ milliseconds.",
|
|
719
|
+
"default" => 29000
|
|
720
|
+
},
|
|
721
|
+
"url" => {
|
|
722
|
+
"type" => "string",
|
|
723
|
+
"description" => "For HTTP or HTTP_PROXY integrations, this should be a fully-qualified URL"
|
|
724
|
+
},
|
|
725
|
+
"responses"=> {
|
|
726
|
+
"type" => "array",
|
|
727
|
+
"items" => {
|
|
728
|
+
"type" => "object",
|
|
729
|
+
"description" => "Customize the response to the client for this method, by adding headers or transforming through a template. If not specified, we will default to returning an un-transformed HTTP 200 for this method.",
|
|
730
|
+
"properties" => {
|
|
731
|
+
"code" => {
|
|
732
|
+
"type" => "integer",
|
|
733
|
+
"description" => "The HTTP status code to return",
|
|
734
|
+
"default" => 200
|
|
735
|
+
},
|
|
736
|
+
"headers" => {
|
|
737
|
+
"type" => "array",
|
|
738
|
+
"items" => {
|
|
739
|
+
"description" => "One or more headers, used by the API Gateway integration response and filtered through the method response before returning to the client",
|
|
740
|
+
"type" => "object",
|
|
741
|
+
"properties" => {
|
|
742
|
+
"header" => {
|
|
743
|
+
"type" => "string",
|
|
744
|
+
"description" => "The name of a header to return, such as +Access-Control-Allow-Methods+"
|
|
745
|
+
},
|
|
746
|
+
"value" => {
|
|
747
|
+
"type" => "string",
|
|
748
|
+
"description" => "The string to map to this header (ex +GET,OPTIONS+)"
|
|
749
|
+
},
|
|
750
|
+
"required" => {
|
|
751
|
+
"type" => "boolean",
|
|
752
|
+
"description" => "Indicate whether this header is required in order to return a response",
|
|
753
|
+
"default" => true
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
},
|
|
758
|
+
"body" => {
|
|
759
|
+
"type" => "array",
|
|
760
|
+
"items" => {
|
|
761
|
+
"type" => "object",
|
|
762
|
+
"description" => "Model for the body of our backend integration's response",
|
|
763
|
+
"properties" => {
|
|
764
|
+
"content_type" => {
|
|
765
|
+
"type" => "string",
|
|
766
|
+
"description" => "An HTTP content type to match to a response, such as +application/json+."
|
|
767
|
+
},
|
|
768
|
+
"is_error" => {
|
|
769
|
+
"type" => "boolean",
|
|
770
|
+
"description" => "Whether this response should be considered an error",
|
|
771
|
+
"default" => false
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
},
|
|
779
|
+
"arn" => {
|
|
780
|
+
"type" => "string",
|
|
781
|
+
"description" => "For AWS or AWS_PROXY integrations with a compatible Amazon resource outside of Mu, a full-qualified ARN such as `arn:aws:apigateway:us-west-2:s3:action/GetObject&Bucket=`bucket&Key=key`"
|
|
782
|
+
},
|
|
783
|
+
"name" => {
|
|
784
|
+
"type" => "string",
|
|
785
|
+
"description" => "A Mu resource name, for integrations with a sibling resource (e.g. a Function)"
|
|
786
|
+
},
|
|
787
|
+
"cors" => {
|
|
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.",
|
|
790
|
+
},
|
|
791
|
+
"type" => {
|
|
792
|
+
"type" => "string",
|
|
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.",
|
|
794
|
+
"enum" => ["aws_generic"].concat(MU::Cloud.resource_types.values.map { |t| t[:cfg_plural] }.sort)
|
|
795
|
+
},
|
|
796
|
+
"aws_generic_action" => {
|
|
797
|
+
"type" => "string",
|
|
798
|
+
"description" => "For use when +type+ is set to +aws_generic+, this should specify the action to be performed in the style of an IAM policy action, e.g. +acm:ListCertificates+ for this integration to return a list of Certificate Manager SSL certificates."
|
|
799
|
+
},
|
|
800
|
+
"deploy_id" => {
|
|
801
|
+
"type" => "string",
|
|
802
|
+
"description" => "A Mu deploy id (e.g. DEMO-DEV-2014111400-NG), for integrations with a sibling resource (e.g. a Function)"
|
|
803
|
+
},
|
|
804
|
+
"iam_role" => {
|
|
805
|
+
"type" => "string",
|
|
806
|
+
"description" => "The name of an IAM role used to grant usage of other AWS artifacts for this integration. If not specified, we will automatically generate an appropriate role."
|
|
807
|
+
},
|
|
808
|
+
"passthrough_behavior" => {
|
|
809
|
+
"type" => "string",
|
|
810
|
+
"description" => "Specifies the pass-through behavior for incoming requests based on the +Content-Type+ header in the request, and the available mapping templates specified in +request_templates+. +WHEN_NO_MATCH+ passes the request body for unmapped content types through to the integration back end without transformation. +WHEN_NO_TEMPLATES+ allows pass-through when the integration has NO content types mapped to templates. +NEVER+ rejects unmapped content types with an HTTP +415+.",
|
|
811
|
+
"enum" => ["WHEN_NO_MATCH", "WHEN_NO_TEMPLATES", "NEVER"],
|
|
812
|
+
"default" => "WHEN_NO_MATCH"
|
|
813
|
+
},
|
|
814
|
+
"request_templates" => {
|
|
815
|
+
"type" => "array",
|
|
816
|
+
"description" => "A JSON-encoded string which represents a map of Velocity templates that are applied on the request payload based on the value of the +Content-Type+ header sent by the client. The content type value is the key in this map, and the template (as a String) is the value.",
|
|
817
|
+
"items" => {
|
|
818
|
+
"type" => "object",
|
|
819
|
+
"description" => "A JSON-encoded string which represents a map of Velocity templates that are applied on the request payload based on the value of the +Content-Type+ header sent by the client. The content type value is the key in this map, and the template (as a String) is the value.",
|
|
820
|
+
"require" => ["content_type", "template"],
|
|
821
|
+
"properties" => {
|
|
822
|
+
"content_type" => {
|
|
823
|
+
"type" => "string",
|
|
824
|
+
"description" => "An HTTP content type to match with a template, such as +application/json+."
|
|
825
|
+
},
|
|
826
|
+
"template" => {
|
|
827
|
+
"type" => "string",
|
|
828
|
+
"description" => "A Velocity template to apply to our reques payload, encoded as a one-line string, like: "+'<tt>"#set($allParams = $input.params())\\n{\\n\\"url_data_json_encoded\\":\\"$input.params(\'url\')\\"\\n}"</tt>'
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
},
|
|
835
|
+
"auth" => {
|
|
836
|
+
"type" => "string",
|
|
837
|
+
"enum" => ["NONE", "CUSTOM", "AWS_IAM", "COGNITO_USER_POOLS"],
|
|
838
|
+
"default" => "NONE"
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
[toplevel_required, schema]
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
# Does this resource type exist as a global (cloud-wide) artifact, or
|
|
848
|
+
# is it localized to a region/zone?
|
|
849
|
+
# @return [Boolean]
|
|
850
|
+
def self.isGlobal?
|
|
851
|
+
false
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
# Denote whether this resource implementation is experiment, ready for
|
|
855
|
+
# testing, or ready for production use.
|
|
856
|
+
def self.quality
|
|
857
|
+
MU::Cloud::BETA
|
|
858
|
+
end
|
|
859
|
+
|
|
860
|
+
# Canonical Amazon Resource Number for this resource
|
|
861
|
+
# @return [String]
|
|
862
|
+
def arn
|
|
863
|
+
"arn:#{MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws"}:execute-api:#{@config["region"]}:#{MU::Cloud::AWS.credToAcct(@credentials)}:#{@cloud_id}"
|
|
864
|
+
end
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
# Cloud-specific pre-processing of {MU::Config::BasketofKittens::endpoints}, bare and unvalidated.
|
|
868
|
+
# @param endpoint [Hash]: The resource to process and validate
|
|
869
|
+
# @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
|
|
870
|
+
# @return [Boolean]: True if validation succeeded, False otherwise
|
|
871
|
+
def self.validateConfig(endpoint, configurator)
|
|
872
|
+
ok = true
|
|
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
|
+
|
|
938
|
+
append = []
|
|
939
|
+
endpoint['deploy_to'] ||= MU.environment || $environment || "dev"
|
|
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
|
+
|
|
957
|
+
if m['integrate_with'] and m['integrate_with']['name']
|
|
958
|
+
if m['integrate_with']['type'] != "aws_generic"
|
|
959
|
+
MU::Config.addDependency(endpoint, m['integrate_with']['name'], m['integrate_with']['type'])
|
|
960
|
+
end
|
|
961
|
+
|
|
962
|
+
m['integrate_with']['backend_http_method'] ||= m['type']
|
|
963
|
+
|
|
964
|
+
m['responses'] ||= [
|
|
965
|
+
"code" => 200
|
|
966
|
+
]
|
|
967
|
+
|
|
968
|
+
if m['cors']
|
|
969
|
+
m['responses'].each { |r|
|
|
970
|
+
r['headers'] ||= []
|
|
971
|
+
r['headers'] << {
|
|
972
|
+
"header" => "Access-Control-Allow-Origin",
|
|
973
|
+
"value" => m['cors'],
|
|
974
|
+
"required" => true
|
|
975
|
+
}
|
|
976
|
+
r['headers'].uniq!
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
append << cors_option_integrations(m['path'], m['cors'])
|
|
980
|
+
end
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
if !m['iam_role']
|
|
984
|
+
m['uri'] ||= "*" if m['integrate_with']['type'] == "aws_generic"
|
|
985
|
+
|
|
986
|
+
roledesc = {
|
|
987
|
+
"name" => endpoint['name']+"-"+m['integrate_with']['name'],
|
|
988
|
+
"credentials" => endpoint['credentials'],
|
|
989
|
+
"can_assume" => [
|
|
990
|
+
{
|
|
991
|
+
"entity_id" => "apigateway.amazonaws.com",
|
|
992
|
+
"entity_type" => "service"
|
|
993
|
+
}
|
|
994
|
+
],
|
|
995
|
+
}
|
|
996
|
+
if m['integrate_with']['type'] == "aws_generic"
|
|
997
|
+
roledesc["policies"] = [
|
|
998
|
+
{
|
|
999
|
+
"name" => m['integrate_with']['aws_generic_action'].gsub(/[^a-z]/i, ""),
|
|
1000
|
+
"permissions" => [m['integrate_with']['aws_generic_action']],
|
|
1001
|
+
"targets" => [{ "identifier" => m['uri'] }]
|
|
1002
|
+
}
|
|
1003
|
+
]
|
|
1004
|
+
elsif m['integrate_with']['type'] == "functions"
|
|
1005
|
+
roledesc["import"] = ["AWSLambdaBasicExecutionRole"]
|
|
1006
|
+
end
|
|
1007
|
+
configurator.insertKitten(roledesc, "roles")
|
|
1008
|
+
|
|
1009
|
+
m['iam_role'] = endpoint['name']+"-"+m['integrate_with']['name']
|
|
1010
|
+
MU::Config.addDependency(endpoint, m['iam_role'], "role")
|
|
1011
|
+
end
|
|
1012
|
+
end
|
|
1013
|
+
}
|
|
1014
|
+
endpoint['methods'].concat(append.uniq) if endpoint['methods']
|
|
1015
|
+
# if something_bad
|
|
1016
|
+
# ok = false
|
|
1017
|
+
# end
|
|
1018
|
+
|
|
1019
|
+
ok
|
|
1020
|
+
end
|
|
1021
|
+
|
|
1022
|
+
def self.cors_option_integrations(path, origins)
|
|
1023
|
+
{
|
|
1024
|
+
"type" => "OPTIONS",
|
|
1025
|
+
"path" => path,
|
|
1026
|
+
"auth" => "NONE",
|
|
1027
|
+
"responses" => [
|
|
1028
|
+
{
|
|
1029
|
+
"code" => 200,
|
|
1030
|
+
"headers" => [
|
|
1031
|
+
{
|
|
1032
|
+
"header" => "Access-Control-Allow-Headers",
|
|
1033
|
+
"value" => "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
|
|
1034
|
+
"required" => true
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
"header" => "Access-Control-Allow-Methods",
|
|
1038
|
+
"value" => "GET,OPTIONS",
|
|
1039
|
+
"required" => true
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
"header" => "Access-Control-Allow-Origin",
|
|
1043
|
+
"value" => origins,
|
|
1044
|
+
"required" => true
|
|
1045
|
+
}
|
|
1046
|
+
],
|
|
1047
|
+
"body" => [
|
|
1048
|
+
{
|
|
1049
|
+
"content_type" => "application/json"
|
|
1050
|
+
}
|
|
1051
|
+
]
|
|
1052
|
+
}
|
|
1053
|
+
],
|
|
1054
|
+
"integrate_with" => {
|
|
1055
|
+
"type" => "mock",
|
|
1056
|
+
"passthrough_behavior" => "WHEN_NO_MATCH",
|
|
1057
|
+
"backend_http_method" => "OPTIONS",
|
|
1058
|
+
"request_templates" => [
|
|
1059
|
+
{
|
|
1060
|
+
"content_type" => "application/json",
|
|
1061
|
+
"template" => '{"statusCode": 200}'
|
|
1062
|
+
}
|
|
1063
|
+
]
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
end
|
|
1067
|
+
private_class_method :cors_option_integrations
|
|
1068
|
+
|
|
1069
|
+
end
|
|
1070
|
+
end
|
|
1071
|
+
end
|
|
1072
|
+
end
|