cloud-mu 3.2.0 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/ansible/roles/mu-nat/tasks/main.yml +3 -0
  4. data/bin/mu-adopt +12 -1
  5. data/bin/mu-aws-setup +41 -7
  6. data/bin/mu-azure-setup +34 -0
  7. data/bin/mu-configure +214 -119
  8. data/bin/mu-gcp-setup +37 -2
  9. data/bin/mu-load-config.rb +2 -1
  10. data/bin/mu-node-manage +3 -0
  11. data/bin/mu-refresh-ssl +67 -0
  12. data/bin/mu-run-tests +28 -6
  13. data/bin/mu-self-update +30 -10
  14. data/bin/mu-upload-chef-artifacts +30 -26
  15. data/cloud-mu.gemspec +10 -8
  16. data/cookbooks/mu-master/attributes/default.rb +5 -1
  17. data/cookbooks/mu-master/metadata.rb +2 -2
  18. data/cookbooks/mu-master/recipes/default.rb +81 -26
  19. data/cookbooks/mu-master/recipes/init.rb +197 -62
  20. data/cookbooks/mu-master/recipes/update_nagios_only.rb +1 -1
  21. data/cookbooks/mu-master/recipes/vault.rb +78 -77
  22. data/cookbooks/mu-master/templates/default/mods/rewrite.conf.erb +1 -0
  23. data/cookbooks/mu-master/templates/default/nagios.conf.erb +103 -0
  24. data/cookbooks/mu-master/templates/default/web_app.conf.erb +14 -30
  25. data/cookbooks/mu-tools/attributes/default.rb +12 -0
  26. data/cookbooks/mu-tools/files/centos-6/CentOS-Base.repo +47 -0
  27. data/cookbooks/mu-tools/libraries/helper.rb +98 -4
  28. data/cookbooks/mu-tools/libraries/monkey.rb +1 -1
  29. data/cookbooks/mu-tools/recipes/apply_security.rb +31 -9
  30. data/cookbooks/mu-tools/recipes/aws_api.rb +8 -2
  31. data/cookbooks/mu-tools/recipes/base_repositories.rb +1 -1
  32. data/cookbooks/mu-tools/recipes/gcloud.rb +2 -9
  33. data/cookbooks/mu-tools/recipes/google_api.rb +7 -0
  34. data/cookbooks/mu-tools/recipes/rsyslog.rb +8 -1
  35. data/cookbooks/mu-tools/resources/disk.rb +113 -42
  36. data/cookbooks/mu-tools/resources/mommacat_request.rb +1 -2
  37. data/cookbooks/mu-tools/templates/centos-8/sshd_config.erb +215 -0
  38. data/extras/Gemfile.lock.bootstrap +394 -0
  39. data/extras/bucketstubs/error.html +0 -0
  40. data/extras/bucketstubs/index.html +0 -0
  41. data/extras/clean-stock-amis +11 -3
  42. data/extras/generate-stock-images +6 -3
  43. data/extras/git_rpm/build.sh +20 -0
  44. data/extras/git_rpm/mugit.spec +53 -0
  45. data/extras/image-generators/AWS/centos7.yaml +19 -16
  46. data/extras/image-generators/AWS/{rhel7.yaml → rhel71.yaml} +0 -0
  47. data/extras/image-generators/AWS/{win2k12.yaml → win2k12r2.yaml} +0 -0
  48. data/extras/image-generators/VMWare/centos8.yaml +15 -0
  49. data/extras/openssl_rpm/build.sh +19 -0
  50. data/extras/openssl_rpm/mussl.spec +46 -0
  51. data/extras/python_rpm/muthon.spec +14 -4
  52. data/extras/ruby_rpm/muby.spec +9 -5
  53. data/extras/sqlite_rpm/build.sh +19 -0
  54. data/extras/sqlite_rpm/muqlite.spec +47 -0
  55. data/install/installer +7 -5
  56. data/modules/mommacat.ru +2 -2
  57. data/modules/mu.rb +14 -7
  58. data/modules/mu/adoption.rb +5 -5
  59. data/modules/mu/cleanup.rb +47 -25
  60. data/modules/mu/cloud.rb +29 -1
  61. data/modules/mu/cloud/dnszone.rb +0 -2
  62. data/modules/mu/cloud/machine_images.rb +1 -1
  63. data/modules/mu/cloud/providers.rb +6 -1
  64. data/modules/mu/cloud/resource_base.rb +16 -7
  65. data/modules/mu/cloud/ssh_sessions.rb +5 -1
  66. data/modules/mu/cloud/wrappers.rb +20 -7
  67. data/modules/mu/config.rb +28 -12
  68. data/modules/mu/config/bucket.rb +31 -2
  69. data/modules/mu/config/cache_cluster.rb +1 -1
  70. data/modules/mu/config/cdn.rb +100 -0
  71. data/modules/mu/config/container_cluster.rb +1 -1
  72. data/modules/mu/config/database.rb +3 -3
  73. data/modules/mu/config/dnszone.rb +4 -3
  74. data/modules/mu/config/endpoint.rb +1 -0
  75. data/modules/mu/config/firewall_rule.rb +1 -1
  76. data/modules/mu/config/function.rb +16 -7
  77. data/modules/mu/config/job.rb +89 -0
  78. data/modules/mu/config/notifier.rb +7 -18
  79. data/modules/mu/config/ref.rb +55 -9
  80. data/modules/mu/config/schema_helpers.rb +12 -3
  81. data/modules/mu/config/server.rb +11 -5
  82. data/modules/mu/config/server_pool.rb +2 -2
  83. data/modules/mu/config/vpc.rb +11 -10
  84. data/modules/mu/defaults/AWS.yaml +106 -106
  85. data/modules/mu/deploy.rb +40 -14
  86. data/modules/mu/groomers/chef.rb +2 -2
  87. data/modules/mu/master.rb +70 -3
  88. data/modules/mu/mommacat.rb +28 -9
  89. data/modules/mu/mommacat/daemon.rb +13 -7
  90. data/modules/mu/mommacat/naming.rb +2 -2
  91. data/modules/mu/mommacat/search.rb +16 -5
  92. data/modules/mu/mommacat/storage.rb +67 -32
  93. data/modules/mu/providers/aws.rb +298 -85
  94. data/modules/mu/providers/aws/alarm.rb +5 -5
  95. data/modules/mu/providers/aws/bucket.rb +284 -50
  96. data/modules/mu/providers/aws/cache_cluster.rb +26 -26
  97. data/modules/mu/providers/aws/cdn.rb +782 -0
  98. data/modules/mu/providers/aws/collection.rb +16 -16
  99. data/modules/mu/providers/aws/container_cluster.rb +84 -64
  100. data/modules/mu/providers/aws/database.rb +59 -55
  101. data/modules/mu/providers/aws/dnszone.rb +29 -12
  102. data/modules/mu/providers/aws/endpoint.rb +535 -50
  103. data/modules/mu/providers/aws/firewall_rule.rb +32 -26
  104. data/modules/mu/providers/aws/folder.rb +1 -1
  105. data/modules/mu/providers/aws/function.rb +300 -134
  106. data/modules/mu/providers/aws/group.rb +16 -14
  107. data/modules/mu/providers/aws/habitat.rb +4 -4
  108. data/modules/mu/providers/aws/job.rb +469 -0
  109. data/modules/mu/providers/aws/loadbalancer.rb +67 -45
  110. data/modules/mu/providers/aws/log.rb +17 -17
  111. data/modules/mu/providers/aws/msg_queue.rb +22 -13
  112. data/modules/mu/providers/aws/nosqldb.rb +99 -8
  113. data/modules/mu/providers/aws/notifier.rb +137 -65
  114. data/modules/mu/providers/aws/role.rb +119 -83
  115. data/modules/mu/providers/aws/search_domain.rb +166 -30
  116. data/modules/mu/providers/aws/server.rb +209 -118
  117. data/modules/mu/providers/aws/server_pool.rb +95 -130
  118. data/modules/mu/providers/aws/storage_pool.rb +19 -11
  119. data/modules/mu/providers/aws/user.rb +5 -5
  120. data/modules/mu/providers/aws/userdata/linux.erb +5 -4
  121. data/modules/mu/providers/aws/vpc.rb +109 -54
  122. data/modules/mu/providers/aws/vpc_subnet.rb +43 -39
  123. data/modules/mu/providers/azure.rb +78 -12
  124. data/modules/mu/providers/azure/server.rb +20 -4
  125. data/modules/mu/providers/cloudformation/server.rb +1 -1
  126. data/modules/mu/providers/google.rb +21 -5
  127. data/modules/mu/providers/google/bucket.rb +1 -1
  128. data/modules/mu/providers/google/container_cluster.rb +1 -1
  129. data/modules/mu/providers/google/database.rb +1 -1
  130. data/modules/mu/providers/google/firewall_rule.rb +1 -1
  131. data/modules/mu/providers/google/folder.rb +7 -3
  132. data/modules/mu/providers/google/function.rb +66 -31
  133. data/modules/mu/providers/google/group.rb +1 -1
  134. data/modules/mu/providers/google/habitat.rb +1 -1
  135. data/modules/mu/providers/google/loadbalancer.rb +1 -1
  136. data/modules/mu/providers/google/role.rb +6 -3
  137. data/modules/mu/providers/google/server.rb +1 -1
  138. data/modules/mu/providers/google/server_pool.rb +1 -1
  139. data/modules/mu/providers/google/user.rb +1 -1
  140. data/modules/mu/providers/google/vpc.rb +28 -3
  141. data/modules/tests/aws-jobs-functions.yaml +46 -0
  142. data/modules/tests/aws-servers-with-handrolled-iam.yaml +37 -0
  143. data/modules/tests/centos6.yaml +4 -0
  144. data/modules/tests/centos7.yaml +4 -0
  145. data/modules/tests/ecs.yaml +2 -2
  146. data/modules/tests/eks.yaml +1 -1
  147. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  148. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  149. data/modules/tests/k8s.yaml +1 -1
  150. data/modules/tests/microservice_app.yaml +288 -0
  151. data/modules/tests/rds.yaml +5 -5
  152. data/modules/tests/regrooms/rds.yaml +5 -5
  153. data/modules/tests/server-with-scrub-muisms.yaml +1 -1
  154. data/modules/tests/super_complex_bok.yml +2 -2
  155. data/modules/tests/super_simple_bok.yml +2 -2
  156. metadata +42 -17
@@ -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: @config['credentials']).create_rest_api(
16
+ resp = MU::Cloud::AWS.apig(region: @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: @config['credentials']).get_resources(
29
+ def generate_methods(integrations = true)
30
+ resp = MU::Cloud::AWS.apig(region: @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(@config['credentials'])}:#{@cloud_id}/*/#{m['type']}/#{m['path']}"
39
+ method_arn = "arn:#{MU::Cloud::AWS.isGovCloud?(@region) ? "aws-us-gov" : "aws"}:execute-api:#{@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: @config['credentials']).get_resources(
43
+ resp = MU::Cloud::AWS.apig(region: @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 == m['path']
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: @config['credentials']).get_resource(
54
+ MU::Cloud::AWS.apig(region: @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: @config['credentials']).update_resource(
58
+ # MU::Cloud::AWS.apig(region: @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: @config['credentials']).create_resource(
70
+ MU::Cloud::AWS.apig(region: @region, credentials: @credentials).create_resource(
70
71
  rest_api_id: @cloud_id,
71
72
  parent_id: root_resource,
72
- path_part: m['path']
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: @config['credentials']).get_method(
79
+ MU::Cloud::AWS.apig(region: @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: @config['credentials']).put_method(
85
+ resp = MU::Cloud::AWS.apig(region: @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: @config['credentials']).put_method_response(params)
114
+ MU::Cloud::AWS.apig(region: @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}", "AWS"]
134
- elsif m['integrate_with']['type'] == "function"
135
- function_obj = @deploy.findLitterMate(name: m['integrate_with']['name'], type: "functions").cloudobj
136
- ["arn:aws:apigateway:"+@config['region']+":lambda:path/2015-03-31/functions/"+function_obj.arn+"/invocations", "AWS"]
136
+ ["arn:aws:apigateway:"+@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:"+@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: @config['credentials']).put_integration(params)
179
+ resp = MU::Cloud::AWS.apig(region: @region, credentials: @credentials).put_integration(params)
168
180
 
169
- if m['integrate_with']['type'] == "function"
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: @config['credentials']).put_integration_response(params)
199
+ MU::Cloud::AWS.apig(region: @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.log "Deploying API Gateway #{@config['name']} to #{@config['deploy_to']}"
201
- MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).create_deployment(
202
- rest_api_id: @cloud_id,
203
- stage_name: @config['deploy_to']
212
+ deployment = MU::Cloud::AWS.apig(region: @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: @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
- my_url = "https://"+@cloud_id+".execute-api."+@config['region']+".amazonaws.com/"+@config['deploy_to']
228
+ my_hostname = @cloud_id+".execute-api."+@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
- # resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).create_authorizer(
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: @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: @region, credentials: @credentials).create_domain_name(params)
282
+ end
283
+
284
+ mappings = MU::Cloud::AWS.apig(region: @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: @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: @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: @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: @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: @config['credentials']).create_vpc_link(
357
+ # resp = MU::Cloud::AWS.apig(region: @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
- @cloud_desc_cache = MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).get_rest_api(
366
+ return nil if !@cloud_id
367
+ @cloud_desc_cache = MU::Cloud::AWS.apig(region: @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
- deploy_struct = MU.structToHash(cloud_desc)
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."+@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 == MU.deploy_id
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" => @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: @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: @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(/#{@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: @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: @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: @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" => "For HTTP or AWS integrations, specify whether the target is a proxy (((docs unclear, is that actually what this means?)))" # XXX is that actually what this means?
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" => "boolean",
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[:cfg_name] }.sort)
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(@config['credentials'])}:#{@cloud_id}"
863
+ "arn:#{MU::Cloud::AWS.isGovCloud?(@region) ? "aws-us-gov" : "aws"}:execute-api:#{@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'] == "function"
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
  ],