cloud-mu 3.1.2 → 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 (201) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +15 -3
  3. data/ansible/roles/mu-windows/README.md +33 -0
  4. data/ansible/roles/mu-windows/defaults/main.yml +2 -0
  5. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  6. data/ansible/roles/mu-windows/files/config.xml +76 -0
  7. data/ansible/roles/mu-windows/handlers/main.yml +2 -0
  8. data/ansible/roles/mu-windows/meta/main.yml +53 -0
  9. data/ansible/roles/mu-windows/tasks/main.yml +36 -0
  10. data/ansible/roles/mu-windows/tests/inventory +2 -0
  11. data/ansible/roles/mu-windows/tests/test.yml +5 -0
  12. data/ansible/roles/mu-windows/vars/main.yml +2 -0
  13. data/bin/mu-adopt +10 -13
  14. data/bin/mu-azure-tests +57 -0
  15. data/bin/mu-cleanup +2 -4
  16. data/bin/mu-configure +52 -0
  17. data/bin/mu-deploy +3 -3
  18. data/bin/mu-findstray-tests +25 -0
  19. data/bin/mu-gen-docs +2 -4
  20. data/bin/mu-load-config.rb +2 -3
  21. data/bin/mu-node-manage +15 -16
  22. data/bin/mu-run-tests +135 -37
  23. data/cloud-mu.gemspec +22 -20
  24. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  25. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  26. data/cookbooks/mu-tools/libraries/helper.rb +3 -2
  27. data/cookbooks/mu-tools/libraries/monkey.rb +35 -0
  28. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  29. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  30. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  31. data/cookbooks/mu-tools/recipes/google_api.rb +2 -2
  32. data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
  33. data/cookbooks/mu-tools/recipes/windows-client.rb +163 -164
  34. data/cookbooks/mu-tools/resources/disk.rb +1 -1
  35. data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
  36. data/extras/clean-stock-amis +25 -19
  37. data/extras/generate-stock-images +1 -0
  38. data/extras/image-generators/AWS/win2k12.yaml +18 -13
  39. data/extras/image-generators/AWS/win2k16.yaml +18 -13
  40. data/extras/image-generators/AWS/win2k19.yaml +21 -0
  41. data/extras/image-generators/Google/centos6.yaml +1 -0
  42. data/extras/image-generators/Google/centos7.yaml +1 -1
  43. data/modules/mommacat.ru +6 -16
  44. data/modules/mu.rb +165 -111
  45. data/modules/mu/adoption.rb +401 -68
  46. data/modules/mu/cleanup.rb +199 -306
  47. data/modules/mu/cloud.rb +100 -1632
  48. data/modules/mu/cloud/database.rb +49 -0
  49. data/modules/mu/cloud/dnszone.rb +46 -0
  50. data/modules/mu/cloud/machine_images.rb +212 -0
  51. data/modules/mu/cloud/providers.rb +81 -0
  52. data/modules/mu/cloud/resource_base.rb +920 -0
  53. data/modules/mu/cloud/server.rb +40 -0
  54. data/modules/mu/cloud/server_pool.rb +1 -0
  55. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  56. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  57. data/modules/mu/cloud/wrappers.rb +165 -0
  58. data/modules/mu/config.rb +171 -1767
  59. data/modules/mu/config/alarm.rb +2 -6
  60. data/modules/mu/config/bucket.rb +4 -4
  61. data/modules/mu/config/cache_cluster.rb +1 -1
  62. data/modules/mu/config/collection.rb +4 -4
  63. data/modules/mu/config/container_cluster.rb +9 -4
  64. data/modules/mu/config/database.rb +83 -104
  65. data/modules/mu/config/database.yml +1 -2
  66. data/modules/mu/config/dnszone.rb +6 -6
  67. data/modules/mu/config/doc_helpers.rb +516 -0
  68. data/modules/mu/config/endpoint.rb +4 -4
  69. data/modules/mu/config/firewall_rule.rb +103 -4
  70. data/modules/mu/config/folder.rb +4 -4
  71. data/modules/mu/config/function.rb +3 -3
  72. data/modules/mu/config/group.rb +4 -4
  73. data/modules/mu/config/habitat.rb +4 -4
  74. data/modules/mu/config/loadbalancer.rb +60 -14
  75. data/modules/mu/config/log.rb +4 -4
  76. data/modules/mu/config/msg_queue.rb +4 -4
  77. data/modules/mu/config/nosqldb.rb +4 -4
  78. data/modules/mu/config/notifier.rb +3 -3
  79. data/modules/mu/config/ref.rb +365 -0
  80. data/modules/mu/config/role.rb +4 -4
  81. data/modules/mu/config/schema_helpers.rb +509 -0
  82. data/modules/mu/config/search_domain.rb +4 -4
  83. data/modules/mu/config/server.rb +97 -70
  84. data/modules/mu/config/server.yml +1 -0
  85. data/modules/mu/config/server_pool.rb +5 -9
  86. data/modules/mu/config/storage_pool.rb +1 -1
  87. data/modules/mu/config/tail.rb +200 -0
  88. data/modules/mu/config/user.rb +4 -4
  89. data/modules/mu/config/vpc.rb +70 -27
  90. data/modules/mu/config/vpc.yml +0 -1
  91. data/modules/mu/defaults/AWS.yaml +83 -60
  92. data/modules/mu/defaults/Azure.yaml +1 -0
  93. data/modules/mu/defaults/Google.yaml +3 -2
  94. data/modules/mu/deploy.rb +30 -26
  95. data/modules/mu/groomer.rb +17 -2
  96. data/modules/mu/groomers/ansible.rb +188 -41
  97. data/modules/mu/groomers/chef.rb +116 -55
  98. data/modules/mu/logger.rb +127 -148
  99. data/modules/mu/master.rb +389 -2
  100. data/modules/mu/master/chef.rb +3 -4
  101. data/modules/mu/master/ldap.rb +3 -3
  102. data/modules/mu/master/ssl.rb +12 -3
  103. data/modules/mu/mommacat.rb +217 -2612
  104. data/modules/mu/mommacat/daemon.rb +397 -0
  105. data/modules/mu/mommacat/naming.rb +473 -0
  106. data/modules/mu/mommacat/search.rb +495 -0
  107. data/modules/mu/mommacat/storage.rb +722 -0
  108. data/modules/mu/{clouds → providers}/README.md +1 -1
  109. data/modules/mu/{clouds → providers}/aws.rb +271 -112
  110. data/modules/mu/{clouds → providers}/aws/alarm.rb +5 -3
  111. data/modules/mu/{clouds → providers}/aws/bucket.rb +26 -22
  112. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +33 -67
  113. data/modules/mu/{clouds → providers}/aws/collection.rb +24 -23
  114. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +681 -721
  115. data/modules/mu/providers/aws/database.rb +1744 -0
  116. data/modules/mu/{clouds → providers}/aws/dnszone.rb +64 -63
  117. data/modules/mu/{clouds → providers}/aws/endpoint.rb +22 -27
  118. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +214 -244
  119. data/modules/mu/{clouds → providers}/aws/folder.rb +7 -7
  120. data/modules/mu/{clouds → providers}/aws/function.rb +17 -22
  121. data/modules/mu/{clouds → providers}/aws/group.rb +23 -23
  122. data/modules/mu/{clouds → providers}/aws/habitat.rb +17 -14
  123. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +57 -48
  124. data/modules/mu/{clouds → providers}/aws/log.rb +15 -12
  125. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +17 -16
  126. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +18 -11
  127. data/modules/mu/{clouds → providers}/aws/notifier.rb +11 -6
  128. data/modules/mu/{clouds → providers}/aws/role.rb +112 -86
  129. data/modules/mu/{clouds → providers}/aws/search_domain.rb +39 -33
  130. data/modules/mu/{clouds → providers}/aws/server.rb +835 -1133
  131. data/modules/mu/{clouds → providers}/aws/server_pool.rb +56 -60
  132. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +24 -42
  133. data/modules/mu/{clouds → providers}/aws/user.rb +21 -22
  134. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  135. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +0 -0
  136. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
  137. data/modules/mu/{clouds → providers}/aws/vpc.rb +523 -929
  138. data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
  139. data/modules/mu/{clouds → providers}/azure.rb +29 -9
  140. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +3 -8
  141. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +18 -11
  142. data/modules/mu/{clouds → providers}/azure/habitat.rb +8 -6
  143. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +5 -5
  144. data/modules/mu/{clouds → providers}/azure/role.rb +8 -10
  145. data/modules/mu/{clouds → providers}/azure/server.rb +95 -48
  146. data/modules/mu/{clouds → providers}/azure/user.rb +6 -8
  147. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  148. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  149. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  150. data/modules/mu/{clouds → providers}/azure/vpc.rb +16 -21
  151. data/modules/mu/{clouds → providers}/cloudformation.rb +18 -7
  152. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  153. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  154. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  155. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  156. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  157. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  158. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  159. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  160. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  161. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  162. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +5 -7
  163. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  164. data/modules/mu/{clouds → providers}/google.rb +67 -30
  165. data/modules/mu/{clouds → providers}/google/bucket.rb +13 -15
  166. data/modules/mu/{clouds → providers}/google/container_cluster.rb +84 -77
  167. data/modules/mu/{clouds → providers}/google/database.rb +10 -20
  168. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +15 -14
  169. data/modules/mu/{clouds → providers}/google/folder.rb +20 -17
  170. data/modules/mu/{clouds → providers}/google/function.rb +139 -167
  171. data/modules/mu/{clouds → providers}/google/group.rb +29 -34
  172. data/modules/mu/{clouds → providers}/google/habitat.rb +21 -22
  173. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +18 -20
  174. data/modules/mu/{clouds → providers}/google/role.rb +92 -58
  175. data/modules/mu/{clouds → providers}/google/server.rb +242 -155
  176. data/modules/mu/{clouds → providers}/google/server_pool.rb +25 -44
  177. data/modules/mu/{clouds → providers}/google/user.rb +95 -31
  178. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  179. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  180. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  181. data/modules/mu/{clouds → providers}/google/vpc.rb +103 -79
  182. data/modules/tests/bucket.yml +4 -0
  183. data/modules/tests/centos6.yaml +11 -0
  184. data/modules/tests/centos7.yaml +11 -0
  185. data/modules/tests/centos8.yaml +12 -0
  186. data/modules/tests/ecs.yaml +23 -0
  187. data/modules/tests/includes-and-params.yaml +2 -1
  188. data/modules/tests/rds.yaml +108 -0
  189. data/modules/tests/regrooms/aws-iam.yaml +201 -0
  190. data/modules/tests/regrooms/bucket.yml +19 -0
  191. data/modules/tests/regrooms/rds.yaml +123 -0
  192. data/modules/tests/server-with-scrub-muisms.yaml +1 -0
  193. data/modules/tests/super_simple_bok.yml +1 -3
  194. data/modules/tests/win2k12.yaml +17 -5
  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 +232 -154
  200. data/extras/image-generators/AWS/windows.yaml +0 -18
  201. data/modules/mu/clouds/aws/database.rb +0 -1985
@@ -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: [])
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,7 +45,10 @@ module MU
44
45
  @savedeploys = savedeploys
45
46
  @diff = diff
46
47
  @habitats = habitats
48
+ @regions = regions
47
49
  @habitats ||= []
50
+ @scrub_mu_isms = scrub_mu_isms
51
+ @merge = merge
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,30 @@ 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
113
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
114
135
  @scraped[type][obj.cloud_id] = obj
115
136
  }
116
137
  end
@@ -123,6 +144,8 @@ module MU
123
144
  MU.log "Failed to locate a folder that resembles #{@parent}", MU::ERR
124
145
  end
125
146
  MU.log "Scraping complete"
147
+
148
+ @scraped
126
149
  end
127
150
 
128
151
  # Given a list of BoK style tags, try to reverse-engineer the correct
@@ -130,8 +153,9 @@ module MU
130
153
  # this infers from Mu-style tagging, but we'll add a couple cases for
131
154
  # special cloud provider cases.
132
155
  # @param tags [Array<Hash>]
156
+ # @param basename [String]
133
157
  # return [String]
134
- def self.tagsToName(tags = [])
158
+ def self.tagsToName(tags = [], basename: nil)
135
159
  tags.each { |tag|
136
160
  if tag['key'] == "aws:cloudformation:logical-id"
137
161
  return tag['value']
@@ -144,6 +168,7 @@ module MU
144
168
  break
145
169
  end
146
170
  }
171
+
147
172
  tags.each { |tag|
148
173
  if tag['key'] == "Name"
149
174
  if muid and tag['value'].match(/^#{Regexp.quote(muid)}-(.*)/)
@@ -153,6 +178,11 @@ module MU
153
178
  end
154
179
  end
155
180
  }
181
+
182
+ if basename and muid and basename.match(/^#{Regexp.quote(muid)}-(.*)/)
183
+ return Regexp.last_match[1].downcase
184
+ end
185
+
156
186
  nil
157
187
  end
158
188
 
@@ -179,53 +209,98 @@ module MU
179
209
  prefix = "mu" if prefix.empty? # so that appnames aren't ever empty
180
210
  end
181
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 = {}
182
218
  groupings.each_pair { |appname, types|
183
- bok = { "appname" => prefix+appname }
184
- if @target_creds
185
- bok["credentials"] = @target_creds
186
- end
187
-
188
- count = 0
189
219
  allowed_types = @types.map { |t| MU::Cloud.resource_types[t][:cfg_plural] }
190
220
  next if (types & allowed_types).size == 0
191
221
  origin = {
192
- "appname" => bok['appname'],
222
+ "appname" => prefix+appname,
193
223
  "types" => (types & allowed_types).sort,
194
224
  "habitats" => @habitats.sort,
195
225
  "group_by" => @group_by.to_s
196
226
  }
197
227
 
198
- deploy = MU::MommaCat.findMatchingDeploy(origin)
199
- if @diff and !deploy
200
- MU.log "--diff was set but I failed to find a deploy like me to compare to", MU::ERR, details: origin
201
- exit 1
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
+
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
+
242
+ bok = { "appname" => prefix+appname }
243
+ if @scrub_mu_isms
244
+ bok["scrub_mu_isms"] = true
245
+ end
246
+ if @target_creds
247
+ bok["credentials"] = @target_creds
248
+ end
249
+
250
+ count = 0
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
202
258
  end
203
259
 
204
260
  threads = []
261
+ timers = {}
262
+ walltimers = {}
205
263
  @clouds.each { |cloud|
206
264
  @scraped.each_pair { |type, resources|
265
+ typestart = Time.now
207
266
  res_class = begin
208
- MU::Cloud.loadCloudType(cloud, type)
209
- rescue MU::Cloud::MuCloudResourceNotImplemented => e
267
+ MU::Cloud.resourceClass(cloud, type)
268
+ rescue MU::Cloud::MuCloudResourceNotImplemented
210
269
  # XXX I don't think this can actually happen
211
270
  next
212
271
  end
213
272
  next if !types.include?(res_class.cfg_plural)
214
273
 
215
274
  bok[res_class.cfg_plural] ||= []
275
+ timers[type] ||= {}
216
276
 
217
277
  class_semaphore = Mutex.new
218
278
 
219
279
  Thread.abort_on_exception = true
220
- resources.each_pair { |cloud_id_thr, obj_thr|
221
- threads << Thread.new(cloud_id_thr, obj_thr) { |cloud_id, obj|
280
+ resources.values.each { |obj_thr|
281
+ obj_desc = nil
282
+ begin
283
+ obj_desc = obj_thr.cloud_desc
284
+ rescue StandardError
285
+ ensure
286
+ if !obj_desc
287
+ MU.log cloud+" "+type.to_s+" "+obj_thr.cloud_id+" did not return a cloud descriptor, skipping", MU::WARN
288
+ next
289
+ end
290
+ end
291
+ threads << Thread.new(obj_thr) { |obj|
292
+ start = Time.now
222
293
 
223
- 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)
224
295
  if kitten_cfg
225
296
  print "."
226
297
  kitten_cfg.delete("credentials") if @target_creds
227
298
  class_semaphore.synchronize {
228
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)
229
304
  }
230
305
  count += 1
231
306
  end
@@ -236,6 +311,7 @@ module MU
236
311
  threads.each { |t|
237
312
  t.join
238
313
  }
314
+
239
315
  puts ""
240
316
  bok[res_class.cfg_plural].sort! { |a, b|
241
317
  strs = [a, b].map { |x|
@@ -257,49 +333,68 @@ module MU
257
333
  bok[res_class.cfg_plural].each { |sibling|
258
334
  next if kitten_cfg == sibling
259
335
  if sibling['name'] == kitten_cfg['name']
260
- MU.log "#{res_class.cfg_name} name #{sibling['name']} unavailable, will attempt to rename duplicate object", MU::DEBUG, details: kitten_cfg
261
- if kitten_cfg['parent'] and kitten_cfg['parent'].respond_to?(:id) and kitten_cfg['parent'].id
262
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['parent'].id
263
- elsif kitten_cfg['project']
264
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['project']
265
- elsif kitten_cfg['region']
266
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['region']
267
- elsif kitten_cfg['cloud_id']
268
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['cloud_id'].gsub(/[^a-z0-9]/i, "-")
269
- else
270
- 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"
271
- end
336
+ MU::Adoption.deDuplicateName(kitten_cfg, res_class)
272
337
  MU.log "De-duplication: Renamed #{res_class.cfg_name} name '#{sibling['name']}' => '#{kitten_cfg['name']}'", MU::NOTICE
273
338
  break
274
339
  end
275
340
  }
276
341
  }
342
+ walltimers[type] ||= 0
343
+ walltimers[type] += (Time.now - typestart)
277
344
  }
278
345
  }
279
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])
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
360
+ }
361
+
280
362
  # No matching resources isn't necessarily an error
281
363
  next if count == 0 or bok.nil?
282
364
 
283
365
  # Now walk through all of the Refs in these objects, resolve them, and minimize
284
366
  # their config footprint
285
367
  MU.log "Minimizing footprint of #{count.to_s} found resources", MU::DEBUG
286
- @boks[bok['appname']] = vacuum(bok, origin: origin, save: @savedeploys)
287
368
 
288
- 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]
289
373
  MU.log "diff flag set, but no comparable deploy provided for #{bok['appname']}", MU::ERR
290
374
  exit 1
291
375
  end
292
376
 
293
- if deploy and @diff
294
- 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)
295
380
  if !prevcfg
296
- 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
297
382
  exit 1
298
383
  end
299
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
300
397
 
301
- prevcfg.diff(newcfg)
302
- exit
303
398
  end
304
399
  }
305
400
  @boks
@@ -307,14 +402,191 @@ module MU
307
402
 
308
403
  private
309
404
 
310
- def scrubSchemaDefaults(conf_chunk, schema_chunk, depth = 0, siblings = nil, type: nil)
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
+
582
+ def scrubSchemaDefaults(conf_chunk, schema_chunk, depth = 0, type: nil)
311
583
  return if schema_chunk.nil?
312
584
 
313
585
  if !conf_chunk.nil? and schema_chunk["properties"].kind_of?(Hash) and conf_chunk.is_a?(Hash)
314
586
  deletia = []
315
587
  schema_chunk["properties"].each_pair { |key, subschema|
316
588
  next if !conf_chunk[key]
317
- shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(key)
589
+ shortclass, _cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(key, false)
318
590
 
319
591
  if subschema["default_if"]
320
592
  subschema["default_if"].each { |cond|
@@ -328,7 +600,7 @@ module MU
328
600
  if subschema["default"] and conf_chunk[key] == subschema["default"]
329
601
  deletia << key
330
602
  elsif ["array", "object"].include?(subschema["type"])
331
- scrubSchemaDefaults(conf_chunk[key], subschema, depth+1, conf_chunk, type: shortclass)
603
+ scrubSchemaDefaults(conf_chunk[key], subschema, depth+1, type: shortclass)
332
604
  end
333
605
  }
334
606
  deletia.each { |key| conf_chunk.delete(key) }
@@ -338,8 +610,7 @@ module MU
338
610
  # theory
339
611
  realschema = if type and schema_chunk["items"] and schema_chunk["items"]["properties"] and item["cloud"] and MU::Cloud.supportedClouds.include?(item['cloud'])
340
612
 
341
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(item["cloud"]).const_get(type)
342
- toplevel_required, cloudschema = cloudclass.schema(self)
613
+ _toplevel_required, cloudschema = MU::Cloud.resourceClass(item['cloud'], type).schema(self)
343
614
 
344
615
  newschema = schema_chunk["items"].dup
345
616
  newschema["properties"].merge!(cloudschema)
@@ -349,7 +620,7 @@ module MU
349
620
  end
350
621
  next if ["array", "object"].include?(realschema["type"])
351
622
 
352
- scrubSchemaDefaults(item, realschema, depth+1, conf_chunk, type: type)
623
+ scrubSchemaDefaults(item, realschema, depth+1, type: type)
353
624
  }
354
625
  end
355
626
 
@@ -363,8 +634,7 @@ module MU
363
634
  # Do the same for our main objects: if they all use the same credentials,
364
635
  # for example, remove the explicit +credentials+ attributes and set that
365
636
  # value globally, once.
366
- def vacuum(bok, origin: nil, save: false, deploy: nil)
367
- deploy ||= generateStubDeploy(bok)
637
+ def vacuum(bok, origin: nil, save: false, deploy: nil, copy_from: nil, keep_missing: false)
368
638
 
369
639
  globals = {
370
640
  'cloud' => {},
@@ -373,10 +643,7 @@ module MU
373
643
  'billing_acct' => {},
374
644
  'us_only' => {},
375
645
  }
376
- clouds = {}
377
- credentials = {}
378
- regions = {}
379
- MU::Cloud.resource_types.each_pair { |typename, attrs|
646
+ MU::Cloud.resource_types.values.each { |attrs|
380
647
  if bok[attrs[:cfg_plural]]
381
648
  processed = []
382
649
  bok[attrs[:cfg_plural]].each { |resource|
@@ -387,11 +654,24 @@ module MU
387
654
  end
388
655
  }
389
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
+
390
666
  begin
391
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
392
672
  new_cfg = resolveReferences(resource, deploy, obj)
393
673
  new_cfg.delete("cloud_id")
394
- cred_cfg = MU::Cloud.const_get(obj.cloud).credConfig(obj.credentials)
674
+ cred_cfg = MU::Cloud.cloudClass(obj.cloud).credConfig(obj.credentials)
395
675
  if cred_cfg['region'] == new_cfg['region']
396
676
  new_cfg.delete('region')
397
677
  end
@@ -401,6 +681,11 @@ module MU
401
681
  end
402
682
  processed << new_cfg
403
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
404
689
  end
405
690
  }
406
691
 
@@ -411,34 +696,33 @@ module MU
411
696
 
412
697
  # Pare out global values like +cloud+ or +region+ that appear to be
413
698
  # universal in the deploy we're creating.
414
- def scrub_globals(h, field)
699
+ scrub_globals = Proc.new { |h, field|
415
700
  if h.is_a?(Hash)
416
701
  newhash = {}
417
702
  h.each_pair { |k, v|
418
703
  next if k == field
419
- newhash[k] = scrub_globals(v, field)
704
+ newhash[k] = scrub_globals.call(v, field)
420
705
  }
421
706
  h = newhash
422
707
  elsif h.is_a?(Array)
423
708
  newarr = []
424
709
  h.each { |v|
425
- newarr << scrub_globals(v, field)
710
+ newarr << scrub_globals.call(v, field)
426
711
  }
427
- h = newarr
712
+ h = newarr.uniq
428
713
  end
429
-
430
714
  h
431
- end
715
+ }
432
716
 
433
717
  globals.each_pair { |field, counts|
434
718
  next if counts.size != 1
435
719
  bok[field] = counts.keys.first
436
720
  MU.log "Setting global default #{field} to #{bok[field]} (#{deploy.deploy_id})", MU::DEBUG
437
- MU::Cloud.resource_types.each_pair { |typename, attrs|
721
+ MU::Cloud.resource_types.values.each { |attrs|
438
722
  if bok[attrs[:cfg_plural]]
439
723
  new_resources = []
440
724
  bok[attrs[:cfg_plural]].each { |resource|
441
- new_resources << scrub_globals(resource, field)
725
+ new_resources << scrub_globals.call(resource, field)
442
726
  }
443
727
  bok[attrs[:cfg_plural]] = new_resources
444
728
  end
@@ -456,9 +740,33 @@ module MU
456
740
  end
457
741
 
458
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
+
459
758
  if cfg.is_a?(MU::Config::Ref)
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
+
460
767
  hashcfg = cfg.to_h
461
- if cfg.kitten(deploy)
768
+
769
+ if cfg.kitten
462
770
  littermate = deploy.findLitterMate(type: cfg.type, name: cfg.name, cloud_id: cfg.id, habitat: cfg.habitat)
463
771
 
464
772
  if littermate and littermate.config['name']
@@ -480,16 +788,16 @@ module MU
480
788
  hashcfg.delete("name") if cfg.id and !cfg.deploy_id
481
789
  end
482
790
  end
483
- elsif hashcfg["id"] # reference to raw cloud ids is reasonable
791
+ elsif hashcfg["id"] and !hashcfg["name"]
484
792
  hashcfg.delete("deploy_id")
485
- hashcfg.delete("name")
486
793
  else
487
794
  pp parent.cloud_desc
488
795
  raise Incomplete, "Failed to resolve reference on behalf of #{parent}"
489
796
  end
490
797
  hashcfg.delete("deploy_id") if hashcfg['deploy_id'] == deploy.deploy_id
798
+
491
799
  if parent and parent.config
492
- cred_cfg = MU::Cloud.const_get(parent.cloud).credConfig(parent.credentials)
800
+ cred_cfg = MU::Cloud.cloudClass(parent.cloud).credConfig(parent.credentials)
493
801
 
494
802
  if parent.config['region'] == hashcfg['region'] or
495
803
  cred_cfg['region'] == hashcfg['region']
@@ -548,7 +856,12 @@ module MU
548
856
  MU.log "Dropping unresolved value", MU::WARN, details: value
549
857
  end
550
858
  }
551
- 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
552
865
  end
553
866
 
554
867
  cfg
@@ -598,6 +911,10 @@ module MU
598
911
 
599
912
  if !@scraped[typename][kitten['cloud_id']]
600
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
601
918
  next
602
919
  end
603
920
 
@@ -608,7 +925,8 @@ module MU
608
925
  deploy.addKitten(
609
926
  attrs[:cfg_plural],
610
927
  kitten['name'],
611
- @scraped[typename][kitten['cloud_id']]
928
+ @scraped[typename][kitten['cloud_id']],
929
+ do_notify: true
612
930
  )
613
931
  }
614
932
  end
@@ -617,6 +935,21 @@ module MU
617
935
  deploy
618
936
  end
619
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
+
620
953
  # Go through everything we've scraped and update our mappings of cloud ids
621
954
  # and bare name fields, so that resources can reference one another
622
955
  # portably by name.