cloud-mu 3.1.3 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +15 -3
  3. data/ansible/roles/mu-windows/README.md +33 -0
  4. data/ansible/roles/mu-windows/defaults/main.yml +2 -0
  5. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  6. data/ansible/roles/mu-windows/files/config.xml +76 -0
  7. data/ansible/roles/mu-windows/handlers/main.yml +2 -0
  8. data/ansible/roles/mu-windows/meta/main.yml +53 -0
  9. data/ansible/roles/mu-windows/tasks/main.yml +36 -0
  10. data/ansible/roles/mu-windows/tests/inventory +2 -0
  11. data/ansible/roles/mu-windows/tests/test.yml +5 -0
  12. data/ansible/roles/mu-windows/vars/main.yml +2 -0
  13. data/bin/mu-adopt +21 -13
  14. data/bin/mu-azure-tests +57 -0
  15. data/bin/mu-cleanup +2 -4
  16. data/bin/mu-configure +52 -0
  17. data/bin/mu-deploy +3 -3
  18. data/bin/mu-findstray-tests +25 -0
  19. data/bin/mu-gen-docs +2 -4
  20. data/bin/mu-load-config.rb +4 -4
  21. data/bin/mu-node-manage +15 -16
  22. data/bin/mu-run-tests +147 -37
  23. data/cloud-mu.gemspec +22 -20
  24. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  25. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  26. data/cookbooks/mu-tools/libraries/helper.rb +3 -2
  27. data/cookbooks/mu-tools/libraries/monkey.rb +35 -0
  28. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  29. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  30. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  31. data/cookbooks/mu-tools/recipes/google_api.rb +2 -2
  32. data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
  33. data/cookbooks/mu-tools/recipes/windows-client.rb +163 -164
  34. data/cookbooks/mu-tools/resources/disk.rb +1 -1
  35. data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
  36. data/extras/clean-stock-amis +25 -19
  37. data/extras/generate-stock-images +1 -0
  38. data/extras/image-generators/AWS/win2k12.yaml +18 -13
  39. data/extras/image-generators/AWS/win2k16.yaml +18 -13
  40. data/extras/image-generators/AWS/win2k19.yaml +21 -0
  41. data/extras/image-generators/Google/centos6.yaml +1 -0
  42. data/extras/image-generators/Google/centos7.yaml +1 -1
  43. data/modules/mommacat.ru +6 -16
  44. data/modules/mu.rb +158 -111
  45. data/modules/mu/adoption.rb +404 -71
  46. data/modules/mu/cleanup.rb +221 -306
  47. data/modules/mu/cloud.rb +129 -1633
  48. data/modules/mu/cloud/database.rb +49 -0
  49. data/modules/mu/cloud/dnszone.rb +44 -0
  50. data/modules/mu/cloud/machine_images.rb +212 -0
  51. data/modules/mu/cloud/providers.rb +81 -0
  52. data/modules/mu/cloud/resource_base.rb +926 -0
  53. data/modules/mu/cloud/server.rb +40 -0
  54. data/modules/mu/cloud/server_pool.rb +1 -0
  55. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  56. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  57. data/modules/mu/cloud/wrappers.rb +169 -0
  58. data/modules/mu/config.rb +171 -1767
  59. data/modules/mu/config/alarm.rb +2 -6
  60. data/modules/mu/config/bucket.rb +32 -3
  61. data/modules/mu/config/cache_cluster.rb +2 -2
  62. data/modules/mu/config/cdn.rb +100 -0
  63. data/modules/mu/config/collection.rb +4 -4
  64. data/modules/mu/config/container_cluster.rb +9 -4
  65. data/modules/mu/config/database.rb +84 -105
  66. data/modules/mu/config/database.yml +1 -2
  67. data/modules/mu/config/dnszone.rb +10 -9
  68. data/modules/mu/config/doc_helpers.rb +516 -0
  69. data/modules/mu/config/endpoint.rb +5 -4
  70. data/modules/mu/config/firewall_rule.rb +103 -4
  71. data/modules/mu/config/folder.rb +4 -4
  72. data/modules/mu/config/function.rb +19 -10
  73. data/modules/mu/config/group.rb +4 -4
  74. data/modules/mu/config/habitat.rb +4 -4
  75. data/modules/mu/config/job.rb +89 -0
  76. data/modules/mu/config/loadbalancer.rb +60 -14
  77. data/modules/mu/config/log.rb +4 -4
  78. data/modules/mu/config/msg_queue.rb +4 -4
  79. data/modules/mu/config/nosqldb.rb +4 -4
  80. data/modules/mu/config/notifier.rb +10 -21
  81. data/modules/mu/config/ref.rb +411 -0
  82. data/modules/mu/config/role.rb +4 -4
  83. data/modules/mu/config/schema_helpers.rb +509 -0
  84. data/modules/mu/config/search_domain.rb +4 -4
  85. data/modules/mu/config/server.rb +98 -71
  86. data/modules/mu/config/server.yml +1 -0
  87. data/modules/mu/config/server_pool.rb +5 -9
  88. data/modules/mu/config/storage_pool.rb +1 -1
  89. data/modules/mu/config/tail.rb +200 -0
  90. data/modules/mu/config/user.rb +4 -4
  91. data/modules/mu/config/vpc.rb +71 -27
  92. data/modules/mu/config/vpc.yml +0 -1
  93. data/modules/mu/defaults/AWS.yaml +91 -68
  94. data/modules/mu/defaults/Azure.yaml +1 -0
  95. data/modules/mu/defaults/Google.yaml +3 -2
  96. data/modules/mu/deploy.rb +43 -26
  97. data/modules/mu/groomer.rb +17 -2
  98. data/modules/mu/groomers/ansible.rb +188 -41
  99. data/modules/mu/groomers/chef.rb +116 -55
  100. data/modules/mu/logger.rb +127 -148
  101. data/modules/mu/master.rb +410 -2
  102. data/modules/mu/master/chef.rb +3 -4
  103. data/modules/mu/master/ldap.rb +3 -3
  104. data/modules/mu/master/ssl.rb +12 -3
  105. data/modules/mu/mommacat.rb +218 -2612
  106. data/modules/mu/mommacat/daemon.rb +403 -0
  107. data/modules/mu/mommacat/naming.rb +473 -0
  108. data/modules/mu/mommacat/search.rb +495 -0
  109. data/modules/mu/mommacat/storage.rb +722 -0
  110. data/modules/mu/{clouds → providers}/README.md +1 -1
  111. data/modules/mu/{clouds → providers}/aws.rb +380 -122
  112. data/modules/mu/{clouds → providers}/aws/alarm.rb +7 -5
  113. data/modules/mu/{clouds → providers}/aws/bucket.rb +297 -59
  114. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +37 -71
  115. data/modules/mu/providers/aws/cdn.rb +782 -0
  116. data/modules/mu/{clouds → providers}/aws/collection.rb +26 -25
  117. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +724 -744
  118. data/modules/mu/providers/aws/database.rb +1744 -0
  119. data/modules/mu/{clouds → providers}/aws/dnszone.rb +88 -70
  120. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  121. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +220 -247
  122. data/modules/mu/{clouds → providers}/aws/folder.rb +8 -8
  123. data/modules/mu/{clouds → providers}/aws/function.rb +300 -142
  124. data/modules/mu/{clouds → providers}/aws/group.rb +31 -29
  125. data/modules/mu/{clouds → providers}/aws/habitat.rb +18 -15
  126. data/modules/mu/providers/aws/job.rb +466 -0
  127. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +66 -56
  128. data/modules/mu/{clouds → providers}/aws/log.rb +17 -14
  129. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +29 -19
  130. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +114 -16
  131. data/modules/mu/{clouds → providers}/aws/notifier.rb +142 -65
  132. data/modules/mu/{clouds → providers}/aws/role.rb +158 -118
  133. data/modules/mu/{clouds → providers}/aws/search_domain.rb +201 -59
  134. data/modules/mu/{clouds → providers}/aws/server.rb +844 -1139
  135. data/modules/mu/{clouds → providers}/aws/server_pool.rb +74 -65
  136. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +26 -44
  137. data/modules/mu/{clouds → providers}/aws/user.rb +24 -25
  138. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  139. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  140. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
  141. data/modules/mu/{clouds → providers}/aws/vpc.rb +525 -931
  142. data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
  143. data/modules/mu/{clouds → providers}/azure.rb +29 -9
  144. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +3 -8
  145. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +18 -11
  146. data/modules/mu/{clouds → providers}/azure/habitat.rb +8 -6
  147. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +5 -5
  148. data/modules/mu/{clouds → providers}/azure/role.rb +8 -10
  149. data/modules/mu/{clouds → providers}/azure/server.rb +97 -49
  150. data/modules/mu/{clouds → providers}/azure/user.rb +6 -8
  151. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  152. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  153. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  154. data/modules/mu/{clouds → providers}/azure/vpc.rb +16 -21
  155. data/modules/mu/{clouds → providers}/cloudformation.rb +18 -7
  156. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  157. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  158. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  159. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  160. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  161. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  162. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  163. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  164. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  165. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  166. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +5 -7
  167. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  168. data/modules/mu/{clouds → providers}/google.rb +68 -30
  169. data/modules/mu/{clouds → providers}/google/bucket.rb +13 -15
  170. data/modules/mu/{clouds → providers}/google/container_cluster.rb +85 -78
  171. data/modules/mu/{clouds → providers}/google/database.rb +11 -21
  172. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +15 -14
  173. data/modules/mu/{clouds → providers}/google/folder.rb +20 -17
  174. data/modules/mu/{clouds → providers}/google/function.rb +140 -168
  175. data/modules/mu/{clouds → providers}/google/group.rb +29 -34
  176. data/modules/mu/{clouds → providers}/google/habitat.rb +21 -22
  177. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +19 -21
  178. data/modules/mu/{clouds → providers}/google/role.rb +94 -58
  179. data/modules/mu/{clouds → providers}/google/server.rb +243 -156
  180. data/modules/mu/{clouds → providers}/google/server_pool.rb +26 -45
  181. data/modules/mu/{clouds → providers}/google/user.rb +95 -31
  182. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  183. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  184. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  185. data/modules/mu/{clouds → providers}/google/vpc.rb +103 -79
  186. data/modules/tests/aws-jobs-functions.yaml +46 -0
  187. data/modules/tests/bucket.yml +4 -0
  188. data/modules/tests/centos6.yaml +15 -0
  189. data/modules/tests/centos7.yaml +15 -0
  190. data/modules/tests/centos8.yaml +12 -0
  191. data/modules/tests/ecs.yaml +23 -0
  192. data/modules/tests/eks.yaml +1 -1
  193. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  194. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  195. data/modules/tests/includes-and-params.yaml +2 -1
  196. data/modules/tests/microservice_app.yaml +288 -0
  197. data/modules/tests/rds.yaml +108 -0
  198. data/modules/tests/regrooms/aws-iam.yaml +201 -0
  199. data/modules/tests/regrooms/bucket.yml +19 -0
  200. data/modules/tests/regrooms/rds.yaml +123 -0
  201. data/modules/tests/server-with-scrub-muisms.yaml +2 -1
  202. data/modules/tests/super_complex_bok.yml +2 -2
  203. data/modules/tests/super_simple_bok.yml +3 -5
  204. data/modules/tests/win2k12.yaml +17 -5
  205. data/modules/tests/win2k16.yaml +25 -0
  206. data/modules/tests/win2k19.yaml +25 -0
  207. data/requirements.txt +1 -0
  208. data/spec/mu/clouds/azure_spec.rb +2 -2
  209. metadata +240 -154
  210. data/extras/image-generators/AWS/windows.yaml +0 -18
  211. data/modules/mu/clouds/aws/database.rb +0 -1985
  212. data/modules/mu/clouds/aws/endpoint.rb +0 -592
@@ -47,7 +47,11 @@ module MU
47
47
  }
48
48
  end
49
49
 
50
+ type_map = {}
51
+
50
52
  @config['attributes'].each { |attr|
53
+ type_map[attr['name']] = attr['type']
54
+
51
55
  params[:attribute_definitions] << {
52
56
  :attribute_name => attr['name'],
53
57
  :attribute_type => attr['type']
@@ -67,6 +71,11 @@ module MU
67
71
  }
68
72
  end
69
73
  }
74
+ # apparently the HASH key always has to be before RANGE, so sort it
75
+ # lexically by that field and call it a day
76
+ params[:key_schema].sort! { |a, b|
77
+ a[:key_type] <=> b[:key_type]
78
+ }
70
79
 
71
80
  if @config['secondary_indexes']
72
81
  @config['secondary_indexes'].each { |idx|
@@ -99,7 +108,11 @@ module MU
99
108
  }
100
109
  end
101
110
 
102
- MU.log "Creating DynamoDB table #{@mu_name}", details: params
111
+ if @tags
112
+ params[:tags] = @tags.each_key.map { |k| { :key => k, :value => @tags[k] } }
113
+ end
114
+
115
+ MU.log "Creating DynamoDB table #{@mu_name}", MU::NOTICE, details: params
103
116
 
104
117
  resp = MU::Cloud::AWS.dynamo(credentials: @config['credentials'], region: @config['region']).create_table(params)
105
118
  @cloud_id = @mu_name
@@ -109,8 +122,24 @@ module MU
109
122
  sleep 5 if resp.table.table_status == "CREATING"
110
123
  end while resp.table.table_status == "CREATING"
111
124
 
112
-
113
125
  tagTable if !@config['scrub_mu_isms']
126
+
127
+ if @config['populate'] and !@config['populate'].empty?
128
+ MU.log "Preloading #{@mu_name} with #{@config['populate'].size.to_s} items"
129
+ items_to_write = @config['populate'].dup
130
+ begin
131
+ batch = items_to_write.slice!(0, (items_to_write.length >= 25 ? 25 : items_to_write.length))
132
+ begin
133
+ MU::Cloud::AWS.dynamo(credentials: @config['credentials'], region: @config['region']).batch_write_item(
134
+ request_items: {
135
+ @cloud_id => batch.map { |i| { put_request: { item: i } } }
136
+ }
137
+ )
138
+ rescue ::Aws::DynamoDB::Errors::ValidationException => e
139
+ MU.log e.message, MU::ERR, details: item
140
+ end
141
+ end while !items_to_write.empty?
142
+ end
114
143
  end
115
144
 
116
145
  # Apply tags to this DynamoDB table
@@ -143,6 +172,7 @@ module MU
143
172
  # Called automatically by {MU::Deploy#createResources}
144
173
  def groom
145
174
  tagTable if !@config['scrub_mu_isms']
175
+ MU.log "NoSQL Table #{@config['name']}: #{@cloud_id}", MU::SUMMARY
146
176
  end
147
177
 
148
178
  # Does this resource type exist as a global (cloud-wide) artifact, or
@@ -163,7 +193,9 @@ module MU
163
193
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
164
194
  # @param region [String]: The cloud provider region
165
195
  # @return [void]
166
- def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
196
+ def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
197
+ MU.log "AWS::NoSQLDb.cleanup: need to support flags['known']", MU::DEBUG, details: flags
198
+
167
199
  resp = MU::Cloud::AWS.dynamo(credentials: credentials, region: region).list_tables
168
200
  if resp and resp.table_names
169
201
  resp.table_names.each { |table|
@@ -178,16 +210,23 @@ module MU
178
210
  begin
179
211
  tags = MU::Cloud::AWS.dynamo(credentials: credentials, region: region).list_tags_of_resource(resource_arn: desc.table_arn)
180
212
  if tags and tags.tags
213
+ deploy_match = false
214
+ master_match = false
181
215
  tags.tags.each { |tag|
182
- if tag.key == "MU-ID" and tag.value == MU.deploy_id
183
- MU.log "Deleting DynamoDB table #{desc.table_name}"
184
- if !noop
185
- MU::Cloud::AWS.dynamo(credentials: credentials, region: region).delete_table(table_name: desc.table_name)
186
- end
216
+ if tag.key == "MU-ID" and tag.value == deploy_id
217
+ deploy_match = true
218
+ elsif tag.key == "MU-MASTER-IP" and tag.value == MU.mu_public_ip
219
+ master_match = true
187
220
  end
188
221
  }
222
+ if deploy_match and (master_match or ignoremaster)
223
+ MU.log "Deleting DynamoDB table #{desc.table_name}"
224
+ if !noop
225
+ MU::Cloud::AWS.dynamo(credentials: credentials, region: region).delete_table(table_name: desc.table_name)
226
+ end
227
+ end
189
228
  end
190
- rescue Aws::DynamoDB::Errors::ResourceNotFoundException => e
229
+ rescue Aws::DynamoDB::Errors::ResourceNotFoundException
191
230
  end
192
231
 
193
232
  }
@@ -205,7 +244,8 @@ module MU
205
244
  # Return the metadata for this user cofiguration
206
245
  # @return [Hash]
207
246
  def notify
208
- MU.structToHash(cloud_desc)
247
+ return nil if !@cloud_id or !cloud_desc(use_cache: false)
248
+ MU.structToHash(cloud_desc, stringify_keys: true)
209
249
  end
210
250
 
211
251
  # Locate an existing DynamoDB table
@@ -235,14 +275,74 @@ module MU
235
275
  found
236
276
  end
237
277
 
278
+ # Reverse-map our cloud description into a runnable config hash.
279
+ # We assume that any values we have in +@config+ are placeholders, and
280
+ # calculate our own accordingly based on what's live in the cloud.
281
+ def toKitten(**_args)
282
+ bok = {
283
+ "cloud" => "AWS",
284
+ "credentials" => @config['credentials'],
285
+ "cloud_id" => @cloud_id,
286
+ "region" => @config['region']
287
+ }
288
+
289
+ if !cloud_desc
290
+ MU.log "toKitten failed to load a cloud_desc from #{@cloud_id}", MU::ERR, details: @config
291
+ return nil
292
+ end
293
+ bok['name'] = cloud_desc.table_name
294
+ bok['read_capacity'] = cloud_desc.provisioned_throughput.read_capacity_units
295
+ bok['write_capacity'] = cloud_desc.provisioned_throughput.write_capacity_units
296
+
297
+
298
+ cloud_desc.attribute_definitions.each { |attr|
299
+ bok['attributes'] ||= []
300
+ newattr = {
301
+ "name" => attr.attribute_name,
302
+ "type" => attr.attribute_type
303
+ }
304
+ if cloud_desc.key_schema
305
+ cloud_desc.key_schema.each { |key|
306
+ next if key.attribute_name == attr.attribute_name
307
+ if key.key_type == "RANGE"
308
+ newattr["primary_partition"] = true
309
+ elsif key.key_type == "HASH"
310
+ newattr["primary_sort"] = true
311
+ end
312
+ }
313
+ end
314
+ bok['attributes'] << newattr
315
+ }
316
+
317
+ if cloud_desc.stream_specification and cloud_desc.stream_specification.stream_enabled
318
+
319
+ bok['stream'] = cloud_desc.stream_specification.stream_view_type
320
+ # cloud_desc.latest_stream_arn
321
+ # MU::Cloud::AWS.dynamostream(credentials: @credentials, region: @config['region']).list_streams
322
+ end
323
+
324
+ bok["populate"] = MU::Cloud::AWS.dynamo(credentials: @credentials, region: @config['region']).scan(
325
+ table_name: @cloud_id
326
+ ).items
327
+
328
+ bok
329
+ end
330
+
238
331
  # Cloud-specific configuration properties.
239
- # @param config [MU::Config]: The calling MU::Config object
332
+ # @param _config [MU::Config]: The calling MU::Config object
240
333
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
241
- def self.schema(config)
334
+ def self.schema(_config)
242
335
  toplevel_required = ["attributes"]
243
336
 
244
337
 
245
338
  schema = {
339
+ "populate" => {
340
+ "type" => "array",
341
+ "items" => {
342
+ "type" => "object",
343
+ "description" => "Key-value pairs, compatible with the +attributes+ schema, with which to populate this +table+ during its initial creation."
344
+ }
345
+ },
246
346
  "attributes" => {
247
347
  "type" => "array",
248
348
  "minItems" => 1,
@@ -360,9 +460,9 @@ module MU
360
460
  # Cloud-specific pre-processing of {MU::Config::BasketofKittens::nosqldbs}, bare and unvalidated.
361
461
 
362
462
  # @param db [Hash]: The resource to process and validate
363
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
463
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
364
464
  # @return [Boolean]: True if validation succeeded, False otherwise
365
- def self.validateConfig(db, configurator)
465
+ def self.validateConfig(db, _configurator)
366
466
  ok = true
367
467
 
368
468
  partition = nil
@@ -399,8 +499,6 @@ module MU
399
499
  ok
400
500
  end
401
501
 
402
- private
403
-
404
502
  end
405
503
  end
406
504
  end
@@ -27,8 +27,8 @@ module MU
27
27
 
28
28
  # Called automatically by {MU::Deploy#createResources}
29
29
  def create
30
- MU::Cloud::AWS.sns(region: @config['region'], credentials: @config['credentials']).create_topic(name: @mu_name)
31
30
  @cloud_id = @mu_name
31
+ MU::Cloud::AWS.sns(region: @config['region'], credentials: @config['credentials']).create_topic(name: @cloud_id)
32
32
  MU.log "Created SNS topic #{@mu_name}"
33
33
  end
34
34
 
@@ -36,17 +36,48 @@ module MU
36
36
  def groom
37
37
  if @config['subscriptions']
38
38
  @config['subscriptions'].each { |sub|
39
- MU::Cloud::AWS::Notifier.subscribe(
40
- arn: arn,
41
- endpoint: sub['endpoint'],
42
- region: @config['region'],
43
- credentials: @config['credentials'],
44
- protocol: sub['type']
45
- )
39
+ if sub['resource'] and !sub['endpoint']
40
+ endpoint_obj = nil
41
+ MU.retrier([], max: 5, wait: 9, loop_if: Proc.new { endpoint_obj.nil? }) {
42
+ endpoint_obj = MU::Config::Ref.get(sub['resource']).kitten(@deploy)
43
+ }
44
+ sub['endpoint'] = endpoint_obj.arn
45
+ end
46
+ subscribe(sub['endpoint'], sub['type'])
46
47
  }
47
48
  end
48
49
  end
49
50
 
51
+ # Subscribe something to this SNS topic
52
+ # @param endpoint [String]: The address, identifier, or ARN of the resource being subscribed
53
+ # @param protocol [String]: The protocol being subscribed
54
+ def subscribe(endpoint, protocol)
55
+ self.class.subscribe(arn, endpoint, protocol, region: @config['region'], credentials: @credentials)
56
+ end
57
+
58
+ # Subscribe something to an SNS topic
59
+ # @param cloud_id [String]: The short name or ARN of an existing SNS topic
60
+ # @param endpoint [String]: The address, identifier, or ARN of the resource being subscribed
61
+ # @param protocol [String]: The protocol being subscribed
62
+ # @param region [String]: The region of the target SNS topic
63
+ # @param credentials [String]:
64
+ def self.subscribe(cloud_id, endpoint, protocol, region: nil, credentials: nil)
65
+ topic = find(cloud_id: cloud_id, region: region, credentials: credentials).values.first
66
+ if !topic
67
+ raise MuError, "Failed to find SNS Topic #{cloud_id} in #{region}"
68
+ end
69
+ arn = topic["TopicArn"]
70
+
71
+ resp = MU::Cloud::AWS.sns(region: region, credentials: credentials).list_subscriptions_by_topic(topic_arn: arn).subscriptions
72
+
73
+ resp.each { |subscription|
74
+ return subscription if subscription.protocol == protocol and subscription.endpoint == endpoint
75
+ }
76
+
77
+ MU.log "Subscribing #{endpoint} (#{protocol}) to SNS topic #{arn}", MU::NOTICE
78
+ MU::Cloud::AWS.sns(region: region, credentials: credentials).subscribe(topic_arn: arn, protocol: protocol, endpoint: endpoint)
79
+ end
80
+
50
81
  # Does this resource type exist as a global (cloud-wide) artifact, or
51
82
  # is it localized to a region/zone?
52
83
  # @return [Boolean]
@@ -65,13 +96,18 @@ module MU
65
96
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
66
97
  # @param region [String]: The cloud provider region
67
98
  # @return [void]
68
- def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
99
+ def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
100
+ MU.log "AWS::Notifier.cleanup: need to support flags['known']", MU::DEBUG, details: flags
101
+ MU.log "Placeholder: AWS Notifier artifacts do not support tags, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: ignoremaster
102
+
69
103
  MU::Cloud::AWS.sns(region: region, credentials: credentials).list_topics.topics.each { |topic|
70
- if topic.topic_arn.match(MU.deploy_id)
104
+ if topic.topic_arn.match(deploy_id)
71
105
  # We don't have a way to tag our SNS topics, so we will delete any topic that has the MU-ID in its ARN.
72
106
  # This may fail to find notifier groups in some cases (eg. cache_cluster) so we might want to delete from each API as well.
73
- MU::Cloud::AWS.sns(region: region, credentials: credentials).delete_topic(topic_arn: topic.topic_arn)
74
- MU.log "Deleted SNS topic: #{topic.topic_arn}"
107
+ MU.log "Deleting SNS topic: #{topic.topic_arn}"
108
+ if !noop
109
+ MU::Cloud::AWS.sns(region: region, credentials: credentials).delete_topic(topic_arn: topic.topic_arn)
110
+ end
75
111
  end
76
112
  }
77
113
  end
@@ -86,6 +122,7 @@ module MU
86
122
  # Return the metadata for this user cofiguration
87
123
  # @return [Hash]
88
124
  def notify
125
+ return nil if !@cloud_id or !cloud_desc(use_cache: false)
89
126
  desc = MU::Cloud::AWS.sns(region: @config["region"], credentials: @config["credentials"]).get_topic_attributes(topic_arn: arn).attributes
90
127
  MU.structToHash(desc)
91
128
  end
@@ -96,9 +133,16 @@ module MU
96
133
  found = {}
97
134
 
98
135
  if args[:cloud_id]
99
- arn = "arn:"+(MU::Cloud::AWS.isGovCloud?(args[:region]) ? "aws-us-gov" : "aws")+":sns:"+args[:region]+":"+MU::Cloud::AWS.credToAcct(args[:credentials])+":"+args[:cloud_id]
100
- desc = MU::Cloud::AWS.sns(region: args[:region], credentials: args[:credentials]).get_topic_attributes(topic_arn: arn).attributes
101
- found[args[:cloud_id]] = desc if desc
136
+ arn = if args[:cloud_id].match(/^arn:/)
137
+ args[:cloud_id]
138
+ else
139
+ "arn:"+(MU::Cloud::AWS.isGovCloud?(args[:region]) ? "aws-us-gov" : "aws")+":sns:"+args[:region]+":"+MU::Cloud::AWS.credToAcct(args[:credentials])+":"+args[:cloud_id]
140
+ end
141
+ begin
142
+ desc = MU::Cloud::AWS.sns(region: args[:region], credentials: args[:credentials]).get_topic_attributes(topic_arn: arn).attributes
143
+ found[args[:cloud_id]] = desc if desc
144
+ rescue ::Aws::SNS::Errors::NotFound
145
+ end
102
146
  else
103
147
  next_token = nil
104
148
  begin
@@ -115,21 +159,72 @@ module MU
115
159
  found
116
160
  end
117
161
 
162
+ # Reverse-map our cloud description into a runnable config hash.
163
+ # We assume that any values we have in +@config+ are placeholders, and
164
+ # calculate our own accordingly based on what's live in the cloud.
165
+ def toKitten(**_args)
166
+ bok = {
167
+ "cloud" => "AWS",
168
+ "credentials" => @config['credentials'],
169
+ "cloud_id" => @cloud_id,
170
+ "region" => @config['region']
171
+ }
172
+
173
+ if !cloud_desc
174
+ MU.log "toKitten failed to load a cloud_desc from #{@cloud_id}", MU::ERR, details: @config
175
+ return nil
176
+ end
177
+
178
+ bok['name'] = cloud_desc["DisplayName"].empty? ? @cloud_id : cloud_desc["DisplayName"]
179
+ svcmap = {
180
+ "lambda" => "functions",
181
+ "sqs" => "msg_queues"
182
+ }
183
+ MU::Cloud::AWS.sns(region: @config['region'], credentials: @credentials).list_subscriptions_by_topic(topic_arn: cloud_desc["TopicArn"]).subscriptions.each { |sub|
184
+ bok['subscriptions'] ||= []
185
+
186
+ bok['subscriptions'] << if sub.endpoint.match(/^arn:[^:]+:(sqs|lambda):([^:]+):(\d+):.*?([^:\/]+)$/)
187
+ _wholestring, service, region, account, id = Regexp.last_match.to_a
188
+ {
189
+ "type" => sub.protocol,
190
+ "resource" => MU::Config::Ref.get(
191
+ type: svcmap[service],
192
+ region: region,
193
+ credentials: @credentials,
194
+ id: id,
195
+ cloud: "AWS",
196
+ habitat: MU::Config::Ref.get(
197
+ id: account,
198
+ cloud: "AWS",
199
+ credentials: @credentials
200
+ )
201
+ )
202
+ }
203
+ else
204
+ {
205
+ "type" => sub.protocol,
206
+ "endpoint" => sub.endpoint
207
+ }
208
+ end
209
+ }
210
+
211
+ bok
212
+ end
213
+
118
214
  # Cloud-specific configuration properties.
119
- # @param config [MU::Config]: The calling MU::Config object
215
+ # @param _config [MU::Config]: The calling MU::Config object
120
216
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
121
- def self.schema(config)
217
+ def self.schema(_config)
122
218
  toplevel_required = []
123
219
  schema = {
124
220
  "subscriptions" => {
125
221
  "type" => "array",
126
222
  "items" => {
127
223
  "type" => "object",
128
- "required" => ["endpoint"],
129
224
  "properties" => {
130
225
  "type" => {
131
226
  "type" => "string",
132
- "description" => "",
227
+ "description" => "Type of endpoint or resource which should receive notifications. If not specified, will attempt to auto-detect.",
133
228
  "enum" => ["http", "https", "email", "email-json", "sms", "sqs", "application", "lambda"]
134
229
  }
135
230
  }
@@ -150,19 +245,35 @@ module MU
150
245
 
151
246
  if notifier['subscriptions']
152
247
  notifier['subscriptions'].each { |sub|
248
+ if sub['resource'] and configurator.haveLitterMate?(sub['resource']['name'], sub['resource']['type'])
249
+ sub['resource']['cloud'] = "AWS"
250
+ MU::Config.addDependency(notifier, sub['resource']['name'], sub['resource']['type'])
251
+ end
153
252
  if !sub["type"]
154
- if sub["endpoint"].match(/^http:/i)
155
- sub["type"] = "http"
156
- elsif sub["endpoint"].match(/^https:/i)
157
- sub["type"] = "https"
158
- elsif sub["endpoint"].match(/^sqs:/i)
159
- sub["type"] = "sqs"
160
- elsif sub["endpoint"].match(/^\+?[\d\-]+$/)
161
- sub["type"] = "sms"
162
- elsif sub["endpoint"].match(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i)
163
- sub["type"] = "email"
164
- else
165
- MU.log "Notifier #{notifier['name']} subscription #{sub['endpoint']} did not specify a type, and I'm unable to guess one", MU::ERR
253
+ sub['type'] = if sub['resource']
254
+ if sub['resource']['type'] == "functions"
255
+ "lambda"
256
+ elsif sub['resource']['type'] == "msg_queues"
257
+ "sqs"
258
+ end
259
+ elsif sub['endpoint']
260
+ if sub["endpoint"].match(/^http:/i)
261
+ "http"
262
+ elsif sub["endpoint"].match(/^https:/i)
263
+ "https"
264
+ elsif sub["endpoint"].match(/:sqs:/i)
265
+ "sqs"
266
+ elsif sub["endpoint"].match(/:lambda:/i)
267
+ "lambda"
268
+ elsif sub["endpoint"].match(/^\+?[\d\-]+$/)
269
+ "sms"
270
+ elsif sub["endpoint"].match(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i)
271
+ "email"
272
+ end
273
+ end
274
+
275
+ if !sub['type']
276
+ MU.log "Notifier #{notifier['name']} subscription did not specify a type, and I'm unable to guess one", MU::ERR, details: sub
166
277
  ok = false
167
278
  end
168
279
  end
@@ -173,40 +284,6 @@ module MU
173
284
  end
174
285
 
175
286
 
176
- # Subscribe to a notifier group. This can either be an email address, SQS queue, application endpoint, etc...
177
- # Will create the subscription only if it doesn't already exist.
178
- # @param arn [String]: The cloud provider's identifier of the notifier group.
179
- # @param protocol [String]: The type of the subscription (eg. email,https, etc..).
180
- # @param endpoint [String]: The endpoint of the subscription. This will depend on the 'protocol' (as an example if protocol is email, endpoint will be the email address) ..
181
- # @param region [String]: The cloud provider region.
182
- def self.subscribe(arn: nil, protocol: nil, endpoint: nil, region: MU.curRegion, credentials: nil)
183
- retries = 0
184
- begin
185
- resp = MU::Cloud::AWS.sns(region: region, credentials: credentials).list_subscriptions_by_topic(topic_arn: arn).subscriptions
186
- rescue Aws::SNS::Errors::NotFound
187
- if retries < 5
188
- MU.log "Couldn't find topic #{arn}, retrying several times in case of a lagging resource"
189
- retries += 1
190
- sleep 30
191
- retry
192
- else
193
- raise MuError, "Couldn't find topic #{arn}, giving up"
194
- end
195
- end
196
-
197
- already_subscribed = false
198
- if resp && !resp.empty?
199
- resp.each { |subscription|
200
- already_subscribed = true if subscription.protocol == protocol && subscription.endpoint == endpoint
201
- }
202
- end
203
-
204
- unless already_subscribed
205
- MU::Cloud::AWS.sns(region: region, credentials: credentials).subscribe(topic_arn: arn, protocol: protocol, endpoint: endpoint)
206
- MU.log "Subscribed #{endpoint} to SNS topic #{arn}"
207
- end
208
- end
209
-
210
287
  # Test if a notifier group exists
211
288
  # Create a new notifier group. Will check if the group exists before creating it.
212
289
  # @param topic_name [String]: The cloud provider's name for the notifier group.