cloud-mu 3.1.3 → 3.3.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 (212) 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 +21 -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 +4 -4
  21. data/bin/mu-node-manage +15 -16
  22. data/bin/mu-run-tests +147 -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 +158 -111
  45. data/modules/mu/adoption.rb +404 -71
  46. data/modules/mu/cleanup.rb +221 -306
  47. data/modules/mu/cloud.rb +129 -1633
  48. data/modules/mu/cloud/database.rb +49 -0
  49. data/modules/mu/cloud/dnszone.rb +44 -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 +926 -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 +169 -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 +32 -3
  61. data/modules/mu/config/cache_cluster.rb +2 -2
  62. data/modules/mu/config/cdn.rb +100 -0
  63. data/modules/mu/config/collection.rb +4 -4
  64. data/modules/mu/config/container_cluster.rb +9 -4
  65. data/modules/mu/config/database.rb +84 -105
  66. data/modules/mu/config/database.yml +1 -2
  67. data/modules/mu/config/dnszone.rb +10 -9
  68. data/modules/mu/config/doc_helpers.rb +516 -0
  69. data/modules/mu/config/endpoint.rb +5 -4
  70. data/modules/mu/config/firewall_rule.rb +103 -4
  71. data/modules/mu/config/folder.rb +4 -4
  72. data/modules/mu/config/function.rb +19 -10
  73. data/modules/mu/config/group.rb +4 -4
  74. data/modules/mu/config/habitat.rb +4 -4
  75. data/modules/mu/config/job.rb +89 -0
  76. data/modules/mu/config/loadbalancer.rb +60 -14
  77. data/modules/mu/config/log.rb +4 -4
  78. data/modules/mu/config/msg_queue.rb +4 -4
  79. data/modules/mu/config/nosqldb.rb +4 -4
  80. data/modules/mu/config/notifier.rb +10 -21
  81. data/modules/mu/config/ref.rb +411 -0
  82. data/modules/mu/config/role.rb +4 -4
  83. data/modules/mu/config/schema_helpers.rb +509 -0
  84. data/modules/mu/config/search_domain.rb +4 -4
  85. data/modules/mu/config/server.rb +98 -71
  86. data/modules/mu/config/server.yml +1 -0
  87. data/modules/mu/config/server_pool.rb +5 -9
  88. data/modules/mu/config/storage_pool.rb +1 -1
  89. data/modules/mu/config/tail.rb +200 -0
  90. data/modules/mu/config/user.rb +4 -4
  91. data/modules/mu/config/vpc.rb +71 -27
  92. data/modules/mu/config/vpc.yml +0 -1
  93. data/modules/mu/defaults/AWS.yaml +91 -68
  94. data/modules/mu/defaults/Azure.yaml +1 -0
  95. data/modules/mu/defaults/Google.yaml +3 -2
  96. data/modules/mu/deploy.rb +43 -26
  97. data/modules/mu/groomer.rb +17 -2
  98. data/modules/mu/groomers/ansible.rb +188 -41
  99. data/modules/mu/groomers/chef.rb +116 -55
  100. data/modules/mu/logger.rb +127 -148
  101. data/modules/mu/master.rb +410 -2
  102. data/modules/mu/master/chef.rb +3 -4
  103. data/modules/mu/master/ldap.rb +3 -3
  104. data/modules/mu/master/ssl.rb +12 -3
  105. data/modules/mu/mommacat.rb +218 -2612
  106. data/modules/mu/mommacat/daemon.rb +403 -0
  107. data/modules/mu/mommacat/naming.rb +473 -0
  108. data/modules/mu/mommacat/search.rb +495 -0
  109. data/modules/mu/mommacat/storage.rb +722 -0
  110. data/modules/mu/{clouds → providers}/README.md +1 -1
  111. data/modules/mu/{clouds → providers}/aws.rb +380 -122
  112. data/modules/mu/{clouds → providers}/aws/alarm.rb +7 -5
  113. data/modules/mu/{clouds → providers}/aws/bucket.rb +297 -59
  114. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +37 -71
  115. data/modules/mu/providers/aws/cdn.rb +782 -0
  116. data/modules/mu/{clouds → providers}/aws/collection.rb +26 -25
  117. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +724 -744
  118. data/modules/mu/providers/aws/database.rb +1744 -0
  119. data/modules/mu/{clouds → providers}/aws/dnszone.rb +88 -70
  120. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  121. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +220 -247
  122. data/modules/mu/{clouds → providers}/aws/folder.rb +8 -8
  123. data/modules/mu/{clouds → providers}/aws/function.rb +300 -142
  124. data/modules/mu/{clouds → providers}/aws/group.rb +31 -29
  125. data/modules/mu/{clouds → providers}/aws/habitat.rb +18 -15
  126. data/modules/mu/providers/aws/job.rb +466 -0
  127. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +66 -56
  128. data/modules/mu/{clouds → providers}/aws/log.rb +17 -14
  129. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +29 -19
  130. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +114 -16
  131. data/modules/mu/{clouds → providers}/aws/notifier.rb +142 -65
  132. data/modules/mu/{clouds → providers}/aws/role.rb +158 -118
  133. data/modules/mu/{clouds → providers}/aws/search_domain.rb +201 -59
  134. data/modules/mu/{clouds → providers}/aws/server.rb +844 -1139
  135. data/modules/mu/{clouds → providers}/aws/server_pool.rb +74 -65
  136. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +26 -44
  137. data/modules/mu/{clouds → providers}/aws/user.rb +24 -25
  138. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  139. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  140. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
  141. data/modules/mu/{clouds → providers}/aws/vpc.rb +525 -931
  142. data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
  143. data/modules/mu/{clouds → providers}/azure.rb +29 -9
  144. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +3 -8
  145. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +18 -11
  146. data/modules/mu/{clouds → providers}/azure/habitat.rb +8 -6
  147. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +5 -5
  148. data/modules/mu/{clouds → providers}/azure/role.rb +8 -10
  149. data/modules/mu/{clouds → providers}/azure/server.rb +97 -49
  150. data/modules/mu/{clouds → providers}/azure/user.rb +6 -8
  151. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  152. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  153. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  154. data/modules/mu/{clouds → providers}/azure/vpc.rb +16 -21
  155. data/modules/mu/{clouds → providers}/cloudformation.rb +18 -7
  156. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  157. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  158. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  159. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  160. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  161. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  162. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  163. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  164. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  165. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  166. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +5 -7
  167. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  168. data/modules/mu/{clouds → providers}/google.rb +68 -30
  169. data/modules/mu/{clouds → providers}/google/bucket.rb +13 -15
  170. data/modules/mu/{clouds → providers}/google/container_cluster.rb +85 -78
  171. data/modules/mu/{clouds → providers}/google/database.rb +11 -21
  172. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +15 -14
  173. data/modules/mu/{clouds → providers}/google/folder.rb +20 -17
  174. data/modules/mu/{clouds → providers}/google/function.rb +140 -168
  175. data/modules/mu/{clouds → providers}/google/group.rb +29 -34
  176. data/modules/mu/{clouds → providers}/google/habitat.rb +21 -22
  177. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +19 -21
  178. data/modules/mu/{clouds → providers}/google/role.rb +94 -58
  179. data/modules/mu/{clouds → providers}/google/server.rb +243 -156
  180. data/modules/mu/{clouds → providers}/google/server_pool.rb +26 -45
  181. data/modules/mu/{clouds → providers}/google/user.rb +95 -31
  182. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  183. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  184. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  185. data/modules/mu/{clouds → providers}/google/vpc.rb +103 -79
  186. data/modules/tests/aws-jobs-functions.yaml +46 -0
  187. data/modules/tests/bucket.yml +4 -0
  188. data/modules/tests/centos6.yaml +15 -0
  189. data/modules/tests/centos7.yaml +15 -0
  190. data/modules/tests/centos8.yaml +12 -0
  191. data/modules/tests/ecs.yaml +23 -0
  192. data/modules/tests/eks.yaml +1 -1
  193. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  194. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  195. data/modules/tests/includes-and-params.yaml +2 -1
  196. data/modules/tests/microservice_app.yaml +288 -0
  197. data/modules/tests/rds.yaml +108 -0
  198. data/modules/tests/regrooms/aws-iam.yaml +201 -0
  199. data/modules/tests/regrooms/bucket.yml +19 -0
  200. data/modules/tests/regrooms/rds.yaml +123 -0
  201. data/modules/tests/server-with-scrub-muisms.yaml +2 -1
  202. data/modules/tests/super_complex_bok.yml +2 -2
  203. data/modules/tests/super_simple_bok.yml +3 -5
  204. data/modules/tests/win2k12.yaml +17 -5
  205. data/modules/tests/win2k16.yaml +25 -0
  206. data/modules/tests/win2k19.yaml +25 -0
  207. data/requirements.txt +1 -0
  208. data/spec/mu/clouds/azure_spec.rb +2 -2
  209. metadata +240 -154
  210. data/extras/image-generators/AWS/windows.yaml +0 -18
  211. data/modules/mu/clouds/aws/database.rb +0 -1985
  212. data/modules/mu/clouds/aws/endpoint.rb +0 -592
@@ -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
@@ -123,6 +145,8 @@ module MU
123
145
  MU.log "Failed to locate a folder that resembles #{@parent}", MU::ERR
124
146
  end
125
147
  MU.log "Scraping complete"
148
+
149
+ @scraped
126
150
  end
127
151
 
128
152
  # Given a list of BoK style tags, try to reverse-engineer the correct
@@ -130,8 +154,9 @@ module MU
130
154
  # this infers from Mu-style tagging, but we'll add a couple cases for
131
155
  # special cloud provider cases.
132
156
  # @param tags [Array<Hash>]
157
+ # @param basename [String]
133
158
  # return [String]
134
- def self.tagsToName(tags = [])
159
+ def self.tagsToName(tags = [], basename: nil)
135
160
  tags.each { |tag|
136
161
  if tag['key'] == "aws:cloudformation:logical-id"
137
162
  return tag['value']
@@ -144,6 +169,7 @@ module MU
144
169
  break
145
170
  end
146
171
  }
172
+
147
173
  tags.each { |tag|
148
174
  if tag['key'] == "Name"
149
175
  if muid and tag['value'].match(/^#{Regexp.quote(muid)}-(.*)/)
@@ -153,6 +179,11 @@ module MU
153
179
  end
154
180
  end
155
181
  }
182
+
183
+ if basename and muid and basename.match(/^#{Regexp.quote(muid)}-(.*)/)
184
+ return Regexp.last_match[1].downcase
185
+ end
186
+
156
187
  nil
157
188
  end
158
189
 
@@ -179,53 +210,98 @@ module MU
179
210
  prefix = "mu" if prefix.empty? # so that appnames aren't ever empty
180
211
  end
181
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 = {}
182
219
  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
220
  allowed_types = @types.map { |t| MU::Cloud.resource_types[t][:cfg_plural] }
190
221
  next if (types & allowed_types).size == 0
191
222
  origin = {
192
- "appname" => bok['appname'],
223
+ "appname" => prefix+appname,
193
224
  "types" => (types & allowed_types).sort,
194
225
  "habitats" => @habitats.sort,
195
226
  "group_by" => @group_by.to_s
196
227
  }
197
228
 
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
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
202
259
  end
203
260
 
204
261
  threads = []
262
+ timers = {}
263
+ walltimers = {}
205
264
  @clouds.each { |cloud|
206
265
  @scraped.each_pair { |type, resources|
266
+ typestart = Time.now
207
267
  res_class = begin
208
- MU::Cloud.loadCloudType(cloud, type)
209
- rescue MU::Cloud::MuCloudResourceNotImplemented => e
268
+ MU::Cloud.resourceClass(cloud, type)
269
+ rescue MU::Cloud::MuCloudResourceNotImplemented
210
270
  # XXX I don't think this can actually happen
211
271
  next
212
272
  end
213
273
  next if !types.include?(res_class.cfg_plural)
214
274
 
215
275
  bok[res_class.cfg_plural] ||= []
276
+ timers[type] ||= {}
216
277
 
217
278
  class_semaphore = Mutex.new
218
279
 
219
280
  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|
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
292
+ threads << Thread.new(obj_thr) { |obj|
293
+ start = Time.now
222
294
 
223
- kitten_cfg = obj.toKitten(rootparent: @default_parent, billing: @billing, habitats: @habitats)
224
- 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']))
225
297
  print "."
226
298
  kitten_cfg.delete("credentials") if @target_creds
227
299
  class_semaphore.synchronize {
228
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)
229
305
  }
230
306
  count += 1
231
307
  end
@@ -236,6 +312,7 @@ module MU
236
312
  threads.each { |t|
237
313
  t.join
238
314
  }
315
+
239
316
  puts ""
240
317
  bok[res_class.cfg_plural].sort! { |a, b|
241
318
  strs = [a, b].map { |x|
@@ -257,49 +334,68 @@ module MU
257
334
  bok[res_class.cfg_plural].each { |sibling|
258
335
  next if kitten_cfg == sibling
259
336
  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
337
+ MU::Adoption.deDuplicateName(kitten_cfg, res_class)
272
338
  MU.log "De-duplication: Renamed #{res_class.cfg_name} name '#{sibling['name']}' => '#{kitten_cfg['name']}'", MU::NOTICE
273
339
  break
274
340
  end
275
341
  }
276
342
  }
343
+ walltimers[type] ||= 0
344
+ walltimers[type] += (Time.now - typestart)
277
345
  }
278
346
  }
279
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])
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
361
+ }
362
+
280
363
  # No matching resources isn't necessarily an error
281
364
  next if count == 0 or bok.nil?
282
365
 
283
366
  # Now walk through all of the Refs in these objects, resolve them, and minimize
284
367
  # their config footprint
285
368
  MU.log "Minimizing footprint of #{count.to_s} found resources", MU::DEBUG
286
- @boks[bok['appname']] = vacuum(bok, origin: origin, save: @savedeploys)
287
369
 
288
- 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]
289
374
  MU.log "diff flag set, but no comparable deploy provided for #{bok['appname']}", MU::ERR
290
375
  exit 1
291
376
  end
292
377
 
293
- if deploy and @diff
294
- 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)
295
381
  if !prevcfg
296
- 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
297
383
  exit 1
298
384
  end
299
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
300
398
 
301
- prevcfg.diff(newcfg)
302
- exit
303
399
  end
304
400
  }
305
401
  @boks
@@ -307,14 +403,191 @@ module MU
307
403
 
308
404
  private
309
405
 
310
- def scrubSchemaDefaults(conf_chunk, schema_chunk, depth = 0, siblings = nil, type: nil)
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
+
583
+ def scrubSchemaDefaults(conf_chunk, schema_chunk, depth = 0, type: nil)
311
584
  return if schema_chunk.nil?
312
585
 
313
586
  if !conf_chunk.nil? and schema_chunk["properties"].kind_of?(Hash) and conf_chunk.is_a?(Hash)
314
587
  deletia = []
315
588
  schema_chunk["properties"].each_pair { |key, subschema|
316
589
  next if !conf_chunk[key]
317
- shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(key)
590
+ shortclass, _cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(key, false)
318
591
 
319
592
  if subschema["default_if"]
320
593
  subschema["default_if"].each { |cond|
@@ -328,7 +601,7 @@ module MU
328
601
  if subschema["default"] and conf_chunk[key] == subschema["default"]
329
602
  deletia << key
330
603
  elsif ["array", "object"].include?(subschema["type"])
331
- scrubSchemaDefaults(conf_chunk[key], subschema, depth+1, conf_chunk, type: shortclass)
604
+ scrubSchemaDefaults(conf_chunk[key], subschema, depth+1, type: shortclass)
332
605
  end
333
606
  }
334
607
  deletia.each { |key| conf_chunk.delete(key) }
@@ -338,8 +611,7 @@ module MU
338
611
  # theory
339
612
  realschema = if type and schema_chunk["items"] and schema_chunk["items"]["properties"] and item["cloud"] and MU::Cloud.supportedClouds.include?(item['cloud'])
340
613
 
341
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(item["cloud"]).const_get(type)
342
- toplevel_required, cloudschema = cloudclass.schema(self)
614
+ _toplevel_required, cloudschema = MU::Cloud.resourceClass(item['cloud'], type).schema(self)
343
615
 
344
616
  newschema = schema_chunk["items"].dup
345
617
  newschema["properties"].merge!(cloudschema)
@@ -349,7 +621,7 @@ module MU
349
621
  end
350
622
  next if ["array", "object"].include?(realschema["type"])
351
623
 
352
- scrubSchemaDefaults(item, realschema, depth+1, conf_chunk, type: type)
624
+ scrubSchemaDefaults(item, realschema, depth+1, type: type)
353
625
  }
354
626
  end
355
627
 
@@ -363,8 +635,7 @@ module MU
363
635
  # Do the same for our main objects: if they all use the same credentials,
364
636
  # for example, remove the explicit +credentials+ attributes and set that
365
637
  # value globally, once.
366
- def vacuum(bok, origin: nil, save: false, deploy: nil)
367
- deploy ||= generateStubDeploy(bok)
638
+ def vacuum(bok, origin: nil, save: false, deploy: nil, copy_from: nil, keep_missing: false)
368
639
 
369
640
  globals = {
370
641
  'cloud' => {},
@@ -373,10 +644,7 @@ module MU
373
644
  'billing_acct' => {},
374
645
  'us_only' => {},
375
646
  }
376
- clouds = {}
377
- credentials = {}
378
- regions = {}
379
- MU::Cloud.resource_types.each_pair { |typename, attrs|
647
+ MU::Cloud.resource_types.values.each { |attrs|
380
648
  if bok[attrs[:cfg_plural]]
381
649
  processed = []
382
650
  bok[attrs[:cfg_plural]].each { |resource|
@@ -387,11 +655,24 @@ module MU
387
655
  end
388
656
  }
389
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
+
390
667
  begin
391
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
392
673
  new_cfg = resolveReferences(resource, deploy, obj)
393
674
  new_cfg.delete("cloud_id")
394
- cred_cfg = MU::Cloud.const_get(obj.cloud).credConfig(obj.credentials)
675
+ cred_cfg = MU::Cloud.cloudClass(obj.cloud).credConfig(obj.credentials)
395
676
  if cred_cfg['region'] == new_cfg['region']
396
677
  new_cfg.delete('region')
397
678
  end
@@ -401,6 +682,11 @@ module MU
401
682
  end
402
683
  processed << new_cfg
403
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
404
690
  end
405
691
  }
406
692
 
@@ -411,34 +697,33 @@ module MU
411
697
 
412
698
  # Pare out global values like +cloud+ or +region+ that appear to be
413
699
  # universal in the deploy we're creating.
414
- def scrub_globals(h, field)
700
+ scrub_globals = Proc.new { |h, field|
415
701
  if h.is_a?(Hash)
416
702
  newhash = {}
417
703
  h.each_pair { |k, v|
418
704
  next if k == field
419
- newhash[k] = scrub_globals(v, field)
705
+ newhash[k] = scrub_globals.call(v, field)
420
706
  }
421
707
  h = newhash
422
708
  elsif h.is_a?(Array)
423
709
  newarr = []
424
710
  h.each { |v|
425
- newarr << scrub_globals(v, field)
711
+ newarr << scrub_globals.call(v, field)
426
712
  }
427
- h = newarr
713
+ h = newarr.uniq
428
714
  end
429
-
430
715
  h
431
- end
716
+ }
432
717
 
433
718
  globals.each_pair { |field, counts|
434
719
  next if counts.size != 1
435
720
  bok[field] = counts.keys.first
436
721
  MU.log "Setting global default #{field} to #{bok[field]} (#{deploy.deploy_id})", MU::DEBUG
437
- MU::Cloud.resource_types.each_pair { |typename, attrs|
722
+ MU::Cloud.resource_types.values.each { |attrs|
438
723
  if bok[attrs[:cfg_plural]]
439
724
  new_resources = []
440
725
  bok[attrs[:cfg_plural]].each { |resource|
441
- new_resources << scrub_globals(resource, field)
726
+ new_resources << scrub_globals.call(resource, field)
442
727
  }
443
728
  bok[attrs[:cfg_plural]] = new_resources
444
729
  end
@@ -456,9 +741,33 @@ module MU
456
741
  end
457
742
 
458
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
+
459
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
+
460
768
  hashcfg = cfg.to_h
461
- if cfg.kitten(deploy)
769
+
770
+ if cfg.kitten
462
771
  littermate = deploy.findLitterMate(type: cfg.type, name: cfg.name, cloud_id: cfg.id, habitat: cfg.habitat)
463
772
 
464
773
  if littermate and littermate.config['name']
@@ -480,16 +789,15 @@ module MU
480
789
  hashcfg.delete("name") if cfg.id and !cfg.deploy_id
481
790
  end
482
791
  end
483
- elsif hashcfg["id"] # reference to raw cloud ids is reasonable
792
+ elsif hashcfg["id"] and !hashcfg["name"]
484
793
  hashcfg.delete("deploy_id")
485
- hashcfg.delete("name")
486
794
  else
487
- pp parent.cloud_desc
488
- raise Incomplete, "Failed to resolve reference on behalf of #{parent}"
795
+ raise Incomplete.new "Failed to resolve reference on behalf of #{parent}", details: hashcfg
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.