cloud-mu 3.1.4 → 3.3.1

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