cloud-mu 3.1.6 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.