cloud-mu 3.1.6 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mu-adopt +4 -12
  3. data/bin/mu-azure-tests +57 -0
  4. data/bin/mu-cleanup +2 -4
  5. data/bin/mu-configure +37 -1
  6. data/bin/mu-deploy +3 -3
  7. data/bin/mu-findstray-tests +25 -0
  8. data/bin/mu-gen-docs +2 -4
  9. data/bin/mu-run-tests +23 -10
  10. data/cloud-mu.gemspec +2 -2
  11. data/cookbooks/mu-tools/libraries/helper.rb +1 -1
  12. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  13. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  14. data/extras/generate-stock-images +1 -0
  15. data/modules/mu.rb +82 -95
  16. data/modules/mu/adoption.rb +356 -56
  17. data/modules/mu/cleanup.rb +21 -20
  18. data/modules/mu/cloud.rb +79 -1753
  19. data/modules/mu/cloud/database.rb +49 -0
  20. data/modules/mu/cloud/dnszone.rb +46 -0
  21. data/modules/mu/cloud/machine_images.rb +212 -0
  22. data/modules/mu/cloud/providers.rb +81 -0
  23. data/modules/mu/cloud/resource_base.rb +920 -0
  24. data/modules/mu/cloud/server.rb +40 -0
  25. data/modules/mu/cloud/server_pool.rb +1 -0
  26. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  27. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  28. data/modules/mu/cloud/wrappers.rb +165 -0
  29. data/modules/mu/config.rb +122 -80
  30. data/modules/mu/config/alarm.rb +2 -6
  31. data/modules/mu/config/bucket.rb +1 -1
  32. data/modules/mu/config/cache_cluster.rb +1 -1
  33. data/modules/mu/config/collection.rb +1 -1
  34. data/modules/mu/config/container_cluster.rb +2 -2
  35. data/modules/mu/config/database.rb +83 -104
  36. data/modules/mu/config/database.yml +1 -2
  37. data/modules/mu/config/dnszone.rb +1 -1
  38. data/modules/mu/config/doc_helpers.rb +4 -5
  39. data/modules/mu/config/endpoint.rb +1 -1
  40. data/modules/mu/config/firewall_rule.rb +3 -19
  41. data/modules/mu/config/folder.rb +1 -1
  42. data/modules/mu/config/function.rb +1 -1
  43. data/modules/mu/config/group.rb +1 -1
  44. data/modules/mu/config/habitat.rb +1 -1
  45. data/modules/mu/config/loadbalancer.rb +57 -11
  46. data/modules/mu/config/log.rb +1 -1
  47. data/modules/mu/config/msg_queue.rb +1 -1
  48. data/modules/mu/config/nosqldb.rb +1 -1
  49. data/modules/mu/config/notifier.rb +1 -1
  50. data/modules/mu/config/ref.rb +30 -4
  51. data/modules/mu/config/role.rb +1 -1
  52. data/modules/mu/config/schema_helpers.rb +30 -34
  53. data/modules/mu/config/search_domain.rb +1 -1
  54. data/modules/mu/config/server.rb +4 -12
  55. data/modules/mu/config/server_pool.rb +3 -7
  56. data/modules/mu/config/storage_pool.rb +1 -1
  57. data/modules/mu/config/tail.rb +10 -0
  58. data/modules/mu/config/user.rb +1 -1
  59. data/modules/mu/config/vpc.rb +12 -17
  60. data/modules/mu/defaults/AWS.yaml +32 -32
  61. data/modules/mu/defaults/Azure.yaml +1 -0
  62. data/modules/mu/defaults/Google.yaml +1 -0
  63. data/modules/mu/deploy.rb +16 -15
  64. data/modules/mu/groomer.rb +15 -0
  65. data/modules/mu/groomers/chef.rb +3 -0
  66. data/modules/mu/logger.rb +120 -144
  67. data/modules/mu/master.rb +1 -1
  68. data/modules/mu/mommacat.rb +54 -25
  69. data/modules/mu/mommacat/daemon.rb +10 -7
  70. data/modules/mu/mommacat/naming.rb +82 -3
  71. data/modules/mu/mommacat/search.rb +47 -15
  72. data/modules/mu/mommacat/storage.rb +72 -41
  73. data/modules/mu/{clouds → providers}/README.md +1 -1
  74. data/modules/mu/{clouds → providers}/aws.rb +114 -47
  75. data/modules/mu/{clouds → providers}/aws/alarm.rb +1 -1
  76. data/modules/mu/{clouds → providers}/aws/bucket.rb +2 -2
  77. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +10 -46
  78. data/modules/mu/{clouds → providers}/aws/collection.rb +3 -3
  79. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +15 -33
  80. data/modules/mu/providers/aws/database.rb +1744 -0
  81. data/modules/mu/{clouds → providers}/aws/dnszone.rb +2 -5
  82. data/modules/mu/{clouds → providers}/aws/endpoint.rb +2 -11
  83. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +33 -29
  84. data/modules/mu/{clouds → providers}/aws/folder.rb +0 -0
  85. data/modules/mu/{clouds → providers}/aws/function.rb +2 -10
  86. data/modules/mu/{clouds → providers}/aws/group.rb +9 -13
  87. data/modules/mu/{clouds → providers}/aws/habitat.rb +1 -1
  88. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +41 -33
  89. data/modules/mu/{clouds → providers}/aws/log.rb +2 -2
  90. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +2 -8
  91. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +0 -0
  92. data/modules/mu/{clouds → providers}/aws/notifier.rb +0 -0
  93. data/modules/mu/{clouds → providers}/aws/role.rb +7 -7
  94. data/modules/mu/{clouds → providers}/aws/search_domain.rb +8 -13
  95. data/modules/mu/{clouds → providers}/aws/server.rb +55 -90
  96. data/modules/mu/{clouds → providers}/aws/server_pool.rb +10 -33
  97. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +19 -36
  98. data/modules/mu/{clouds → providers}/aws/user.rb +8 -12
  99. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  100. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +0 -0
  101. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +0 -0
  102. data/modules/mu/{clouds → providers}/aws/vpc.rb +135 -70
  103. data/modules/mu/{clouds → providers}/aws/vpc_subnet.rb +0 -0
  104. data/modules/mu/{clouds → providers}/azure.rb +4 -1
  105. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
  106. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
  107. data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
  108. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
  109. data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
  110. data/modules/mu/{clouds → providers}/azure/server.rb +30 -23
  111. data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
  112. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  113. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  114. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  115. data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
  116. data/modules/mu/{clouds → providers}/cloudformation.rb +1 -1
  117. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  118. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  119. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  120. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  121. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  122. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  123. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  124. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  125. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  126. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  127. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
  128. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  129. data/modules/mu/{clouds → providers}/google.rb +14 -6
  130. data/modules/mu/{clouds → providers}/google/bucket.rb +1 -1
  131. data/modules/mu/{clouds → providers}/google/container_cluster.rb +28 -13
  132. data/modules/mu/{clouds → providers}/google/database.rb +1 -8
  133. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +2 -2
  134. data/modules/mu/{clouds → providers}/google/folder.rb +4 -8
  135. data/modules/mu/{clouds → providers}/google/function.rb +3 -3
  136. data/modules/mu/{clouds → providers}/google/group.rb +8 -16
  137. data/modules/mu/{clouds → providers}/google/habitat.rb +3 -7
  138. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +1 -1
  139. data/modules/mu/{clouds → providers}/google/role.rb +42 -34
  140. data/modules/mu/{clouds → providers}/google/server.rb +25 -10
  141. data/modules/mu/{clouds → providers}/google/server_pool.rb +10 -10
  142. data/modules/mu/{clouds → providers}/google/user.rb +31 -21
  143. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  144. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  145. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  146. data/modules/mu/{clouds → providers}/google/vpc.rb +37 -2
  147. data/modules/tests/centos6.yaml +11 -0
  148. data/modules/tests/centos7.yaml +11 -0
  149. data/modules/tests/centos8.yaml +12 -0
  150. data/modules/tests/rds.yaml +108 -0
  151. data/modules/tests/regrooms/rds.yaml +123 -0
  152. data/spec/mu/clouds/azure_spec.rb +2 -2
  153. metadata +108 -89
  154. data/modules/mu/clouds/aws/database.rb +0 -1974
@@ -30,7 +30,8 @@ module MU
30
30
  :omnibus => "Jam everything into one monolothic configuration"
31
31
  }
32
32
 
33
- def initialize(clouds: MU::Cloud.supportedClouds, types: MU::Cloud.resource_types.keys, parent: nil, billing: nil, sources: nil, credentials: nil, group_by: :logical, savedeploys: false, diff: false, habitats: [], scrub_mu_isms: false)
33
+
34
+ def initialize(clouds: MU::Cloud.supportedClouds, types: MU::Cloud.resource_types.keys, parent: nil, billing: nil, sources: nil, credentials: nil, group_by: :logical, savedeploys: false, diff: false, habitats: [], scrub_mu_isms: false, regions: [], merge: false)
34
35
  @scraped = {}
35
36
  @clouds = clouds
36
37
  @types = types
@@ -44,8 +45,10 @@ module MU
44
45
  @savedeploys = savedeploys
45
46
  @diff = diff
46
47
  @habitats = habitats
48
+ @regions = regions
47
49
  @habitats ||= []
48
50
  @scrub_mu_isms = scrub_mu_isms
51
+ @merge = merge
49
52
  end
50
53
 
51
54
  # Walk cloud providers with available credentials to discover resources
@@ -53,7 +56,7 @@ module MU
53
56
  @default_parent = nil
54
57
 
55
58
  @clouds.each { |cloud|
56
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
59
+ cloudclass = MU::Cloud.cloudClass(cloud)
57
60
  next if cloudclass.listCredentials.nil?
58
61
 
59
62
  if cloud == "Google" and !@parent and @target_creds
@@ -65,7 +68,6 @@ module MU
65
68
 
66
69
  cloudclass.listCredentials.each { |credset|
67
70
  next if @sources and !@sources.include?(credset)
68
-
69
71
  cfg = cloudclass.credConfig(credset)
70
72
  if cfg and cfg['restrict_to_habitats']
71
73
  cfg['restrict_to_habitats'] << cfg['project'] if cfg['project']
@@ -90,7 +92,7 @@ module MU
90
92
 
91
93
  @types.each { |type|
92
94
  begin
93
- resclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(type)
95
+ resclass = MU::Cloud.resourceClass(cloud, type)
94
96
  rescue ::MU::Cloud::MuCloudResourceNotImplemented
95
97
  next
96
98
  end
@@ -106,6 +108,7 @@ module MU
106
108
  credentials: credset,
107
109
  allow_multi: true,
108
110
  habitats: @habitats.dup,
111
+ region: @regions,
109
112
  dummy_ok: true,
110
113
  skip_provider_owned: true,
111
114
  # debug: false#,
@@ -114,7 +117,9 @@ module MU
114
117
 
115
118
  if found and found.size > 0
116
119
  if resclass.cfg_plural == "habitats"
117
- found.reject! { |h| !cloudclass.listHabitats(credset).include?(h) }
120
+ found.reject! { |h|
121
+ !cloudclass.listHabitats(credset).include?(h.cloud_id)
122
+ }
118
123
  end
119
124
  MU.log "Found #{found.size.to_s} raw #{resclass.cfg_plural} in #{cloud}"
120
125
  @scraped[type] ||= {}
@@ -123,6 +128,10 @@ module MU
123
128
  next
124
129
  end
125
130
  # XXX apply any filters (e.g. MU-ID tags)
131
+ if obj.cloud_id.nil?
132
+ MU.log "This damn thing gave me no cloud id, what do I even do with that", MU::ERR, details: obj
133
+ exit
134
+ end
126
135
  @scraped[type][obj.cloud_id] = obj
127
136
  }
128
137
  end
@@ -200,7 +209,36 @@ module MU
200
209
  prefix = "mu" if prefix.empty? # so that appnames aren't ever empty
201
210
  end
202
211
 
212
+ # Find any previous deploys with this particular profile, which we'll use
213
+ # later for --diff.
214
+ @existing_deploys = {}
215
+ @existing_deploys_by_id = {}
216
+ @origins = {}
217
+ @types_found_in = {}
218
+ groupings.each_pair { |appname, types|
219
+ allowed_types = @types.map { |t| MU::Cloud.resource_types[t][:cfg_plural] }
220
+ next if (types & allowed_types).size == 0
221
+ origin = {
222
+ "appname" => prefix+appname,
223
+ "types" => (types & allowed_types).sort,
224
+ "habitats" => @habitats.sort,
225
+ "group_by" => @group_by.to_s
226
+ }
227
+
228
+ @existing_deploys[appname] = MU::MommaCat.findMatchingDeploy(origin)
229
+ if @existing_deploys[appname]
230
+ @existing_deploys_by_id[@existing_deploys[appname].deploy_id] = @existing_deploys[appname]
231
+ @origins[appname] = origin
232
+ origin['types'].each { |t|
233
+ @types_found_in[t] = @existing_deploys[appname]
234
+ }
235
+ end
236
+ }
237
+
203
238
  groupings.each_pair { |appname, types|
239
+ allowed_types = @types.map { |t| MU::Cloud.resource_types[t][:cfg_plural] }
240
+ next if (types & allowed_types).size == 0
241
+
204
242
  bok = { "appname" => prefix+appname }
205
243
  if @scrub_mu_isms
206
244
  bok["scrub_mu_isms"] = true
@@ -210,26 +248,23 @@ module MU
210
248
  end
211
249
 
212
250
  count = 0
213
- allowed_types = @types.map { |t| MU::Cloud.resource_types[t][:cfg_plural] }
214
- next if (types & allowed_types).size == 0
215
- origin = {
216
- "appname" => bok['appname'],
217
- "types" => (types & allowed_types).sort,
218
- "habitats" => @habitats.sort,
219
- "group_by" => @group_by.to_s
220
- }
221
-
222
- deploy = MU::MommaCat.findMatchingDeploy(origin)
223
- if @diff and !deploy
224
- MU.log "--diff was set but I failed to find a deploy like me to compare to", MU::ERR, details: origin
225
- exit 1
251
+ if @diff
252
+ if !@existing_deploys[appname]
253
+ MU.log "--diff was set but I failed to find a deploy like '#{appname}' to compare to (have #{@existing_deploys.keys.join(", ")})", MU::ERR, details: @origins[appname]
254
+ exit 1
255
+ else
256
+ MU.log "Will diff current live resources against #{@existing_deploys[appname].deploy_id}", MU::NOTICE, details: @origins[appname]
257
+ end
226
258
  end
227
259
 
228
260
  threads = []
261
+ timers = {}
262
+ walltimers = {}
229
263
  @clouds.each { |cloud|
230
264
  @scraped.each_pair { |type, resources|
265
+ typestart = Time.now
231
266
  res_class = begin
232
- MU::Cloud.loadCloudType(cloud, type)
267
+ MU::Cloud.resourceClass(cloud, type)
233
268
  rescue MU::Cloud::MuCloudResourceNotImplemented
234
269
  # XXX I don't think this can actually happen
235
270
  next
@@ -237,6 +272,7 @@ module MU
237
272
  next if !types.include?(res_class.cfg_plural)
238
273
 
239
274
  bok[res_class.cfg_plural] ||= []
275
+ timers[type] ||= {}
240
276
 
241
277
  class_semaphore = Mutex.new
242
278
 
@@ -253,13 +289,18 @@ module MU
253
289
  end
254
290
  end
255
291
  threads << Thread.new(obj_thr) { |obj|
292
+ start = Time.now
256
293
 
257
- kitten_cfg = obj.toKitten(rootparent: @default_parent, billing: @billing, habitats: @habitats)
294
+ kitten_cfg = obj.toKitten(rootparent: @default_parent, billing: @billing, habitats: @habitats, types: @types)
258
295
  if kitten_cfg
259
296
  print "."
260
297
  kitten_cfg.delete("credentials") if @target_creds
261
298
  class_semaphore.synchronize {
262
299
  bok[res_class.cfg_plural] << kitten_cfg
300
+ if !kitten_cfg['cloud_id']
301
+ MU.log "No cloud id in this #{res_class.cfg_name} kitten!", MU::ERR, details: kitten_cfg
302
+ end
303
+ timers[type][kitten_cfg['cloud_id']] = (Time.now - start)
263
304
  }
264
305
  count += 1
265
306
  end
@@ -270,6 +311,7 @@ module MU
270
311
  threads.each { |t|
271
312
  t.join
272
313
  }
314
+
273
315
  puts ""
274
316
  bok[res_class.cfg_plural].sort! { |a, b|
275
317
  strs = [a, b].map { |x|
@@ -291,24 +333,30 @@ module MU
291
333
  bok[res_class.cfg_plural].each { |sibling|
292
334
  next if kitten_cfg == sibling
293
335
  if sibling['name'] == kitten_cfg['name']
294
- MU.log "#{res_class.cfg_name} name #{sibling['name']} unavailable, will attempt to rename duplicate object", MU::DEBUG, details: kitten_cfg
295
- if kitten_cfg['parent'] and kitten_cfg['parent'].respond_to?(:id) and kitten_cfg['parent'].id
296
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['parent'].id
297
- elsif kitten_cfg['project']
298
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['project']
299
- elsif kitten_cfg['region']
300
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['region']
301
- elsif kitten_cfg['cloud_id']
302
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['cloud_id'].gsub(/[^a-z0-9]/i, "-")
303
- else
304
- raise MU::Config::DuplicateNameError, "Saw duplicate #{res_class.cfg_name} name #{sibling['name']} and couldn't come up with a good way to differentiate them"
305
- end
336
+ MU::Adoption.deDuplicateName(kitten_cfg, res_class)
306
337
  MU.log "De-duplication: Renamed #{res_class.cfg_name} name '#{sibling['name']}' => '#{kitten_cfg['name']}'", MU::NOTICE
307
338
  break
308
339
  end
309
340
  }
310
341
  }
342
+ walltimers[type] ||= 0
343
+ walltimers[type] += (Time.now - typestart)
344
+ }
345
+ }
346
+
347
+ timers.each_pair { |type, resources|
348
+ next if resources.empty?
349
+ total = resources.values.sum
350
+ top_5 = resources.keys.sort { |a, b|
351
+ resources[b] <=> resources[a]
352
+ }.slice(0, 5).map { |k|
353
+ k.to_s+": "+sprintf("%.2fs", resources[k])
311
354
  }
355
+ if walltimers[type] < 45
356
+ MU.log "Kittened #{resources.size.to_s} eligible #{type}s in #{sprintf("%.2fs", walltimers[type])}"
357
+ else
358
+ MU.log "Kittened #{resources.size.to_s} eligible #{type}s in #{sprintf("%.2fs", walltimers[type])} (CPU time #{sprintf("%.2fs", total)}, avg #{sprintf("%.2fs", total/resources.size)}). Top 5:", MU::NOTICE, details: top_5
359
+ end
312
360
  }
313
361
 
314
362
  # No matching resources isn't necessarily an error
@@ -317,23 +365,36 @@ module MU
317
365
  # Now walk through all of the Refs in these objects, resolve them, and minimize
318
366
  # their config footprint
319
367
  MU.log "Minimizing footprint of #{count.to_s} found resources", MU::DEBUG
320
- @boks[bok['appname']] = vacuum(bok, origin: origin, save: @savedeploys)
321
368
 
322
- if @diff and !deploy
369
+ generated_deploy = generateStubDeploy(bok)
370
+ @boks[bok['appname']] = vacuum(bok, origin: @origins[appname], deploy: generated_deploy, save: @savedeploys)
371
+
372
+ if @diff and !@existing_deploys[appname]
323
373
  MU.log "diff flag set, but no comparable deploy provided for #{bok['appname']}", MU::ERR
324
374
  exit 1
325
375
  end
326
376
 
327
- if deploy and @diff
328
- prevcfg = MU::Config.manxify(vacuum(deploy.original_config, deploy: deploy))
377
+ if @diff
378
+ prev_vacuumed = vacuum(@existing_deploys[appname].original_config, deploy: @existing_deploys[appname], keep_missing: true, copy_from: generated_deploy)
379
+ prevcfg = MU::Config.manxify(prev_vacuumed)
329
380
  if !prevcfg
330
- MU.log "#{deploy.deploy_id} didn't have a working original config for me to compare", MU::ERR
381
+ MU.log "#{@existing_deploys[appname].deploy_id} didn't have a working original config for me to compare", MU::ERR
331
382
  exit 1
332
383
  end
333
384
  newcfg = MU::Config.manxify(@boks[bok['appname']])
385
+ report = prevcfg.diff(newcfg)
386
+
387
+ if report
388
+
389
+ if MU.muCfg['adopt_change_notify']
390
+ notifyChanges(@existing_deploys[appname], report.freeze)
391
+ end
392
+ if @merge
393
+ MU.log "Saving changes to #{@existing_deploys[appname].deploy_id}"
394
+ @existing_deploys[appname].updateBasketofKittens(newcfg, save_now: true)
395
+ end
396
+ end
334
397
 
335
- prevcfg.diff(newcfg)
336
- exit
337
398
  end
338
399
  }
339
400
  @boks
@@ -341,6 +402,183 @@ module MU
341
402
 
342
403
  private
343
404
 
405
+ # @param tier [Hash]
406
+ # @param parent_key [String]
407
+ def crawlChangeReport(tier, parent_key = nil, indent: "")
408
+ report = []
409
+ if tier.is_a?(Array)
410
+ tier.each { |a|
411
+ sub_report = crawlChangeReport(a, parent_key)
412
+ report.concat(sub_report) if sub_report and !sub_report.empty?
413
+ }
414
+ elsif tier.is_a?(Hash)
415
+ if tier[:action]
416
+ preposition = if tier[:action] == :added
417
+ "to"
418
+ elsif tier[:action] == :removed
419
+ "from"
420
+ else
421
+ "in"
422
+ end
423
+
424
+ name = ""
425
+ type_of = parent_key.sub(/s$|\[.*/, '') if parent_key
426
+ loc = tier[:habitat]
427
+
428
+ if tier[:value] and tier[:value].is_a?(Hash)
429
+ name, loc = MU::MommaCat.getChunkName(tier[:value], type_of)
430
+ elsif parent_key
431
+ name = parent_key
432
+ end
433
+
434
+ path_str = []
435
+ slack_path_str = ""
436
+ if tier[:parents] and tier[:parents].size > 2
437
+ path = tier[:parents].clone
438
+ slack_path_str += "#{preposition} \*"+path.join(" ⇨ ")+"\*" if path.size > 0
439
+ path.shift
440
+ path.shift
441
+ path.pop if path.last == name
442
+ for c in (0..(path.size-1)) do
443
+ path_str << (" " * (c+2)) + (path[c] || "<nil>")
444
+ end
445
+ end
446
+ path_str << "" if !path_str.empty?
447
+
448
+ plain = (name ? name : type_of) if name or type_of
449
+ plain ||= "" # XXX but this is a problem
450
+ slack = "`"+plain+"`"
451
+
452
+ plain += " ("+loc+")" if loc and !loc.empty?
453
+ color = plain
454
+
455
+ if tier[:action] == :added
456
+ color = "+ ".green + plain
457
+ plain = "+ " + plain
458
+ slack += " added"
459
+ elsif tier[:action] == :removed
460
+ color = "- ".red + plain
461
+ plain = "- " + plain
462
+ slack += " removed"
463
+ end
464
+
465
+ slack += " #{tier[:action]} #{preposition} \*#{loc}\*" if loc and !loc.empty? and [Array, Hash].include?(tier[:value].class)
466
+
467
+ plain = path_str.join(" => \n") + indent + plain
468
+ color = path_str.join(" => \n") + indent + color
469
+
470
+ slack += " "+slack_path_str if !slack_path_str.empty?
471
+ myreport = {
472
+ "slack" => slack,
473
+ "plain" => plain,
474
+ "color" => color
475
+ }
476
+
477
+ append = ""
478
+ if tier[:value] and (tier[:value].is_a?(Array) or tier[:value].is_a?(Hash))
479
+ if tier[:value].is_a?(Hash)
480
+ if name
481
+ tier[:value].delete("entity")
482
+ tier[:value].delete(name.sub(/\[.*/, '')) if name
483
+ end
484
+ if (tier[:value].keys - ["id", "name", "type"]).size > 0
485
+ myreport["details"] = tier[:value].clone
486
+ append = PP.pp(tier[:value], '').gsub(/(^|\n)/, '\1'+indent)
487
+ end
488
+ else
489
+ append = indent+"["+tier[:value].map { |v| MU::MommaCat.getChunkName(v, type_of).reverse.join("/") || v.to_s.light_blue }.join(", ")+"]"
490
+ slack += " #{tier[:action].to_s}: "+tier[:value].map { |v| MU::MommaCat.getChunkName(v, type_of).reverse.join("/") || v.to_s }.join(", ")
491
+ end
492
+ else
493
+ tier[:value] ||= "<nil>"
494
+ if ![:removed].include?(tier[:action])
495
+ myreport["slack"] += ". New #{tier[:field] ? "`"+tier[:field]+"`" : :value}: \*#{tier[:value]}\*"
496
+ else
497
+ myreport["slack"] += " (was \*#{tier[:value]}\*)"
498
+ end
499
+ append = tier[:value].to_s.bold
500
+ end
501
+
502
+ if append and !append.empty?
503
+ myreport["plain"] += " =>\n "+indent+append
504
+ myreport["color"] += " =>\n "+indent+append
505
+ end
506
+
507
+ report << myreport if tier[:action]
508
+ end
509
+
510
+ # Just because we've got changes at this level doesn't mean there aren't
511
+ # more further down.
512
+ tier.each_pair { |k, v|
513
+ next if !(v.is_a?(Hash) or v.is_a?(Array))
514
+ sub_report = crawlChangeReport(v, k, indent: indent+" ")
515
+ report.concat(sub_report) if sub_report and !sub_report.empty?
516
+ }
517
+ end
518
+
519
+ report
520
+ end
521
+
522
+
523
+ def notifyChanges(deploy, report)
524
+ snippet_threshold = (MU.muCfg['adopt_change_notify'] && MU.muCfg['adopt_change_notify']['slack_snippet_threshold']) || 5
525
+
526
+ report.each_pair { |res_type, resources|
527
+ shortclass, _cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(res_type, false)
528
+ next if !shortclass # we don't really care about Mu metadata changes
529
+ resources.each_pair { |name, data|
530
+ if MU::MommaCat.getChunkName(data[:value], res_type).first.nil?
531
+ symbol = if data[:action] == :added
532
+ "+".green
533
+ elsif data[:action] == :removed
534
+ "-".red
535
+ else
536
+ "~".yellow
537
+ end
538
+ puts (symbol+" "+res_type+"["+name+"]")
539
+ end
540
+
541
+ noun = shortclass ? shortclass.to_s : res_type.capitalize
542
+ verb = if data[:action]
543
+ data[:action].to_s
544
+ else
545
+ "modified"
546
+ end
547
+
548
+ changes = crawlChangeReport(data.freeze, res_type)
549
+
550
+ slacktext = "#{noun} \*#{name}\* was #{verb}"
551
+ if data[:habitat]
552
+ slacktext += " in \*#{data[:habitat]}\*"
553
+ end
554
+ snippets = []
555
+
556
+ if [:added, :removed].include?(data[:action]) and data[:value]
557
+ snippets << { text: "```"+JSON.pretty_generate(data[:value])+"```" }
558
+ else
559
+ changes.each { |c|
560
+ slacktext += "\n • "+c["slack"]
561
+ if c["details"]
562
+ details = JSON.pretty_generate(c["details"])
563
+ snippets << { text: "```"+JSON.pretty_generate(c["details"])+"```" }
564
+ end
565
+ }
566
+ end
567
+
568
+ changes.each { |c|
569
+ puts c["color"]
570
+ }
571
+ puts ""
572
+
573
+ if MU.muCfg['adopt_change_notify'] and MU.muCfg['adopt_change_notify']['slack']
574
+ deploy.sendAdminSlack(slacktext, scrub_mu_isms: MU.muCfg['adopt_scrub_mu_isms'], snippets: snippets, noop: false)
575
+ end
576
+
577
+ }
578
+ }
579
+
580
+ end
581
+
344
582
  def scrubSchemaDefaults(conf_chunk, schema_chunk, depth = 0, type: nil)
345
583
  return if schema_chunk.nil?
346
584
 
@@ -372,8 +610,7 @@ module MU
372
610
  # theory
373
611
  realschema = if type and schema_chunk["items"] and schema_chunk["items"]["properties"] and item["cloud"] and MU::Cloud.supportedClouds.include?(item['cloud'])
374
612
 
375
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(item["cloud"]).const_get(type)
376
- _toplevel_required, cloudschema = cloudclass.schema(self)
613
+ _toplevel_required, cloudschema = MU::Cloud.resourceClass(item['cloud'], type).schema(self)
377
614
 
378
615
  newschema = schema_chunk["items"].dup
379
616
  newschema["properties"].merge!(cloudschema)
@@ -397,8 +634,7 @@ module MU
397
634
  # Do the same for our main objects: if they all use the same credentials,
398
635
  # for example, remove the explicit +credentials+ attributes and set that
399
636
  # value globally, once.
400
- def vacuum(bok, origin: nil, save: false, deploy: nil)
401
- deploy ||= generateStubDeploy(bok)
637
+ def vacuum(bok, origin: nil, save: false, deploy: nil, copy_from: nil, keep_missing: false)
402
638
 
403
639
  globals = {
404
640
  'cloud' => {},
@@ -418,11 +654,24 @@ module MU
418
654
  end
419
655
  }
420
656
  obj = deploy.findLitterMate(type: attrs[:cfg_plural], name: resource['name'])
657
+ inject_metadata = save
658
+ if obj.nil? and copy_from
659
+ obj = copy_from.findLitterMate(type: attrs[:cfg_plural], name: resource['name'])
660
+ if obj
661
+ inject_metadata = true
662
+ obj.intoDeploy(deploy, force: true)
663
+ end
664
+ end
665
+
421
666
  begin
422
667
  raise Incomplete if obj.nil?
668
+ if inject_metadata
669
+ deploydata = obj.notify
670
+ deploy.notify(attrs[:cfg_plural], resource['name'], deploydata, triggering_node: obj)
671
+ end
423
672
  new_cfg = resolveReferences(resource, deploy, obj)
424
673
  new_cfg.delete("cloud_id")
425
- cred_cfg = MU::Cloud.const_get(obj.cloud).credConfig(obj.credentials)
674
+ cred_cfg = MU::Cloud.cloudClass(obj.cloud).credConfig(obj.credentials)
426
675
  if cred_cfg['region'] == new_cfg['region']
427
676
  new_cfg.delete('region')
428
677
  end
@@ -432,6 +681,11 @@ module MU
432
681
  end
433
682
  processed << new_cfg
434
683
  rescue Incomplete
684
+ if keep_missing
685
+ processed << resource
686
+ else
687
+ MU.log "#{attrs[:cfg_name]} #{resource['name']} didn't show up from findLitterMate", MU::WARN, details: deploy.original_config[attrs[:cfg_plural]].reject { |r| r['name'] != "" }
688
+ end
435
689
  end
436
690
  }
437
691
 
@@ -442,24 +696,23 @@ module MU
442
696
 
443
697
  # Pare out global values like +cloud+ or +region+ that appear to be
444
698
  # universal in the deploy we're creating.
445
- def scrub_globals(h, field)
699
+ scrub_globals = Proc.new { |h, field|
446
700
  if h.is_a?(Hash)
447
701
  newhash = {}
448
702
  h.each_pair { |k, v|
449
703
  next if k == field
450
- newhash[k] = scrub_globals(v, field)
704
+ newhash[k] = scrub_globals.call(v, field)
451
705
  }
452
706
  h = newhash
453
707
  elsif h.is_a?(Array)
454
708
  newarr = []
455
709
  h.each { |v|
456
- newarr << scrub_globals(v, field)
710
+ newarr << scrub_globals.call(v, field)
457
711
  }
458
- h = newarr
712
+ h = newarr.uniq
459
713
  end
460
-
461
714
  h
462
- end
715
+ }
463
716
 
464
717
  globals.each_pair { |field, counts|
465
718
  next if counts.size != 1
@@ -469,7 +722,7 @@ module MU
469
722
  if bok[attrs[:cfg_plural]]
470
723
  new_resources = []
471
724
  bok[attrs[:cfg_plural]].each { |resource|
472
- new_resources << scrub_globals(resource, field)
725
+ new_resources << scrub_globals.call(resource, field)
473
726
  }
474
727
  bok[attrs[:cfg_plural]] = new_resources
475
728
  end
@@ -487,11 +740,33 @@ module MU
487
740
  end
488
741
 
489
742
  def resolveReferences(cfg, deploy, parent)
743
+ mask_deploy_id = false
744
+
745
+ check_deploy_id = Proc.new { |cfgblob|
746
+ (deploy and
747
+ (cfgblob.is_a?(MU::Config::Ref) or cfgblob.is_a?(Hash)) and
748
+ cfgblob['deploy_id'] and
749
+ cfgblob['deploy_id'] != deploy.deploy_id and
750
+ @diff and
751
+ @types_found_in[cfgblob['type']] and
752
+ @types_found_in[cfgblob['type']].deploy_id == cfgblob['deploy_id']
753
+ )
754
+ }
755
+
756
+ mask_deploy_id = check_deploy_id.call(cfg)
757
+
490
758
  if cfg.is_a?(MU::Config::Ref)
491
- cfg.kitten(deploy) || cfg.kitten
759
+ if mask_deploy_id
760
+ cfg.delete("deploy_id")
761
+ cfg.delete("mommacat")
762
+ cfg.kitten(deploy)
763
+ else
764
+ cfg.kitten(deploy) || cfg.kitten
765
+ end
766
+
492
767
  hashcfg = cfg.to_h
493
768
 
494
- if cfg.kitten(deploy)
769
+ if cfg.kitten
495
770
  littermate = deploy.findLitterMate(type: cfg.type, name: cfg.name, cloud_id: cfg.id, habitat: cfg.habitat)
496
771
 
497
772
  if littermate and littermate.config['name']
@@ -522,7 +797,7 @@ module MU
522
797
  hashcfg.delete("deploy_id") if hashcfg['deploy_id'] == deploy.deploy_id
523
798
 
524
799
  if parent and parent.config
525
- cred_cfg = MU::Cloud.const_get(parent.cloud).credConfig(parent.credentials)
800
+ cred_cfg = MU::Cloud.cloudClass(parent.cloud).credConfig(parent.credentials)
526
801
 
527
802
  if parent.config['region'] == hashcfg['region'] or
528
803
  cred_cfg['region'] == hashcfg['region']
@@ -581,7 +856,12 @@ module MU
581
856
  MU.log "Dropping unresolved value", MU::WARN, details: value
582
857
  end
583
858
  }
584
- cfg = new_array
859
+ cfg = new_array.uniq
860
+ end
861
+
862
+ if mask_deploy_id or check_deploy_id.call(cfg)
863
+ cfg.delete("deploy_id")
864
+ MU.log "#{parent} in #{deploy.deploy_id} references something in #{@types_found_in[cfg['type']].deploy_id}, ditching extraneous deploy_id", MU::DEBUG, details: cfg.to_h
585
865
  end
586
866
 
587
867
  cfg
@@ -631,6 +911,10 @@ module MU
631
911
 
632
912
  if !@scraped[typename][kitten['cloud_id']]
633
913
  MU.log "No object in scraped tree for #{attrs[:cfg_name]} #{kitten['cloud_id']} (#{kitten['name']})", MU::ERR, details: kitten
914
+ if kitten['cloud_id'].nil?
915
+ pp caller
916
+ exit
917
+ end
634
918
  next
635
919
  end
636
920
 
@@ -641,7 +925,8 @@ module MU
641
925
  deploy.addKitten(
642
926
  attrs[:cfg_plural],
643
927
  kitten['name'],
644
- @scraped[typename][kitten['cloud_id']]
928
+ @scraped[typename][kitten['cloud_id']],
929
+ do_notify: true
645
930
  )
646
931
  }
647
932
  end
@@ -650,6 +935,21 @@ module MU
650
935
  deploy
651
936
  end
652
937
 
938
+ def self.deDuplicateName(kitten_cfg, res_class)
939
+ orig_name = kitten_cfg['name'].dup
940
+ if kitten_cfg['parent'] and kitten_cfg['parent'].respond_to?(:id) and kitten_cfg['parent'].id
941
+ kitten_cfg['name'] = kitten_cfg['name']+"-"+kitten_cfg['parent'].id
942
+ elsif kitten_cfg['project']
943
+ kitten_cfg['name'] = kitten_cfg['name']+"-"+kitten_cfg['project']
944
+ elsif kitten_cfg['region']
945
+ kitten_cfg['name'] = kitten_cfg['name']+"-"+kitten_cfg['region']
946
+ elsif kitten_cfg['cloud_id']
947
+ kitten_cfg['name'] = kitten_cfg['name']+"-"+kitten_cfg['cloud_id'].gsub(/[^a-z0-9]/i, "-")
948
+ else
949
+ raise MU::Config::DuplicateNameError, "Saw duplicate #{res_class.cfg_name} name #{orig_name} and couldn't come up with a good way to differentiate them"
950
+ end
951
+ end
952
+
653
953
  # Go through everything we've scraped and update our mappings of cloud ids
654
954
  # and bare name fields, so that resources can reference one another
655
955
  # portably by name.