cloud-mu 3.1.2 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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.