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
@@ -24,11 +24,6 @@ module MU
24
24
  class BootstrapTempFail < MuNonFatal;
25
25
  end
26
26
 
27
- # An exception we can use with transient Net::SSH errors, which require
28
- # special handling due to obnoxious asynchronous interrupt behaviors.
29
- class NetSSHFail < MuNonFatal;
30
- end
31
-
32
27
  # Exception thrown when a request is made to an unimplemented cloud
33
28
  # resource.
34
29
  class MuCloudResourceNotImplemented < StandardError;
@@ -45,11 +40,11 @@ module MU
45
40
  end
46
41
 
47
42
  # Methods which a cloud resource implementation, e.g. Server, must implement
48
- generic_class_methods = [:find, :cleanup, :validateConfig, :schema, :isGlobal?]
49
- generic_instance_methods = [:create, :notify, :mu_name, :cloud_id, :config]
43
+ @@generic_class_methods = [:find, :cleanup, :validateConfig, :schema, :isGlobal?]
44
+ @@generic_instance_methods = [:create, :notify, :mu_name, :cloud_id, :config]
50
45
 
51
46
  # Class methods which the base of a cloud implementation must implement
52
- generic_class_methods_toplevel = [:required_instance_methods, :myRegion, :listRegions, :listAZs, :hosted?, :hosted_config, :config_example, :writeDeploySecret, :listCredentials, :credConfig, :listInstanceTypes, :adminBucketName, :adminBucketUrl, :habitat]
47
+ @@generic_class_methods_toplevel = [:required_instance_methods, :myRegion, :listRegions, :listAZs, :hosted?, :hosted_config, :config_example, :writeDeploySecret, :listCredentials, :credConfig, :listInstanceTypes, :adminBucketName, :adminBucketUrl, :listHabitats, :habitat, :virtual?]
53
48
 
54
49
  # Public attributes which will be available on all instantiated cloud resource objects
55
50
  #
@@ -178,8 +173,8 @@ module MU
178
173
  :interface => self.const_get("Folder"),
179
174
  :deps_wait_on_my_creation => true,
180
175
  :waits_on_parent_completion => true,
181
- :class => generic_class_methods,
182
- :instance => generic_instance_methods
176
+ :class => @@generic_class_methods,
177
+ :instance => @@generic_instance_methods
183
178
  },
184
179
  :Habitat => {
185
180
  :has_multiples => false,
@@ -189,8 +184,8 @@ module MU
189
184
  :interface => self.const_get("Habitat"),
190
185
  :deps_wait_on_my_creation => true,
191
186
  :waits_on_parent_completion => true,
192
- :class => generic_class_methods + [:isLive?],
193
- :instance => generic_instance_methods + [:groom]
187
+ :class => @@generic_class_methods + [:isLive?],
188
+ :instance => @@generic_instance_methods + [:groom]
194
189
  },
195
190
  :Collection => {
196
191
  :has_multiples => false,
@@ -200,8 +195,8 @@ module MU
200
195
  :interface => self.const_get("Collection"),
201
196
  :deps_wait_on_my_creation => true,
202
197
  :waits_on_parent_completion => false,
203
- :class => generic_class_methods,
204
- :instance => generic_instance_methods
198
+ :class => @@generic_class_methods,
199
+ :instance => @@generic_instance_methods
205
200
  },
206
201
  :Database => {
207
202
  :has_multiples => true,
@@ -211,8 +206,8 @@ module MU
211
206
  :interface => self.const_get("Database"),
212
207
  :deps_wait_on_my_creation => true,
213
208
  :waits_on_parent_completion => false,
214
- :class => generic_class_methods,
215
- :instance => generic_instance_methods + [:groom, :allowHost]
209
+ :class => @@generic_class_methods,
210
+ :instance => @@generic_instance_methods + [:groom, :allowHost]
216
211
  },
217
212
  :DNSZone => {
218
213
  :has_multiples => false,
@@ -222,8 +217,8 @@ module MU
222
217
  :interface => self.const_get("DNSZone"),
223
218
  :deps_wait_on_my_creation => true,
224
219
  :waits_on_parent_completion => true,
225
- :class => generic_class_methods + [:genericMuDNSEntry, :createRecordsFromConfig],
226
- :instance => generic_instance_methods
220
+ :class => @@generic_class_methods + [:genericMuDNSEntry, :createRecordsFromConfig],
221
+ :instance => @@generic_instance_methods
227
222
  },
228
223
  :FirewallRule => {
229
224
  :has_multiples => false,
@@ -233,8 +228,8 @@ module MU
233
228
  :interface => self.const_get("FirewallRule"),
234
229
  :deps_wait_on_my_creation => true,
235
230
  :waits_on_parent_completion => false,
236
- :class => generic_class_methods,
237
- :instance => generic_instance_methods + [:groom, :addRule]
231
+ :class => @@generic_class_methods,
232
+ :instance => @@generic_instance_methods + [:groom, :addRule]
238
233
  },
239
234
  :LoadBalancer => {
240
235
  :has_multiples => false,
@@ -244,8 +239,8 @@ module MU
244
239
  :interface => self.const_get("LoadBalancer"),
245
240
  :deps_wait_on_my_creation => true,
246
241
  :waits_on_parent_completion => false,
247
- :class => generic_class_methods,
248
- :instance => generic_instance_methods + [:groom, :registerNode]
242
+ :class => @@generic_class_methods,
243
+ :instance => @@generic_instance_methods + [:groom, :registerNode]
249
244
  },
250
245
  :Server => {
251
246
  :has_multiples => true,
@@ -255,8 +250,8 @@ module MU
255
250
  :interface => self.const_get("Server"),
256
251
  :deps_wait_on_my_creation => false,
257
252
  :waits_on_parent_completion => false,
258
- :class => generic_class_methods + [:validateInstanceType, :imageTimeStamp],
259
- :instance => generic_instance_methods + [:groom, :postBoot, :getSSHConfig, :canonicalIP, :getWindowsAdminPassword, :active?, :groomer, :mu_windows_name, :mu_windows_name=, :reboot, :addVolume, :genericNAT]
253
+ :class => @@generic_class_methods + [:validateInstanceType, :imageTimeStamp],
254
+ :instance => @@generic_instance_methods + [:groom, :postBoot, :getSSHConfig, :canonicalIP, :getWindowsAdminPassword, :active?, :groomer, :mu_windows_name, :mu_windows_name=, :reboot, :addVolume, :genericNAT, :listIPs]
260
255
  },
261
256
  :ServerPool => {
262
257
  :has_multiples => false,
@@ -266,8 +261,8 @@ module MU
266
261
  :interface => self.const_get("ServerPool"),
267
262
  :deps_wait_on_my_creation => false,
268
263
  :waits_on_parent_completion => true,
269
- :class => generic_class_methods,
270
- :instance => generic_instance_methods + [:groom, :listNodes]
264
+ :class => @@generic_class_methods,
265
+ :instance => @@generic_instance_methods + [:groom, :listNodes]
271
266
  },
272
267
  :VPC => {
273
268
  :has_multiples => false,
@@ -277,8 +272,8 @@ module MU
277
272
  :interface => self.const_get("VPC"),
278
273
  :deps_wait_on_my_creation => true,
279
274
  :waits_on_parent_completion => false,
280
- :class => generic_class_methods,
281
- :instance => generic_instance_methods + [:groom, :subnets, :getSubnet, :listSubnets, :findBastion, :findNat]
275
+ :class => @@generic_class_methods,
276
+ :instance => @@generic_instance_methods + [:groom, :subnets, :getSubnet, :findBastion, :findNat]
282
277
  },
283
278
  :CacheCluster => {
284
279
  :has_multiples => true,
@@ -288,8 +283,8 @@ module MU
288
283
  :interface => self.const_get("CacheCluster"),
289
284
  :deps_wait_on_my_creation => true,
290
285
  :waits_on_parent_completion => false,
291
- :class => generic_class_methods,
292
- :instance => generic_instance_methods + [:groom]
286
+ :class => @@generic_class_methods,
287
+ :instance => @@generic_instance_methods + [:groom]
293
288
  },
294
289
  :Alarm => {
295
290
  :has_multiples => false,
@@ -299,8 +294,8 @@ module MU
299
294
  :interface => self.const_get("Alarm"),
300
295
  :deps_wait_on_my_creation => false,
301
296
  :waits_on_parent_completion => true,
302
- :class => generic_class_methods,
303
- :instance => generic_instance_methods + [:groom]
297
+ :class => @@generic_class_methods,
298
+ :instance => @@generic_instance_methods + [:groom]
304
299
  },
305
300
  :Notifier => {
306
301
  :has_multiples => false,
@@ -310,8 +305,8 @@ module MU
310
305
  :interface => self.const_get("Notifier"),
311
306
  :deps_wait_on_my_creation => false,
312
307
  :waits_on_parent_completion => false,
313
- :class => generic_class_methods,
314
- :instance => generic_instance_methods + [:groom]
308
+ :class => @@generic_class_methods,
309
+ :instance => @@generic_instance_methods + [:groom]
315
310
  },
316
311
  :Log => {
317
312
  :has_multiples => false,
@@ -321,8 +316,8 @@ module MU
321
316
  :interface => self.const_get("Log"),
322
317
  :deps_wait_on_my_creation => true,
323
318
  :waits_on_parent_completion => true,
324
- :class => generic_class_methods,
325
- :instance => generic_instance_methods + [:groom]
319
+ :class => @@generic_class_methods,
320
+ :instance => @@generic_instance_methods + [:groom]
326
321
  },
327
322
  :StoragePool => {
328
323
  :has_multiples => false,
@@ -332,8 +327,8 @@ module MU
332
327
  :interface => self.const_get("StoragePool"),
333
328
  :deps_wait_on_my_creation => true,
334
329
  :waits_on_parent_completion => false,
335
- :class => generic_class_methods,
336
- :instance => generic_instance_methods + [:groom]
330
+ :class => @@generic_class_methods,
331
+ :instance => @@generic_instance_methods + [:groom]
337
332
  },
338
333
  :Function => {
339
334
  :has_multiples => false,
@@ -343,8 +338,8 @@ module MU
343
338
  :interface => self.const_get("Function"),
344
339
  :deps_wait_on_my_creation => true,
345
340
  :waits_on_parent_completion => false,
346
- :class => generic_class_methods,
347
- :instance => generic_instance_methods + [:groom]
341
+ :class => @@generic_class_methods,
342
+ :instance => @@generic_instance_methods + [:groom]
348
343
  },
349
344
  :Endpoint => {
350
345
  :has_multiples => false,
@@ -354,8 +349,8 @@ module MU
354
349
  :interface => self.const_get("Endpoint"),
355
350
  :deps_wait_on_my_creation => true,
356
351
  :waits_on_parent_completion => false,
357
- :class => generic_class_methods,
358
- :instance => generic_instance_methods + [:groom]
352
+ :class => @@generic_class_methods,
353
+ :instance => @@generic_instance_methods + [:groom]
359
354
  },
360
355
  :ContainerCluster => {
361
356
  :has_multiples => false,
@@ -365,8 +360,8 @@ module MU
365
360
  :interface => self.const_get("ContainerCluster"),
366
361
  :deps_wait_on_my_creation => true,
367
362
  :waits_on_parent_completion => false,
368
- :class => generic_class_methods,
369
- :instance => generic_instance_methods + [:groom]
363
+ :class => @@generic_class_methods,
364
+ :instance => @@generic_instance_methods + [:groom]
370
365
  },
371
366
  :SearchDomain => {
372
367
  :has_multiples => false,
@@ -376,8 +371,8 @@ module MU
376
371
  :interface => self.const_get("SearchDomain"),
377
372
  :deps_wait_on_my_creation => true,
378
373
  :waits_on_parent_completion => false,
379
- :class => generic_class_methods,
380
- :instance => generic_instance_methods + [:groom]
374
+ :class => @@generic_class_methods,
375
+ :instance => @@generic_instance_methods + [:groom]
381
376
  },
382
377
  :MsgQueue => {
383
378
  :has_multiples => false,
@@ -387,8 +382,8 @@ module MU
387
382
  :interface => self.const_get("MsgQueue"),
388
383
  :deps_wait_on_my_creation => true,
389
384
  :waits_on_parent_completion => true,
390
- :class => generic_class_methods,
391
- :instance => generic_instance_methods + [:groom]
385
+ :class => @@generic_class_methods,
386
+ :instance => @@generic_instance_methods + [:groom]
392
387
  },
393
388
  :User => {
394
389
  :has_multiples => false,
@@ -398,8 +393,8 @@ module MU
398
393
  :interface => self.const_get("User"),
399
394
  :deps_wait_on_my_creation => true,
400
395
  :waits_on_parent_completion => true,
401
- :class => generic_class_methods,
402
- :instance => generic_instance_methods + [:groom]
396
+ :class => @@generic_class_methods,
397
+ :instance => @@generic_instance_methods + [:groom]
403
398
  },
404
399
  :Group => {
405
400
  :has_multiples => false,
@@ -409,8 +404,8 @@ module MU
409
404
  :interface => self.const_get("Group"),
410
405
  :deps_wait_on_my_creation => true,
411
406
  :waits_on_parent_completion => true,
412
- :class => generic_class_methods,
413
- :instance => generic_instance_methods + [:groom]
407
+ :class => @@generic_class_methods,
408
+ :instance => @@generic_instance_methods + [:groom]
414
409
  },
415
410
  :Role => {
416
411
  :has_multiples => false,
@@ -420,8 +415,8 @@ module MU
420
415
  :interface => self.const_get("Role"),
421
416
  :deps_wait_on_my_creation => true,
422
417
  :waits_on_parent_completion => true,
423
- :class => generic_class_methods,
424
- :instance => generic_instance_methods + [:groom]
418
+ :class => @@generic_class_methods,
419
+ :instance => @@generic_instance_methods + [:groom]
425
420
  },
426
421
  :Bucket => {
427
422
  :has_multiples => false,
@@ -431,8 +426,8 @@ module MU
431
426
  :interface => self.const_get("Bucket"),
432
427
  :deps_wait_on_my_creation => true,
433
428
  :waits_on_parent_completion => true,
434
- :class => generic_class_methods + [:upload],
435
- :instance => generic_instance_methods + [:groom, :upload]
429
+ :class => @@generic_class_methods + [:upload],
430
+ :instance => @@generic_instance_methods + [:groom, :upload]
436
431
  },
437
432
  :NoSQLDB => {
438
433
  :has_multiples => false,
@@ -442,201 +437,11 @@ module MU
442
437
  :interface => self.const_get("NoSQLDB"),
443
438
  :deps_wait_on_my_creation => true,
444
439
  :waits_on_parent_completion => true,
445
- :class => generic_class_methods,
446
- :instance => generic_instance_methods + [:groom]
440
+ :class => @@generic_class_methods,
441
+ :instance => @@generic_instance_methods + [:groom]
447
442
  }
448
443
  }.freeze
449
444
 
450
- # The public AWS S3 bucket where we expect to find YAML files listing our
451
- # standard base images for various platforms.
452
- BASE_IMAGE_BUCKET = "cloudamatic"
453
- # The path in the AWS S3 bucket where we expect to find YAML files listing
454
- # our standard base images for various platforms.
455
- BASE_IMAGE_PATH = "/images"
456
-
457
- # Aliases for platform names, in case we don't have actual images built for
458
- # them.
459
- PLATFORM_ALIASES = {
460
- "linux" => "centos7",
461
- "windows" => "win2k12r2",
462
- "win2k12" => "win2k12r2",
463
- "ubuntu" => "ubuntu16",
464
- "centos" => "centos7",
465
- "rhel7" => "rhel71",
466
- "rhel" => "rhel71",
467
- "amazon" => "amazon2016"
468
- }
469
-
470
- @@image_fetch_cache = {}
471
- @@platform_cache = []
472
- @@image_fetch_semaphore = Mutex.new
473
-
474
- # Rifle our image lists from {MU::Cloud.getStockImage} and return a list
475
- # of valid +platform+ names.
476
- # @return [Array<String>]
477
- def self.listPlatforms
478
- return @@platform_cache if @@platform_cache and !@@platform_cache.empty?
479
- @@platform_cache = MU::Cloud.supportedClouds.map { |cloud|
480
- begin
481
- loadCloudType(cloud, :Server)
482
- rescue MU::Cloud::MuCloudResourceNotImplemented, MU::MuError => e
483
- next
484
- end
485
-
486
- images = MU::Cloud.getStockImage(cloud, quiet: true)
487
- if images
488
- images.keys
489
- else
490
- nil
491
- end
492
- }.flatten.uniq
493
- @@platform_cache.delete(nil)
494
- @@platform_cache.sort
495
- @@platform_cache
496
- end
497
-
498
- # Locate a base image for a {MU::Cloud::Server} resource. First we check
499
- # Mu's public bucket, which should list the latest and greatest. If we can't
500
- # fetch that, then we fall back to a YAML file that's bundled as part of Mu,
501
- # but which will typically be less up-to-date.
502
- # @param cloud [String]: The cloud provider for which to return an image list
503
- # @param platform [String]: The supported platform for which to return an image or images. If not specified, we'll return our entire library for the appropriate cloud provider.
504
- # @param region [String]: The region for which the returned image or images should be supported, for cloud providers which require it (such as AWS).
505
- # @param fail_hard [Boolean]: Raise an exception on most errors, such as an inability to reach our public listing, lack of matching images, etc.
506
- # @return [Hash,String,nil]
507
- def self.getStockImage(cloud = MU::Config.defaultCloud, platform: nil, region: nil, fail_hard: false, quiet: false)
508
-
509
- if !MU::Cloud.supportedClouds.include?(cloud)
510
- MU.log "'#{cloud}' is not a supported cloud provider! Available providers:", MU::ERR, details: MU::Cloud.supportedClouds
511
- raise MuError, "'#{cloud}' is not a supported cloud provider!"
512
- end
513
-
514
- urls = ["http://"+BASE_IMAGE_BUCKET+".s3-website-us-east-1.amazonaws.com"+BASE_IMAGE_PATH]
515
- if $MU_CFG and $MU_CFG['custom_images_url']
516
- urls << $MU_CFG['custom_images_url']
517
- end
518
-
519
- images = nil
520
- urls.each { |base_url|
521
- @@image_fetch_semaphore.synchronize {
522
- if @@image_fetch_cache[cloud] and (Time.now - @@image_fetch_cache[cloud]['time']) < 30
523
- images = @@image_fetch_cache[cloud]['contents'].dup
524
- else
525
- begin
526
- Timeout.timeout(2) do
527
- response = open("#{base_url}/#{cloud}.yaml").read
528
- images ||= {}
529
- images.deep_merge!(YAML.load(response))
530
- break
531
- end
532
- rescue Exception => e
533
- if fail_hard
534
- raise MuError, "Failed to fetch stock images from #{base_url}/#{cloud}.yaml (#{e.message})"
535
- else
536
- MU.log "Failed to fetch stock images from #{base_url}/#{cloud}.yaml (#{e.message})", MU::WARN if !quiet
537
- end
538
- end
539
- end
540
- }
541
- }
542
-
543
- @@image_fetch_semaphore.synchronize {
544
- @@image_fetch_cache[cloud] = {
545
- 'contents' => images.dup,
546
- 'time' => Time.now
547
- }
548
- }
549
-
550
- backwards_compat = {
551
- "AWS" => "amazon_images",
552
- "Google" => "google_images",
553
- }
554
-
555
- # Load from inside our repository, if we didn't get images elsewise
556
- if images.nil?
557
- [backwards_compat[cloud], cloud].each { |file|
558
- next if file.nil?
559
- if File.exist?("#{MU.myRoot}/modules/mu/defaults/#{file}.yaml")
560
- images = YAML.load(File.read("#{MU.myRoot}/modules/mu/defaults/#{file}.yaml"))
561
- break
562
- end
563
- }
564
- end
565
-
566
- # Now overlay local overrides, both of the systemwide (/opt/mu/etc) and
567
- # per-user (~/.mu/etc) variety.
568
- [backwards_compat[cloud], cloud].each { |file|
569
- next if file.nil?
570
- if File.exist?("#{MU.etcDir}/#{file}.yaml")
571
- images ||= {}
572
- images.deep_merge!(YAML.load(File.read("#{MU.etcDir}/#{file}.yaml")))
573
- end
574
- if Process.uid != 0
575
- basepath = Etc.getpwuid(Process.uid).dir+"/.mu/etc"
576
- if File.exist?("#{basepath}/#{file}.yaml")
577
- images ||= {}
578
- images.deep_merge!(YAML.load(File.read("#{basepath}/#{file}.yaml")))
579
- end
580
- end
581
- }
582
-
583
- if images.nil?
584
- if fail_hard
585
- raise MuError, "Failed to find any base images for #{cloud}"
586
- else
587
- MU.log "Failed to find any base images for #{cloud}", MU::WARN if !quiet
588
- return nil
589
- end
590
- end
591
-
592
- PLATFORM_ALIASES.each_pair { |a, t|
593
- if images[t] and !images[a]
594
- images[a] = images[t]
595
- end
596
- }
597
-
598
- if platform
599
- if !images[platform]
600
- if fail_hard
601
- raise MuError, "No base image for platform #{platform} in cloud #{cloud}"
602
- else
603
- MU.log "No base image for platform #{platform} in cloud #{cloud}", MU::WARN if !quiet
604
- return nil
605
- end
606
- end
607
- images = images[platform]
608
-
609
- if region
610
- # We won't fuss about the region argument if this isn't a cloud that
611
- # has regions, just quietly don't bother.
612
- if images.is_a?(Hash)
613
- if images[region]
614
- images = images[region]
615
- else
616
- if fail_hard
617
- raise MuError, "No base image for platform #{platform} in cloud #{cloud} region #{region} found"
618
- else
619
- MU.log "No base image for platform #{platform} in cloud #{cloud} region #{region} found", MU::WARN if !quiet
620
- return nil
621
- end
622
- end
623
- end
624
- end
625
- else
626
- if region
627
- images.each_pair { |p, regions|
628
- # Filter to match our requested region, but for all the platforms,
629
- # since we didn't specify one.
630
- if regions.is_a?(Hash)
631
- regions.delete_if { |r| r != region }
632
- end
633
- }
634
- end
635
- end
636
-
637
- images
638
- end
639
-
640
445
  # A list of supported cloud resource types as Mu classes
641
446
  def self.resource_types;
642
447
  @@resource_types
@@ -644,80 +449,32 @@ module MU
644
449
 
645
450
  # Shorthand lookup for resource type names. Given any of the shorthand class name, configuration name (singular or plural), or full class name, return all four as a set.
646
451
  # @param type [String]: A string that looks like our short or full class name or singular or plural configuration names.
452
+ # @param assert [Boolean]: Raise an exception if the type isn't valid
647
453
  # @return [Array]: Class name (Symbol), singular config name (String), plural config name (String), full class name (Object)
648
- def self.getResourceNames(type)
649
- return [nil, nil, nil, nil, {}] if !type
454
+ def self.getResourceNames(type, assert = true)
455
+ if !type
456
+ if assert
457
+ raise MuError, "nil resource type requested in getResourceNames"
458
+ else
459
+ return [nil, nil, nil, nil, {}]
460
+ end
461
+ end
650
462
  @@resource_types.each_pair { |name, cloudclass|
651
463
  if name == type.to_sym or
652
464
  cloudclass[:cfg_name] == type or
653
465
  cloudclass[:cfg_plural] == type or
654
- Object.const_get("MU").const_get("Cloud").const_get(name) == type
655
- cfg_name = cloudclass[:cfg_name]
466
+ MU::Cloud.const_get(name) == type
656
467
  type = name
657
- return [type.to_sym, cloudclass[:cfg_name], cloudclass[:cfg_plural], Object.const_get("MU").const_get("Cloud").const_get(name), cloudclass]
658
- end
659
- }
660
- [nil, nil, nil, nil, {}]
661
- end
662
-
663
- # Net::SSH exceptions seem to have their own behavior vis a vis threads,
664
- # and our regular call stack gets circumvented when they're thrown. Cheat
665
- # here to catch them gracefully.
666
- def self.handleNetSSHExceptions
667
- Thread.handle_interrupt(Net::SSH::Exception => :never) {
668
- begin
669
- Thread.handle_interrupt(Net::SSH::Exception => :immediate) {
670
- MU.log "(Probably harmless) Caught a Net::SSH Exception in #{Thread.current.inspect}", MU::DEBUG, details: Thread.current.backtrace
671
- }
672
- ensure
673
- # raise NetSSHFail, "Net::SSH had a nutty"
674
- end
675
- }
676
- end
677
-
678
- # List of known/supported Cloud providers. This may be modified at runtime
679
- # if an implemention is defective or missing required methods.
680
- @@supportedCloudList = ['AWS', 'CloudFormation', 'Google', 'Azure']
681
-
682
- # List of known/supported Cloud providers
683
- # @return [Array<String>]
684
- def self.supportedClouds
685
- @@supportedCloudList
686
- end
687
-
688
- # List of known/supported Cloud providers for which we have at least one
689
- # set of credentials configured.
690
- # @return [Array<String>]
691
- def self.availableClouds
692
- available = []
693
- MU::Cloud.supportedClouds.each { |cloud|
694
- begin
695
- cloudbase = Object.const_get("MU").const_get("Cloud").const_get(cloud)
696
- next if cloudbase.listCredentials.nil? or cloudbase.listCredentials.empty?
697
- available << cloud
698
- rescue NameError
468
+ return [type.to_sym, cloudclass[:cfg_name], cloudclass[:cfg_plural], MU::Cloud.const_get(name), cloudclass]
699
469
  end
700
470
  }
471
+ if assert
472
+ raise MuError, "Invalid resource type #{type} requested in getResourceNames"
473
+ end
701
474
 
702
- available
475
+ [nil, nil, nil, nil, {}]
703
476
  end
704
477
 
705
- # Load the container class for each cloud we know about, and inject autoload
706
- # code for each of its supported resource type classes.
707
- failed = []
708
- MU::Cloud.supportedClouds.each { |cloud|
709
- require "mu/clouds/#{cloud.downcase}"
710
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
711
- generic_class_methods_toplevel.each { |method|
712
- if !cloudclass.respond_to?(method)
713
- MU.log "MU::Cloud::#{cloud} has not implemented required class method #{method}, disabling", MU::ERR
714
- failed << cloud
715
- end
716
- }
717
- }
718
- failed.uniq!
719
- @@supportedCloudList = @@supportedCloudList - failed
720
-
721
478
  # @return [Mutex]
722
479
  def self.userdata_mutex
723
480
  @userdata_mutex ||= Mutex.new
@@ -741,7 +498,7 @@ module MU
741
498
  end
742
499
  template_variables["credentials"] ||= credentials
743
500
  $mu = OpenStruct.new(template_variables)
744
- userdata_dir = File.expand_path(MU.myRoot+"/modules/mu/clouds/#{cloud.downcase}/userdata")
501
+ userdata_dir = File.expand_path(MU.myRoot+"/modules/mu/providers/#{cloud.downcase}/userdata")
745
502
 
746
503
  platform = if %w{win2k12r2 win2k12 win2k8 win2k8r2 win2k16 windows win2k19}.include?(platform)
747
504
  "windows"
@@ -795,14 +552,26 @@ module MU
795
552
  }
796
553
  end
797
554
 
555
+ # Given a resource type, validate that it's legit and return its base class from the {MU::Cloud} module
556
+ # @param type [String]
557
+ # @return [MU::Cloud]
558
+ def self.loadBaseType(type)
559
+ raise MuError, "Argument to MU::Cloud.loadBaseType cannot be nil" if type.nil?
560
+ shortclass, cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(type)
561
+ if !shortclass
562
+ raise MuCloudResourceNotImplemented, "#{type} does not appear to be a valid resource type"
563
+ end
564
+ Object.const_get("MU").const_get("Cloud").const_get(shortclass)
565
+ end
566
+
798
567
  @cloud_class_cache = {}
799
568
  # Given a cloud layer and resource type, return the class which implements it.
800
569
  # @param cloud [String]: The Cloud layer
801
570
  # @param type [String]: The resource type. Can be the full class name, symbolic name, or Basket of Kittens configuration shorthand for the resource type.
802
571
  # @return [Class]: The cloud-specific class implementing this resource
803
- def self.loadCloudType(cloud, type)
804
- raise MuError, "cloud argument to MU::Cloud.loadCloudType cannot be nil" if cloud.nil?
805
- shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
572
+ def self.resourceClass(cloud, type)
573
+ raise MuError, "cloud argument to MU::Cloud.resourceClass cannot be nil" if cloud.nil?
574
+ shortclass, cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(type)
806
575
  if @cloud_class_cache.has_key?(cloud) and @cloud_class_cache[cloud].has_key?(type)
807
576
  if @cloud_class_cache[cloud][type].nil?
808
577
  raise MuError, "The '#{type}' resource is not supported in cloud #{cloud} (tried MU::#{cloud}::#{type})", caller
@@ -813,1349 +582,48 @@ module MU
813
582
  if cfg_name.nil?
814
583
  raise MuError, "Can't find a cloud resource type named '#{type}'"
815
584
  end
816
- if !File.size?(MU.myRoot+"/modules/mu/clouds/#{cloud.downcase}.rb")
585
+ if !File.size?(MU.myRoot+"/modules/mu/providers/#{cloud.downcase}.rb")
817
586
  raise MuError, "Requested to use unsupported provisioning layer #{cloud}"
818
587
  end
819
588
  begin
820
- require "mu/clouds/#{cloud.downcase}/#{cfg_name}"
589
+ require "mu/providers/#{cloud.downcase}/#{cfg_name}"
821
590
  rescue LoadError => e
822
591
  raise MuCloudResourceNotImplemented, "MU::Cloud::#{cloud} does not currently implement #{shortclass}, or implementation does not load correctly (#{e.message})"
823
592
  end
593
+
824
594
  @cloud_class_cache[cloud] = {} if !@cloud_class_cache.has_key?(cloud)
825
595
  begin
826
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
827
- myclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(type)
828
- @@resource_types[type.to_sym][:class].each { |class_method|
596
+ cloudclass = const_get("MU").const_get("Cloud").const_get(cloud)
597
+ myclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(shortclass)
598
+
599
+ @@resource_types[shortclass.to_sym][:class].each { |class_method|
829
600
  if !myclass.respond_to?(class_method) or myclass.method(class_method).owner.to_s != "#<Class:#{myclass}>"
830
- raise MuError, "MU::Cloud::#{cloud}::#{type} has not implemented required class method #{class_method}"
601
+ raise MuError, "MU::Cloud::#{cloud}::#{shortclass} has not implemented required class method #{class_method}"
831
602
  end
832
603
  }
833
- @@resource_types[type.to_sym][:instance].each { |instance_method|
604
+ @@resource_types[shortclass.to_sym][:instance].each { |instance_method|
834
605
  if !myclass.public_instance_methods.include?(instance_method)
835
- raise MuCloudResourceNotImplemented, "MU::Cloud::#{cloud}::#{type} has not implemented required instance method #{instance_method}"
606
+ raise MuCloudResourceNotImplemented, "MU::Cloud::#{cloud}::#{shortclass} has not implemented required instance method #{instance_method}"
836
607
  end
837
608
  }
838
609
  cloudclass.required_instance_methods.each { |instance_method|
839
610
  if !myclass.public_instance_methods.include?(instance_method)
840
- MU.log "MU::Cloud::#{cloud}::#{type} has not implemented required instance method #{instance_method}, will declare as attr_accessor", MU::DEBUG
611
+ MU.log "MU::Cloud::#{cloud}::#{shortclass} has not implemented required instance method #{instance_method}, will declare as attr_accessor", MU::DEBUG
841
612
  end
842
613
  }
843
614
 
844
615
  @cloud_class_cache[cloud][type] = myclass
616
+
845
617
  return myclass
846
618
  rescue NameError => e
847
619
  @cloud_class_cache[cloud][type] = nil
848
- raise MuCloudResourceNotImplemented, "The '#{type}' resource is not supported in cloud #{cloud} (tried MU::#{cloud}::#{type})", e.backtrace
620
+ raise MuCloudResourceNotImplemented, "The '#{type}' resource is not supported in cloud #{cloud} (tried MU::Cloud::#{cloud}::#{shortclass})", e.backtrace
849
621
  end
850
622
  end
851
623
 
852
- MU::Cloud.supportedClouds.each { |cloud|
853
- Object.const_get("MU").const_get("Cloud").const_get(cloud).class_eval {
854
-
855
- # Automatically load supported cloud resource classes when they're
856
- # referenced.
857
- def self.const_missing(symbol)
858
- if MU::Cloud.resource_types.has_key?(symbol.to_sym)
859
- return MU::Cloud.loadCloudType(name.sub(/.*?::([^:]+)$/, '\1'), symbol)
860
- else
861
- raise MuCloudResourceNotImplemented, "No such cloud resource #{name}:#{symbol}"
862
- end
863
- end
864
- }
865
- }
866
-
867
- @@resource_types.each_pair { |name, attrs|
868
- Object.const_get("MU").const_get("Cloud").const_get(name).class_eval {
869
- attr_reader :cloudclass
870
- attr_reader :cloudobj
871
- attr_reader :destroyed
872
- attr_reader :delayed_save
873
-
874
- def self.shortname
875
- name.sub(/.*?::([^:]+)$/, '\1')
876
- end
877
-
878
- def self.cfg_plural
879
- MU::Cloud.resource_types[shortname.to_sym][:cfg_plural]
880
- end
881
-
882
- def self.has_multiples
883
- MU::Cloud.resource_types[shortname.to_sym][:has_multiples]
884
- end
885
-
886
- def self.cfg_name
887
- MU::Cloud.resource_types[shortname.to_sym][:cfg_name]
888
- end
889
-
890
- def self.can_live_in_vpc
891
- MU::Cloud.resource_types[shortname.to_sym][:can_live_in_vpc]
892
- end
893
-
894
- def self.waits_on_parent_completion
895
- MU::Cloud.resource_types[shortname.to_sym][:waits_on_parent_completion]
896
- end
897
-
898
- def self.deps_wait_on_my_creation
899
- MU::Cloud.resource_types[shortname.to_sym][:deps_wait_on_my_creation]
900
- end
901
-
902
- # Print something palatable when we're called in a string context.
903
- def to_s
904
- fullname = "#{self.class.shortname}"
905
- if !@cloudobj.nil? and !@cloudobj.mu_name.nil?
906
- @mu_name ||= @cloudobj.mu_name
907
- end
908
- if !@mu_name.nil? and !@mu_name.empty?
909
- fullname = fullname + " '#{@mu_name}'"
910
- end
911
- if !@cloud_id.nil?
912
- fullname = fullname + " (#{@cloud_id})"
913
- end
914
- return fullname
915
- end
916
-
917
- # Set our +deploy+ and +deploy_id+ attributes, optionally doing so even
918
- # if they have already been set.
919
- #
920
- # @param mommacat [MU::MommaCat]: The deploy to which we're being told we belong
921
- # @param force [Boolean]: Set even if we already have a deploy object
922
- # @return [String]: Our new +deploy_id+
923
- def intoDeploy(mommacat, force: false)
924
- if force or (!@deploy)
925
- MU.log "Inserting #{self} (#{self.object_id}) into #{mommacat.deploy_id}", MU::DEBUG
926
- @deploy = mommacat
927
- @deploy_id = @deploy.deploy_id
928
- @cloudobj.intoDeploy(mommacat, force: force) if @cloudobj
929
- end
930
- @deploy_id
931
- end
932
-
933
- # @param mommacat [MU::MommaCat]: The deployment containing this cloud resource
934
- # @param mu_name [String]: Optional- specify the full Mu resource name of an existing resource to load, instead of creating a new one
935
- # @param cloud_id [String]: Optional- specify the cloud provider's identifier for an existing resource to load, instead of creating a new one
936
- # @param kitten_cfg [Hash]: The parse configuration for this object from {MU::Config}
937
- def initialize(**args)
938
- raise MuError, "Cannot invoke Cloud objects without a configuration" if args[:kitten_cfg].nil?
939
-
940
- # We are a parent wrapper object. Initialize our child object and
941
- # housekeeping bits accordingly.
942
- if self.class.name.match(/^MU::Cloud::([^:]+)$/)
943
- @live = true
944
- @delayed_save = args[:delayed_save]
945
- @method_semaphore = Mutex.new
946
- @method_locks = {}
947
- if args[:mommacat]
948
- MU.log "Initializing an instance of #{self.class.name} in #{args[:mommacat].deploy_id} #{mu_name}", MU::DEBUG, details: args[:kitten_cfg]
949
- elsif args[:mu_name].nil?
950
- raise MuError, "Can't instantiate a MU::Cloud object with a live deploy or giving us a mu_name"
951
- else
952
- MU.log "Initializing a detached #{self.class.name} named #{args[:mu_name]}", MU::DEBUG, details: args[:kitten_cfg]
953
- end
954
-
955
- my_cloud = args[:kitten_cfg]['cloud'].to_s || MU::Config.defaultCloud
956
- if my_cloud.nil? or !MU::Cloud.supportedClouds.include?(my_cloud)
957
- raise MuError, "Can't instantiate a MU::Cloud object without a valid cloud (saw '#{my_cloud}')"
958
- end
959
-
960
- @cloudclass = MU::Cloud.loadCloudType(my_cloud, self.class.shortname)
961
- @cloudparentclass = Object.const_get("MU").const_get("Cloud").const_get(my_cloud)
962
- @cloudobj = @cloudclass.new(
963
- mommacat: args[:mommacat],
964
- kitten_cfg: args[:kitten_cfg],
965
- cloud_id: args[:cloud_id],
966
- mu_name: args[:mu_name]
967
- )
968
- raise MuError, "Unknown error instantiating #{self}" if @cloudobj.nil?
969
- # These should actually call the method live instead of caching a static value
970
- PUBLIC_ATTRS.each { |a|
971
- instance_variable_set(("@"+a.to_s).to_sym, @cloudobj.send(a))
972
- }
973
- @deploy ||= args[:mommacat]
974
- @deploy_id ||= @deploy.deploy_id if @deploy
975
-
976
- # Register with the containing deployment
977
- if !@deploy.nil? and !@cloudobj.mu_name.nil? and
978
- !@cloudobj.mu_name.empty? and !args[:delay_descriptor_load]
979
- describe # XXX is this actually safe here?
980
- @deploy.addKitten(self.class.cfg_name, @config['name'], self)
981
- elsif !@deploy.nil? and @cloudobj.mu_name.nil?
982
- MU.log "#{self} in #{@deploy.deploy_id} didn't generate a mu_name after being loaded/initialized, dependencies on this resource will probably be confused!", MU::ERR, details: [caller, args.keys]
983
- end
984
-
985
-
986
- # We are actually a child object invoking this via super() from its
987
- # own initialize(), so initialize all the attributes and instance
988
- # variables we know to be universal.
989
- else
990
-
991
- # Declare the attributes that everyone should have
992
- class << self
993
- PUBLIC_ATTRS.each { |a|
994
- attr_reader a
995
- }
996
- end
997
-
998
- # XXX this butchers ::Id and ::Ref objects that might be used by dependencies() to good effect, but we also can't expect our implementations to cope with knowing when a .to_s has to be appended to things at random
999
- @config = MU::Config.manxify(args[:kitten_cfg]) || MU::Config.manxify(args[:config])
1000
-
1001
- if !@config
1002
- MU.log "Missing config arguments in setInstanceVariables, can't initialize a cloud object without it", MU::ERR, details: args.keys
1003
- raise MuError, "Missing config arguments in setInstanceVariables"
1004
- end
1005
-
1006
- @deploy = args[:mommacat] || args[:deploy]
1007
-
1008
- @credentials = args[:credentials]
1009
- @credentials ||= @config['credentials']
1010
-
1011
- @cloud = @config['cloud']
1012
- if !@cloud
1013
- if self.class.name.match(/^MU::Cloud::([^:]+)(?:::.+|$)/)
1014
- cloudclass_name = Regexp.last_match[1]
1015
- if MU::Cloud.supportedClouds.include?(cloudclass_name)
1016
- @cloud = cloudclass_name
1017
- end
1018
- end
1019
- end
1020
- if !@cloud
1021
- raise MuError, "Failed to determine what cloud #{self} should be in!"
1022
- end
1023
-
1024
- @environment = @config['environment']
1025
- if @deploy
1026
- @deploy_id = @deploy.deploy_id
1027
- @appname = @deploy.appname
1028
- end
1029
-
1030
- @cloudclass = MU::Cloud.loadCloudType(@cloud, self.class.shortname)
1031
- @cloudparentclass = Object.const_get("MU").const_get("Cloud").const_get(@cloud)
1032
-
1033
- # A pre-existing object, you say?
1034
- if args[:cloud_id]
1035
-
1036
- # TODO implement ::Id for every cloud... and they should know how to get from
1037
- # cloud_desc to a fully-resolved ::Id object, not just the short string
1038
-
1039
- @cloud_id = args[:cloud_id]
1040
- describe(cloud_id: @cloud_id)
1041
- @habitat_id = habitat_id # effectively, cache this
1042
-
1043
- # If we can build us an ::Id object for @cloud_id instead of a
1044
- # string, do so.
1045
- begin
1046
- idclass = Object.const_get("MU").const_get("Cloud").const_get(@cloud).const_get("Id")
1047
- long_id = if @deploydata and @deploydata[idclass.idattr.to_s]
1048
- @deploydata[idclass.idattr.to_s]
1049
- elsif self.respond_to?(idclass.idattr)
1050
- self.send(idclass.idattr)
1051
- end
1052
-
1053
- @cloud_id = idclass.new(long_id) if !long_id.nil? and !long_id.empty?
1054
- # 1 see if we have the value on the object directly or in deploy data
1055
- # 2 set an attr_reader with the value
1056
- # 3 rewrite our @cloud_id attribute with a ::Id object
1057
- rescue NameError, MU::Cloud::MuCloudResourceNotImplemented
1058
- end
1059
-
1060
- end
1061
-
1062
- # Use pre-existing mu_name (we're probably loading an extant deploy)
1063
- # if available
1064
- if args[:mu_name]
1065
- @mu_name = args[:mu_name].dup
1066
- # If scrub_mu_isms is set, our mu_name is always just the bare name
1067
- # field of the resource.
1068
- elsif @config['scrub_mu_isms']
1069
- @mu_name = @config['name'].dup
1070
- # XXX feck it insert an inheritable method right here? Set a default? How should resource implementations determine whether they're instantiating a new object?
1071
- end
1072
-
1073
- @tags = {}
1074
- if !@config['scrub_mu_isms']
1075
- @tags = @deploy ? @deploy.listStandardTags : MU::MommaCat.listStandardTags
1076
- end
1077
- if @config['tags']
1078
- @config['tags'].each { |tag|
1079
- @tags[tag['key']] = tag['value']
1080
- }
1081
- end
1082
-
1083
- if @cloudparentclass.respond_to?(:resourceInitHook)
1084
- @cloudparentclass.resourceInitHook(self, @deploy)
1085
- end
1086
-
1087
- # Add cloud-specific instance methods for our resource objects to
1088
- # inherit.
1089
- if @cloudparentclass.const_defined?(:AdditionalResourceMethods)
1090
- self.extend @cloudparentclass.const_get(:AdditionalResourceMethods)
1091
- end
1092
-
1093
- if ["Server", "ServerPool"].include?(self.class.shortname) and @deploy
1094
- @mu_name ||= @deploy.getResourceName(@config['name'], need_unique_string: @config.has_key?("basis"))
1095
- if self.class.shortname == "Server"
1096
- @groomer = MU::Groomer.new(self)
1097
- end
1098
-
1099
- @groomclass = MU::Groomer.loadGroomer(@config["groomer"])
1100
-
1101
- if windows? or @config['active_directory'] and !@mu_windows_name
1102
- if !@deploydata.nil? and !@deploydata['mu_windows_name'].nil?
1103
- @mu_windows_name = @deploydata['mu_windows_name']
1104
- else
1105
- # Use the same random differentiator as the "real" name if we're
1106
- # from a ServerPool. Helpful for admin sanity.
1107
- unq = @mu_name.sub(/^.*?-(...)$/, '\1')
1108
- if @config['basis'] and !unq.nil? and !unq.empty?
1109
- @mu_windows_name = @deploy.getResourceName(@config['name'], max_length: 15, need_unique_string: true, use_unique_string: unq, reuse_unique_string: true)
1110
- else
1111
- @mu_windows_name = @deploy.getResourceName(@config['name'], max_length: 15, need_unique_string: true)
1112
- end
1113
- end
1114
- end
1115
- class << self
1116
- attr_reader :groomer
1117
- attr_reader :groomerclass
1118
- attr_accessor :mu_windows_name # XXX might be ok as reader now
1119
- end
1120
- end
1121
- end
1122
-
1123
- end
1124
-
1125
- def cloud
1126
- if @cloud
1127
- @cloud
1128
- elsif @config and @config['cloud']
1129
- @config['cloud']
1130
- elsif self.class.name.match(/^MU::Cloud::([^:]+)::.+/)
1131
- cloudclass_name = Regexp.last_match[1]
1132
- if MU::Cloud.supportedClouds.include?(cloudclass_name)
1133
- cloudclass_name
1134
- else
1135
- nil
1136
- end
1137
- else
1138
- nil
1139
- end
1140
- end
1141
-
1142
-
1143
- # Remove all metadata and cloud resources associated with this object
1144
- def destroy
1145
- if self.class.cfg_name == "server"
1146
- begin
1147
- ip = canonicalIP
1148
- MU::MommaCat.removeIPFromSSHKnownHosts(ip) if ip
1149
- if @deploy and @deploy.deployment and
1150
- @deploy.deployment['servers'] and @config['name'] and
1151
- me = @deploy.deployment['servers'][@config['name']][@mu_name]
1152
- if me
1153
- ["private_ip_address", "public_ip_address"].each { |field|
1154
- if me[field]
1155
- MU::MommaCat.removeIPFromSSHKnownHosts(me[field])
1156
- end
1157
- }
1158
- if me["private_ip_list"]
1159
- me["private_ip_list"].each { |ip|
1160
- MU::MommaCat.removeIPFromSSHKnownHosts(ip)
1161
- }
1162
- end
1163
- end
1164
- end
1165
- rescue MU::MuError => e
1166
- MU.log e.message, MU::WARN
1167
- end
1168
- end
1169
- if !@cloudobj.nil? and !@cloudobj.groomer.nil?
1170
- @cloudobj.groomer.cleanup
1171
- elsif !@groomer.nil?
1172
- @groomer.cleanup
1173
- end
1174
- if !@deploy.nil?
1175
- if !@cloudobj.nil? and !@config.nil? and !@cloudobj.mu_name.nil?
1176
- @deploy.notify(self.class.cfg_plural, @config['name'], nil, mu_name: @cloudobj.mu_name, remove: true, triggering_node: @cloudobj, delayed_save: @delayed_save)
1177
- elsif !@mu_name.nil?
1178
- @deploy.notify(self.class.cfg_plural, @config['name'], nil, mu_name: @mu_name, remove: true, triggering_node: self, delayed_save: @delayed_save)
1179
- end
1180
- @deploy.removeKitten(self)
1181
- end
1182
- # Make sure that if notify gets called again it won't go returning a
1183
- # bunch of now-bogus metadata.
1184
- @destroyed = true
1185
- if !@cloudobj.nil?
1186
- def @cloudobj.notify
1187
- {}
1188
- end
1189
- else
1190
- def notify
1191
- {}
1192
- end
1193
- end
1194
- end
1195
-
1196
- # Return the cloud object's idea of where it lives (project, account,
1197
- # etc) in the form of an identifier. If not applicable for this object,
1198
- # we expect to return +nil+.
1199
- # @return [String,nil]
1200
- def habitat(nolookup: true)
1201
- return nil if ["folder", "habitat"].include?(self.class.cfg_name)
1202
- if @cloudobj
1203
- @cloudparentclass.habitat(@cloudobj, nolookup: nolookup, deploy: @deploy)
1204
- else
1205
- @cloudparentclass.habitat(self, nolookup: nolookup, deploy: @deploy)
1206
- end
1207
- end
1208
-
1209
- def habitat_id(nolookup: false)
1210
- @habitat_id ||= habitat(nolookup: nolookup)
1211
- @habitat_id
1212
- end
1213
-
1214
- # We're fundamentally a wrapper class, so go ahead and reroute requests
1215
- # that are meant for our wrapped object.
1216
- def method_missing(method_sym, *arguments)
1217
- if @cloudobj
1218
- MU.log "INVOKING #{method_sym.to_s} FROM PARENT CLOUD OBJECT #{self}", MU::DEBUG, details: arguments
1219
- @cloudobj.method(method_sym).call(*arguments)
1220
- else
1221
- raise NoMethodError, "No such instance method #{method_sym.to_s} available on #{self.class.name}"
1222
- end
1223
- end
1224
-
1225
- # Merge the passed hash into the existing configuration hash of this
1226
- # cloud object. Currently this is only used by the {MU::Adoption}
1227
- # module. I don't love exposing this to the whole internal API, but I'm
1228
- # probably overthinking that.
1229
- # @param newcfg [Hash]
1230
- def config!(newcfg)
1231
- @config.merge!(newcfg)
1232
- end
1233
-
1234
- def cloud_desc(use_cache: true)
1235
- describe
1236
-
1237
- if !@cloudobj.nil?
1238
- if @cloudobj.class.instance_methods(false).include?(:cloud_desc)
1239
- @cloud_desc_cache ||= @cloudobj.cloud_desc
1240
- end
1241
- end
1242
- if !@config.nil? and !@cloud_id.nil? and (!use_cache or @cloud_desc_cache.nil?)
1243
- # The find() method should be returning a Hash with the cloud_id
1244
- # as a key and a cloud platform descriptor as the value.
1245
- begin
1246
- args = {
1247
- :region => @config['region'],
1248
- :cloud => @config['cloud'],
1249
- :cloud_id => @cloud_id,
1250
- :credentials => @credentials,
1251
- :project => habitat_id, # XXX this belongs in our required_instance_methods hack
1252
- :flags => @config
1253
- }
1254
- @cloudparentclass.required_instance_methods.each { |m|
1255
- # if respond_to?(m)
1256
- # args[m] = method(m).call
1257
- # else
1258
- args[m] = instance_variable_get(("@"+m.to_s).to_sym)
1259
- # end
1260
- }
1261
-
1262
- matches = self.class.find(args)
1263
- if !matches.nil? and matches.is_a?(Hash)
1264
- # XXX or if the hash is keyed with an ::Id element, oh boy
1265
- # puts matches[@cloud_id][:self_link]
1266
- # puts matches[@cloud_id][:url]
1267
- # if matches[@cloud_id][:self_link]
1268
- # @url ||= matches[@cloud_id][:self_link]
1269
- # elsif matches[@cloud_id][:url]
1270
- # @url ||= matches[@cloud_id][:url]
1271
- # elsif matches[@cloud_id][:arn]
1272
- # @arn ||= matches[@cloud_id][:arn]
1273
- # end
1274
- if matches[@cloud_id]
1275
- @cloud_desc_cache = matches[@cloud_id]
1276
- else
1277
- matches.each_pair { |k, v| # flatten out ::Id objects just in case
1278
- if @cloud_id.to_s == k.to_s
1279
- @cloud_desc_cache = v
1280
- break
1281
- end
1282
- }
1283
- end
1284
- end
1285
-
1286
- if !@cloud_desc_cache
1287
- MU.log "cloud_desc via #{self.class.name}.find() failed to locate a live object.\nWas called by #{caller[0]}", MU::WARN, details: args
1288
- end
1289
- rescue Exception => e
1290
- MU.log "Got #{e.inspect} trying to find cloud handle for #{self.class.shortname} #{@mu_name} (#{@cloud_id})", MU::WARN
1291
- raise e
1292
- end
1293
- end
1294
-
1295
- return @cloud_desc_cache
1296
- end
1297
-
1298
- # Retrieve all of the known metadata for this resource.
1299
- # @param cloud_id [String]: The cloud platform's identifier for the resource we're describing. Makes lookups more efficient.
1300
- # @param update_cache [Boolean]: Ignore cached data if we have any, instead reconsituting from original sources.
1301
- # @return [Array<Hash>]: mu_name, config, deploydata
1302
- def describe(cloud_id: nil, update_cache: false)
1303
- if cloud_id.nil? and !@cloudobj.nil?
1304
- @cloud_id ||= @cloudobj.cloud_id
1305
- end
1306
- res_type = self.class.cfg_plural
1307
- res_name = @config['name'] if !@config.nil?
1308
- @credentials ||= @config['credentials'] if !@config.nil?
1309
- deploydata = nil
1310
- if !@deploy.nil? and @deploy.is_a?(MU::MommaCat) and
1311
- !@deploy.deployment.nil? and
1312
- !@deploy.deployment[res_type].nil? and
1313
- !@deploy.deployment[res_type][res_name].nil?
1314
- deploydata = @deploy.deployment[res_type][res_name]
1315
- else
1316
- # XXX This should only happen on a brand new resource, but we should
1317
- # probably complain under other circumstances, if we can
1318
- # differentiate them.
1319
- end
1320
-
1321
- if self.class.has_multiples and !@mu_name.nil? and deploydata.is_a?(Hash) and deploydata.has_key?(@mu_name)
1322
- @deploydata = deploydata[@mu_name]
1323
- elsif deploydata.is_a?(Hash)
1324
- @deploydata = deploydata
1325
- end
1326
-
1327
- if @cloud_id.nil? and @deploydata.is_a?(Hash)
1328
- if @mu_name.nil? and @deploydata.has_key?('#MU_NAME')
1329
- @mu_name = @deploydata['#MU_NAME']
1330
- end
1331
- if @deploydata.has_key?('cloud_id')
1332
- @cloud_id ||= @deploydata['cloud_id']
1333
- end
1334
- end
1335
-
1336
- return [@mu_name, @config, @deploydata]
1337
- end
1338
-
1339
- # Fetch MU::Cloud objects for each of this object's dependencies, and
1340
- # return in an easily-navigable Hash. This can include things listed in
1341
- # @config['dependencies'], implicitly-defined dependencies such as
1342
- # add_firewall_rules or vpc stanzas, and may refer to objects internal
1343
- # to this deployment or external. Will populate the instance variables
1344
- # @dependencies (general dependencies, which can only be sibling
1345
- # resources in this deployment), as well as for certain config stanzas
1346
- # which can refer to external resources (@vpc, @loadbalancers,
1347
- # @add_firewall_rules)
1348
- def dependencies(use_cache: false, debug: false)
1349
- @dependencies = {} if @dependencies.nil?
1350
- @loadbalancers = [] if @loadbalancers.nil?
1351
- if @config.nil?
1352
- return [@dependencies, @vpc, @loadbalancers]
1353
- end
1354
- if use_cache and @dependencies.size > 0
1355
- return [@dependencies, @vpc, @loadbalancers]
1356
- end
1357
- @config['dependencies'] = [] if @config['dependencies'].nil?
1358
-
1359
- loglevel = debug ? MU::NOTICE : MU::DEBUG
1360
-
1361
- # First, general dependencies. These should all be fellow members of
1362
- # the current deployment.
1363
- @config['dependencies'].each { |dep|
1364
- @dependencies[dep['type']] ||= {}
1365
- next if @dependencies[dep['type']].has_key?(dep['name'])
1366
- handle = @deploy.findLitterMate(type: dep['type'], name: dep['name']) if !@deploy.nil?
1367
- if !handle.nil?
1368
- MU.log "Loaded dependency for #{self}: #{dep['name']} => #{handle}", loglevel
1369
- @dependencies[dep['type']][dep['name']] = handle
1370
- else
1371
- # XXX yell under circumstances where we should expect to have
1372
- # our stuff available already?
1373
- end
1374
- }
1375
-
1376
- # Special dependencies: my containing VPC
1377
- if self.class.can_live_in_vpc and !@config['vpc'].nil?
1378
- @config['vpc']["id"] ||= @config['vpc']["vpc_id"] # old deploys
1379
- @config['vpc']["name"] ||= @config['vpc']["vpc_name"] # old deploys
1380
- # If something hash-ified a MU::Config::Ref here, fix it
1381
- if !@config['vpc']["id"].nil? and @config['vpc']["id"].is_a?(Hash)
1382
- @config['vpc']["id"] = MU::Config::Ref.new(@config['vpc']["id"])
1383
- end
1384
- if !@config['vpc']["id"].nil?
1385
- if @config['vpc']["id"].is_a?(MU::Config::Ref) and !@config['vpc']["id"].kitten.nil?
1386
- @vpc = @config['vpc']["id"].kitten
1387
- else
1388
- if @config['vpc']['habitat']
1389
- @config['vpc']['habitat'] = MU::Config::Ref.get(@config['vpc']['habitat'])
1390
- end
1391
- vpc_ref = MU::Config::Ref.get(@config['vpc'])
1392
- @vpc = vpc_ref.kitten
1393
- end
1394
- elsif !@config['vpc']["name"].nil? and @deploy
1395
- MU.log "Attempting findLitterMate on VPC for #{self}", loglevel, details: @config['vpc']
1396
-
1397
- sib_by_name = @deploy.findLitterMate(name: @config['vpc']['name'], type: "vpcs", return_all: true, habitat: @config['vpc']['project'], debug: debug)
1398
- if sib_by_name.is_a?(Array)
1399
- if sib_by_name.size == 1
1400
- @vpc = matches.first
1401
- MU.log "Single VPC match for #{self}", loglevel, details: @vpc.to_s
1402
- else
1403
- # XXX ok but this is the wrong place for this really the config parser needs to sort this out somehow
1404
- # we got multiple matches, try to pick one by preferred subnet
1405
- # behavior
1406
- MU.log "Sorting a bunch of VPC matches for #{self}", loglevel, details: sib_by_name.map { |s| s.to_s }.join(", ")
1407
- sib_by_name.each { |sibling|
1408
- all_private = sibling.subnets.map { |s| s.private? }.all?(true)
1409
- all_public = sibling.subnets.map { |s| s.private? }.all?(false)
1410
- names = sibling.subnets.map { |s| s.name }
1411
- ids = sibling.subnets.map { |s| s.cloud_id }
1412
- if all_private and ["private", "all_private"].include?(@config['vpc']['subnet_pref'])
1413
- @vpc = sibling
1414
- break
1415
- elsif all_public and ["public", "all_public"].include?(@config['vpc']['subnet_pref'])
1416
- @vpc = sibling
1417
- break
1418
- elsif @config['vpc']['subnet_name'] and
1419
- names.include?(@config['vpc']['subnet_name'])
1420
- puts "CHOOSING #{@vpc.to_s} 'cause it has #{@config['vpc']['subnet_name']}"
1421
- @vpc = sibling
1422
- break
1423
- elsif @config['vpc']['subnet_id'] and
1424
- ids.include?(@config['vpc']['subnet_id'])
1425
- @vpc = sibling
1426
- break
1427
- end
1428
- }
1429
- if !@vpc
1430
- sibling = sib_by_name.sample
1431
- MU.log "Got multiple matching VPCs for #{self.class.cfg_name} #{@mu_name}, so I'm arbitrarily choosing #{sibling.mu_name}", MU::WARN, details: @config['vpc']
1432
- @vpc = sibling
1433
- end
1434
- end
1435
- else
1436
- @vpc = sib_by_name
1437
- MU.log "Found exact VPC match for #{self}", loglevel, details: sib_by_name.to_s
1438
- end
1439
- else
1440
- MU.log "No shortcuts available to fetch VPC for #{self}", loglevel, details: @config['vpc']
1441
- end
1442
-
1443
- if !@vpc and !@config['vpc']["name"].nil? and
1444
- @dependencies.has_key?("vpc") and
1445
- @dependencies["vpc"].has_key?(@config['vpc']["name"])
1446
- MU.log "Grabbing VPC I see in @dependencies['vpc']['#{@config['vpc']["name"]}'] for #{self}", loglevel, details: @config['vpc']
1447
- @vpc = @dependencies["vpc"][@config['vpc']["name"]]
1448
- elsif !@vpc
1449
- tag_key, tag_value = @config['vpc']['tag'].split(/=/, 2) if !@config['vpc']['tag'].nil?
1450
- if !@config['vpc'].has_key?("id") and
1451
- !@config['vpc'].has_key?("deploy_id") and !@deploy.nil?
1452
- @config['vpc']["deploy_id"] = @deploy.deploy_id
1453
- end
1454
- MU.log "Doing findStray for VPC for #{self}", loglevel, details: @config['vpc']
1455
- vpcs = MU::MommaCat.findStray(
1456
- @config['cloud'],
1457
- "vpc",
1458
- deploy_id: @config['vpc']["deploy_id"],
1459
- cloud_id: @config['vpc']["id"],
1460
- name: @config['vpc']["name"],
1461
- tag_key: tag_key,
1462
- tag_value: tag_value,
1463
- habitats: [@project_id],
1464
- region: @config['vpc']["region"],
1465
- calling_deploy: @deploy,
1466
- credentials: @credentials,
1467
- dummy_ok: true,
1468
- debug: debug
1469
- )
1470
- @vpc = vpcs.first if !vpcs.nil? and vpcs.size > 0
1471
- end
1472
- if @vpc and @vpc.config and @vpc.config['bastion'] and
1473
- @vpc.config['bastion'].to_h['name'] != @config['name']
1474
- refhash = @vpc.config['bastion'].to_h
1475
- refhash['deploy_id'] ||= @vpc.deploy.deploy_id
1476
- natref = MU::Config::Ref.get(refhash)
1477
- if natref and natref.kitten(@vpc.deploy)
1478
- @nat = natref.kitten(@vpc.deploy)
1479
- end
1480
- end
1481
- if @nat.nil? and !@vpc.nil? and (
1482
- @config['vpc'].has_key?("nat_host_id") or
1483
- @config['vpc'].has_key?("nat_host_tag") or
1484
- @config['vpc'].has_key?("nat_host_ip") or
1485
- @config['vpc'].has_key?("nat_host_name")
1486
- )
1487
-
1488
- nat_tag_key, nat_tag_value = @config['vpc']['nat_host_tag'].split(/=/, 2) if !@config['vpc']['nat_host_tag'].nil?
1489
-
1490
- @nat = @vpc.findBastion(
1491
- nat_name: @config['vpc']['nat_host_name'],
1492
- nat_cloud_id: @config['vpc']['nat_host_id'],
1493
- nat_tag_key: nat_tag_key,
1494
- nat_tag_value: nat_tag_value,
1495
- nat_ip: @config['vpc']['nat_host_ip']
1496
- )
1497
-
1498
- if @nat.nil?
1499
- if !@vpc.cloud_desc.nil?
1500
- @nat = @vpc.findNat(
1501
- nat_cloud_id: @config['vpc']['nat_host_id'],
1502
- nat_filter_key: "vpc-id",
1503
- region: @config['vpc']["region"],
1504
- nat_filter_value: @vpc.cloud_id,
1505
- credentials: @config['credentials']
1506
- )
1507
- else
1508
- @nat = @vpc.findNat(
1509
- nat_cloud_id: @config['vpc']['nat_host_id'],
1510
- region: @config['vpc']["region"],
1511
- credentials: @config['credentials']
1512
- )
1513
- end
1514
- end
1515
- end
1516
- elsif self.class.cfg_name == "vpc"
1517
- @vpc = self
1518
- end
1519
-
1520
- # Google accounts usually have a useful default VPC we can use
1521
- if @vpc.nil? and @project_id and @cloud == "Google" and
1522
- self.class.can_live_in_vpc
1523
- MU.log "Seeing about default VPC for #{self.to_s}", MU::NOTICE
1524
- vpcs = MU::MommaCat.findStray(
1525
- "Google",
1526
- "vpc",
1527
- cloud_id: "default",
1528
- habitats: [@project_id],
1529
- credentials: @credentials,
1530
- dummy_ok: true,
1531
- debug: debug
1532
- )
1533
- @vpc = vpcs.first if !vpcs.nil? and vpcs.size > 0
1534
- end
1535
-
1536
- # Special dependencies: LoadBalancers I've asked to attach to an
1537
- # instance.
1538
- if @config.has_key?("loadbalancers")
1539
- @loadbalancers = [] if !@loadbalancers
1540
- @config['loadbalancers'].each { |lb|
1541
- MU.log "Loading LoadBalancer for #{self}", MU::DEBUG, details: lb
1542
- if @dependencies.has_key?("loadbalancer") and
1543
- @dependencies["loadbalancer"].has_key?(lb['concurrent_load_balancer'])
1544
- @loadbalancers << @dependencies["loadbalancer"][lb['concurrent_load_balancer']]
1545
- else
1546
- if !lb.has_key?("existing_load_balancer") and
1547
- !lb.has_key?("deploy_id") and !@deploy.nil?
1548
- lb["deploy_id"] = @deploy.deploy_id
1549
- end
1550
- lbs = MU::MommaCat.findStray(
1551
- @config['cloud'],
1552
- "loadbalancer",
1553
- deploy_id: lb["deploy_id"],
1554
- cloud_id: lb['existing_load_balancer'],
1555
- name: lb['concurrent_load_balancer'],
1556
- region: @config["region"],
1557
- calling_deploy: @deploy,
1558
- dummy_ok: true
1559
- )
1560
- @loadbalancers << lbs.first if !lbs.nil? and lbs.size > 0
1561
- end
1562
- }
1563
- end
1564
-
1565
- return [@dependencies, @vpc, @loadbalancers]
1566
- end
1567
-
1568
- # Defaults any resources that don't declare their release-readiness to
1569
- # ALPHA. That'll learn 'em.
1570
- def self.quality
1571
- MU::Cloud::ALPHA
1572
- end
1573
-
1574
- # Return a list of "container" artifacts, by class, that apply to this
1575
- # resource type in a cloud provider. This is so methods that call find
1576
- # know whether to call +find+ with identifiers for parent resources.
1577
- # This is similar in purpose to the +isGlobal?+ resource class method,
1578
- # which tells our search functions whether or not a resource scopes to
1579
- # a region. In almost all cases this is one-entry list consisting of
1580
- # +:Habitat+. Notable exceptions include most implementations of
1581
- # +Habitat+, which either reside inside a +:Folder+ or nothing at all;
1582
- # whereas a +:Folder+ tends to not have any containing parent. Very few
1583
- # resource implementations will need to override this.
1584
- # A +nil+ entry in this list is interpreted as "this resource can be
1585
- # global."
1586
- # @return [Array<Symbol,nil>]
1587
- def self.canLiveIn
1588
- if self.shortname == "Folder"
1589
- [nil, :Folder]
1590
- elsif self.shortname == "Habitat"
1591
- [:Folder]
1592
- else
1593
- [:Habitat]
1594
- end
1595
- end
1596
-
1597
- def self.find(*flags)
1598
- allfound = {}
1599
-
1600
- MU::Cloud.availableClouds.each { |cloud|
1601
- begin
1602
- args = flags.first
1603
- next if args[:cloud] and args[:cloud] != cloud
1604
- # skip this cloud if we have a region argument that makes no
1605
- # sense there
1606
- cloudbase = Object.const_get("MU").const_get("Cloud").const_get(cloud)
1607
- next if cloudbase.listCredentials.nil? or cloudbase.listCredentials.empty? or cloudbase.credConfig(args[:credentials]).nil?
1608
- if args[:region] and cloudbase.respond_to?(:listRegions)
1609
- if !cloudbase.listRegions(credentials: args[:credentials])
1610
- MU.log "Failed to get region list for credentials #{args[:credentials]} in cloud #{cloud}", MU::ERR, details: caller
1611
- else
1612
- next if !cloudbase.listRegions(credentials: args[:credentials]).include?(args[:region])
1613
- end
1614
- end
1615
- begin
1616
- cloudclass = MU::Cloud.loadCloudType(cloud, shortname)
1617
- rescue MU::MuError => e
1618
- next
1619
- end
1620
-
1621
- found = cloudclass.find(args)
1622
- if !found.nil?
1623
- if found.is_a?(Hash)
1624
- allfound.merge!(found)
1625
- else
1626
- raise MuError, "#{cloudclass}.find returned a non-Hash result"
1627
- end
1628
- end
1629
- rescue MuCloudResourceNotImplemented
1630
- end
1631
- }
1632
- allfound
1633
- end
1634
-
1635
- if shortname == "DNSZone"
1636
- def self.genericMuDNSEntry(*flags)
1637
- # XXX have this switch on a global config for where Mu puts its DNS
1638
- cloudclass = MU::Cloud.loadCloudType(MU::Config.defaultCloud, "DNSZone")
1639
- cloudclass.genericMuDNSEntry(flags.first)
1640
- end
1641
- def self.createRecordsFromConfig(*flags)
1642
- cloudclass = MU::Cloud.loadCloudType(MU::Config.defaultCloud, "DNSZone")
1643
- if !flags.nil? and flags.size == 1
1644
- cloudclass.createRecordsFromConfig(flags.first)
1645
- else
1646
- cloudclass.createRecordsFromConfig(*flags)
1647
- end
1648
- end
1649
- end
1650
-
1651
- if shortname == "Server" or shortname == "ServerPool"
1652
- def windows?
1653
- return true if %w{win2k16 win2k12r2 win2k12 win2k8 win2k8r2 win2k19 windows}.include?(@config['platform'])
1654
- begin
1655
- return true if cloud_desc.respond_to?(:platform) and cloud_desc.platform == "Windows"
1656
- # XXX ^ that's AWS-speak, doesn't cover GCP or anything else; maybe we should require cloud layers to implement this so we can just call @cloudobj.windows?
1657
- rescue MU::MuError
1658
- return false
1659
- end
1660
- false
1661
- end
1662
-
1663
- # Gracefully message and attempt to accommodate the common transient errors peculiar to Windows nodes
1664
- # @param e [Exception]: The exception that we're handling
1665
- # @param retries [Integer]: The current number of retries, which we'll increment and pass back to the caller
1666
- # @param rebootable_fails [Integer]: The current number of reboot-worthy failures, which we'll increment and pass back to the caller
1667
- # @param max_retries [Integer]: Maximum number of retries to attempt; we'll raise an exception if this is exceeded
1668
- # @param reboot_on_problems [Boolean]: Whether we should try to reboot a "stuck" machine
1669
- # @param retry_interval [Integer]: How many seconds to wait before returning for another attempt
1670
- def handleWindowsFail(e, retries, rebootable_fails, max_retries: 30, reboot_on_problems: false, retry_interval: 45)
1671
- msg = "WinRM connection to https://"+@mu_name+":5986/wsman: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})"
1672
- if e.class.name == "WinRM::WinRMAuthorizationError" or e.message.match(/execution expired/) and reboot_on_problems
1673
- if rebootable_fails > 0 and (rebootable_fails % 5) == 0
1674
- MU.log "#{@mu_name} still misbehaving, forcing Stop and Start from API", MU::WARN
1675
- reboot(true) # vicious API stop/start
1676
- sleep retry_interval*3
1677
- rebootable_fails = 0
1678
- else
1679
- if rebootable_fails == 3
1680
- MU.log "#{@mu_name} misbehaving, attempting to reboot from API", MU::WARN
1681
- reboot # graceful API restart
1682
- sleep retry_interval*2
1683
- end
1684
- rebootable_fails = rebootable_fails + 1
1685
- end
1686
- end
1687
- if retries < max_retries
1688
- if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0 and retries != 0)
1689
- MU.log msg, MU::NOTICE
1690
- elsif retries/max_retries > 0.5
1691
- MU.log msg, MU::WARN, details: e.inspect
1692
- end
1693
- sleep retry_interval
1694
- retries = retries + 1
1695
- else
1696
- raise MuError, "#{@mu_name}: #{e.inspect} trying to connect with WinRM, max_retries exceeded", e.backtrace
1697
- end
1698
- return [retries, rebootable_fails]
1699
- end
1700
-
1701
- def windowsRebootPending?(shell = nil)
1702
- if shell.nil?
1703
- shell = getWinRMSession(1, 30)
1704
- end
1705
- # if (Get-Item "HKLM:/SOFTWARE/Microsoft/Windows/CurrentVersion/WindowsUpdate/Auto Update/RebootRequired" -EA Ignore) { exit 1 }
1706
- cmd = %Q{
1707
- if (Get-ChildItem "HKLM:/Software/Microsoft/Windows/CurrentVersion/Component Based Servicing/RebootPending" -EA Ignore) {
1708
- echo "Component Based Servicing/RebootPending is true"
1709
- exit 1
1710
- }
1711
- if (Get-ItemProperty "HKLM:/SYSTEM/CurrentControlSet/Control/Session Manager" -Name PendingFileRenameOperations -EA Ignore) {
1712
- echo "Control/Session Manager/PendingFileRenameOperations is true"
1713
- exit 1
1714
- }
1715
- try {
1716
- $util = [wmiclass]"\\\\.\\root\\ccm\\clientsdk:CCM_ClientUtilities"
1717
- $status = $util.DetermineIfRebootPending()
1718
- if(($status -ne $null) -and $status.RebootPending){
1719
- echo "WMI says RebootPending is true"
1720
- exit 1
1721
- }
1722
- } catch {
1723
- exit 0
1724
- }
1725
- exit 0
1726
- }
1727
- resp = shell.run(cmd)
1728
- returnval = resp.exitcode == 0 ? false : true
1729
- shell.close
1730
- returnval
1731
- end
1732
-
1733
- # Basic setup tasks performed on a new node during its first WinRM
1734
- # connection. Most of this is terrible Windows glue.
1735
- # @param shell [WinRM::Shells::Powershell]: An active Powershell session to the new node.
1736
- def initialWinRMTasks(shell)
1737
- retries = 0
1738
- rebootable_fails = 0
1739
- begin
1740
- if !@config['use_cloud_provider_windows_password']
1741
- pw = @groomer.getSecret(
1742
- vault: @config['mu_name'],
1743
- item: "windows_credentials",
1744
- field: "password"
1745
- )
1746
- win_check_for_pw = %Q{Add-Type -AssemblyName System.DirectoryServices.AccountManagement; $Creds = (New-Object System.Management.Automation.PSCredential("#{@config["windows_admin_username"]}", (ConvertTo-SecureString "#{pw}" -AsPlainText -Force)));$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine); $DS.ValidateCredentials($Creds.GetNetworkCredential().UserName, $Creds.GetNetworkCredential().password); echo $Result}
1747
- resp = shell.run(win_check_for_pw)
1748
- if resp.stdout.chomp != "True"
1749
- win_set_pw = %Q{(([adsi]('WinNT://./#{@config["windows_admin_username"]}, user')).psbase.invoke('SetPassword', '#{pw}'))}
1750
- resp = shell.run(win_set_pw)
1751
- puts resp.stdout
1752
- MU.log "Resetting Windows host password", MU::NOTICE, details: resp.stdout
1753
- end
1754
- end
1755
-
1756
- # Install Cygwin here, because for some reason it breaks inside Chef
1757
- # XXX would love to not do this here
1758
- pkgs = ["bash", "mintty", "vim", "curl", "openssl", "wget", "lynx", "openssh"]
1759
- admin_home = "c:/bin/cygwin/home/#{@config["windows_admin_username"]}"
1760
- install_cygwin = %Q{
1761
- If (!(Test-Path "c:/bin/cygwin/Cygwin.bat")){
1762
- $WebClient = New-Object System.Net.WebClient
1763
- $WebClient.DownloadFile("http://cygwin.com/setup-x86_64.exe","$env:Temp/setup-x86_64.exe")
1764
- Start-Process -wait -FilePath $env:Temp/setup-x86_64.exe -ArgumentList "-q -n -l $env:Temp/cygwin -R c:/bin/cygwin -s http://mirror.cs.vt.edu/pub/cygwin/cygwin/ -P #{pkgs.join(',')}"
1765
- }
1766
- if(!(Test-Path #{admin_home})){
1767
- New-Item -type directory -path #{admin_home}
1768
- }
1769
- if(!(Test-Path #{admin_home}/.ssh)){
1770
- New-Item -type directory -path #{admin_home}/.ssh
1771
- }
1772
- if(!(Test-Path #{admin_home}/.ssh/authorized_keys)){
1773
- New-Item #{admin_home}/.ssh/authorized_keys -type file -force -value "#{@deploy.ssh_public_key}"
1774
- }
1775
- }
1776
- resp = shell.run(install_cygwin)
1777
- if resp.exitcode != 0
1778
- MU.log "Failed at installing Cygwin", MU::ERR, details: resp
1779
- end
1780
-
1781
- set_hostname = true
1782
- hostname = nil
1783
- if !@config['active_directory'].nil?
1784
- if @config['active_directory']['node_type'] == "domain_controller" && @config['active_directory']['domain_controller_hostname']
1785
- hostname = @config['active_directory']['domain_controller_hostname']
1786
- @mu_windows_name = hostname
1787
- set_hostname = true
1788
- else
1789
- # Do we have an AD specific hostname?
1790
- hostname = @mu_windows_name
1791
- set_hostname = true
1792
- end
1793
- else
1794
- hostname = @mu_windows_name
1795
- end
1796
- resp = shell.run(%Q{hostname})
1797
-
1798
- if resp.stdout.chomp != hostname
1799
- resp = shell.run(%Q{Rename-Computer -NewName '#{hostname}' -Force -PassThru -Restart; Restart-Computer -Force})
1800
- MU.log "Renaming Windows host to #{hostname}; this will trigger a reboot", MU::NOTICE, details: resp.stdout
1801
- reboot(true)
1802
- sleep 30
1803
- end
1804
- rescue WinRM::WinRMError, HTTPClient::ConnectTimeoutError => e
1805
- retries, rebootable_fails = handleWindowsFail(e, retries, rebootable_fails, max_retries: 10, reboot_on_problems: true, retry_interval: 30)
1806
- retry
1807
- end
1808
- end
1809
-
1810
-
1811
- # Basic setup tasks performed on a new node during its first initial
1812
- # ssh connection. Most of this is terrible Windows glue.
1813
- # @param ssh [Net::SSH::Connection::Session]: The active SSH session to the new node.
1814
- def initialSSHTasks(ssh)
1815
- win_env_fix = %q{echo 'export PATH="$PATH:/cygdrive/c/opscode/chef/embedded/bin"' > "$HOME/chef-client"; echo 'prev_dir="`pwd`"; for __dir in /proc/registry/HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Session\ Manager/Environment;do cd "$__dir"; for __var in `ls * | grep -v TEMP | grep -v TMP`;do __var=`echo $__var | tr "[a-z]" "[A-Z]"`; test -z "${!__var}" && export $__var="`cat $__var`" >/dev/null 2>&1; done; done; cd "$prev_dir"; /cygdrive/c/opscode/chef/bin/chef-client.bat $@' >> "$HOME/chef-client"; chmod 700 "$HOME/chef-client"; ( grep "^alias chef-client=" "$HOME/.bashrc" || echo 'alias chef-client="$HOME/chef-client"' >> "$HOME/.bashrc" ) ; ( grep "^alias mu-groom=" "$HOME/.bashrc" || echo 'alias mu-groom="powershell -File \"c:/Program Files/Amazon/Ec2ConfigService/Scripts/UserScript.ps1\""' >> "$HOME/.bashrc" )}
1816
- win_installer_check = %q{ls /proc/registry/HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Installer/}
1817
- lnx_installer_check = %q{ps auxww | awk '{print $11}' | egrep '(/usr/bin/yum|apt-get|dpkg)'}
1818
- lnx_updates_check = %q{( test -f /.mu-installer-ran-updates || ! test -d /var/lib/cloud/instance ) || echo "userdata still running"}
1819
- win_set_pw = nil
1820
-
1821
- if windows? and !@config['use_cloud_provider_windows_password']
1822
- # This covers both the case where we have a windows password passed from a vault and where we need to use a a random Windows Admin password generated by MU::Cloud::Server.generateWindowsPassword
1823
- pw = @groomer.getSecret(
1824
- vault: @config['mu_name'],
1825
- item: "windows_credentials",
1826
- field: "password"
1827
- )
1828
- win_check_for_pw = %Q{powershell -Command '& {Add-Type -AssemblyName System.DirectoryServices.AccountManagement; $Creds = (New-Object System.Management.Automation.PSCredential("#{@config["windows_admin_username"]}", (ConvertTo-SecureString "#{pw}" -AsPlainText -Force)));$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine); $DS.ValidateCredentials($Creds.GetNetworkCredential().UserName, $Creds.GetNetworkCredential().password); echo $Result}'}
1829
- win_set_pw = %Q{powershell -Command "& {(([adsi]('WinNT://./#{@config["windows_admin_username"]}, user')).psbase.invoke('SetPassword', '#{pw}'))}"}
1830
- end
1831
-
1832
- # There shouldn't be a use case where a domain joined computer goes through initialSSHTasks. Removing Active Directory specific computer rename.
1833
- set_hostname = true
1834
- hostname = nil
1835
- if !@config['active_directory'].nil?
1836
- if @config['active_directory']['node_type'] == "domain_controller" && @config['active_directory']['domain_controller_hostname']
1837
- hostname = @config['active_directory']['domain_controller_hostname']
1838
- @mu_windows_name = hostname
1839
- set_hostname = true
1840
- else
1841
- # Do we have an AD specific hostname?
1842
- hostname = @mu_windows_name
1843
- set_hostname = true
1844
- end
1845
- else
1846
- hostname = @mu_windows_name
1847
- end
1848
- win_check_for_hostname = %Q{powershell -Command '& {hostname}'}
1849
- win_set_hostname = %Q{powershell -Command "& {Rename-Computer -NewName '#{hostname}' -Force -PassThru -Restart; Restart-Computer -Force }"}
1850
-
1851
- begin
1852
- # Set our admin password first, if we need to
1853
- if windows? and !win_set_pw.nil? and !win_check_for_pw.nil?
1854
- output = ssh.exec!(win_check_for_pw)
1855
- raise MU::Cloud::BootstrapTempFail, "Got nil output from ssh session, waiting and retrying" if output.nil?
1856
- if !output.match(/True/)
1857
- MU.log "Setting Windows password for user #{@config['windows_admin_username']}", details: ssh.exec!(win_set_pw)
1858
- end
1859
- end
1860
- if windows?
1861
- output = ssh.exec!(win_env_fix)
1862
- output = ssh.exec!(win_installer_check)
1863
- raise MU::Cloud::BootstrapTempFail, "Got nil output from ssh session, waiting and retrying" if output.nil?
1864
- if output.match(/InProgress/)
1865
- raise MU::Cloud::BootstrapTempFail, "Windows Installer service is still doing something, need to wait"
1866
- end
1867
- if set_hostname and !@hostname_set and @mu_windows_name
1868
- output = ssh.exec!(win_check_for_hostname)
1869
- raise MU::Cloud::BootstrapTempFail, "Got nil output from ssh session, waiting and retrying" if output.nil?
1870
- if !output.match(/#{@mu_windows_name}/)
1871
- MU.log "Setting Windows hostname to #{@mu_windows_name}", details: ssh.exec!(win_set_hostname)
1872
- @hostname_set = true
1873
- # Reboot from the API too, in case Windows is flailing
1874
- if !@cloudobj.nil?
1875
- @cloudobj.reboot
1876
- else
1877
- reboot
1878
- end
1879
- raise MU::Cloud::BootstrapTempFail, "Set hostname in Windows, waiting for reboot"
1880
- end
1881
- end
1882
- else
1883
- output = ssh.exec!(lnx_installer_check)
1884
- if !output.nil? and !output.empty?
1885
- raise MU::Cloud::BootstrapTempFail, "Linux package manager is still doing something, need to wait (#{output})"
1886
- end
1887
- if !@config['skipinitialupdates'] and
1888
- !@config['scrub_mu_isms'] and
1889
- !@config['userdata_script']
1890
- output = ssh.exec!(lnx_updates_check)
1891
- if !output.nil? and output.match(/userdata still running/)
1892
- raise MU::Cloud::BootstrapTempFail, "Waiting for initial userdata system updates to complete"
1893
- end
1894
- end
1895
- end
1896
- rescue RuntimeError => e
1897
- raise MU::Cloud::BootstrapTempFail, "Got #{e.inspect} performing initial SSH connect tasks, will try again"
1898
- end
1899
-
1900
- end
1901
-
1902
- # Get a privileged Powershell session on the server in question, using SSL-encrypted WinRM with certificate authentication.
1903
- # @param max_retries [Integer]:
1904
- # @param retry_interval [Integer]:
1905
- # @param timeout [Integer]:
1906
- # @param winrm_retries [Integer]:
1907
- # @param reboot_on_problems [Boolean]:
1908
- def getWinRMSession(max_retries = 40, retry_interval = 60, timeout: 30, winrm_retries: 5, reboot_on_problems: false)
1909
- nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = getSSHConfig
1910
- @mu_name ||= @config['mu_name']
1911
-
1912
- conn = nil
1913
- shell = nil
1914
- opts = nil
1915
- # and now, a thing I really don't want to do
1916
- MU::MommaCat.addInstanceToEtcHosts(canonical_ip, @mu_name)
1917
-
1918
- # catch exceptions that circumvent our regular call stack
1919
- Thread.abort_on_exception = false
1920
- Thread.handle_interrupt(WinRM::WinRMWSManFault => :never) {
1921
- begin
1922
- Thread.handle_interrupt(WinRM::WinRMWSManFault => :immediate) {
1923
- MU.log "(Probably harmless) Caught a WinRM::WinRMWSManFault in #{Thread.current.inspect}", MU::DEBUG, details: Thread.current.backtrace
1924
- }
1925
- ensure
1926
- # Reraise something useful
1927
- end
1928
- }
1929
-
1930
- retries = 0
1931
- rebootable_fails = 0
1932
- begin
1933
- MU.log "Calling WinRM on #{@mu_name}", MU::DEBUG, details: opts
1934
- opts = {
1935
- endpoint: 'https://'+@mu_name+':5986/wsman',
1936
- retry_limit: winrm_retries,
1937
- no_ssl_peer_verification: true, # XXX this should not be necessary; we get 'hostname "foo" does not match the server certificate' even when it clearly does match
1938
- ca_trust_path: "#{MU.mySSLDir}/Mu_CA.pem",
1939
- transport: :ssl,
1940
- operation_timeout: timeout,
1941
- client_cert: "#{MU.mySSLDir}/#{@mu_name}-winrm.crt",
1942
- client_key: "#{MU.mySSLDir}/#{@mu_name}-winrm.key"
1943
- }
1944
- conn = WinRM::Connection.new(opts)
1945
- MU.log "WinRM connection to #{@mu_name} created", MU::DEBUG, details: conn
1946
- shell = conn.shell(:powershell)
1947
- shell.run('ipconfig') # verify that we can do something
1948
- rescue Errno::EHOSTUNREACH, Errno::ECONNREFUSED, HTTPClient::ConnectTimeoutError, OpenSSL::SSL::SSLError, SocketError, WinRM::WinRMError, Timeout::Error => e
1949
- retries, rebootable_fails = handleWindowsFail(e, retries, rebootable_fails, max_retries: max_retries, reboot_on_problems: reboot_on_problems, retry_interval: retry_interval)
1950
- retry
1951
- ensure
1952
- MU::MommaCat.removeInstanceFromEtcHosts(@mu_name)
1953
- end
1954
-
1955
- shell
1956
- end
1957
-
1958
- # @param max_retries [Integer]: Number of connection attempts to make before giving up
1959
- # @param retry_interval [Integer]: Number of seconds to wait between connection attempts
1960
- # @return [Net::SSH::Connection::Session]
1961
- def getSSHSession(max_retries = 12, retry_interval = 30)
1962
- ssh_keydir = Etc.getpwnam(@deploy.mu_user).dir+"/.ssh"
1963
- nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = getSSHConfig
1964
- session = nil
1965
- retries = 0
1966
-
1967
- vpc_class = Object.const_get("MU").const_get("Cloud").const_get(@cloud).const_get("VPC")
1968
-
1969
- # XXX WHY is this a thing
1970
- Thread.handle_interrupt(Errno::ECONNREFUSED => :never) {
1971
- }
1972
-
1973
- begin
1974
- MU::Cloud.handleNetSSHExceptions
1975
- if !nat_ssh_host.nil?
1976
- proxy_cmd = "ssh -q -o StrictHostKeyChecking=no -W %h:%p #{nat_ssh_user}@#{nat_ssh_host}"
1977
- MU.log "Attempting SSH to #{canonical_ip} (#{@mu_name}) as #{ssh_user} with key #{@deploy.ssh_key_name} using proxy '#{proxy_cmd}'" if retries == 0
1978
- proxy = Net::SSH::Proxy::Command.new(proxy_cmd)
1979
- session = Net::SSH.start(
1980
- canonical_ip,
1981
- ssh_user,
1982
- :config => false,
1983
- :keys_only => true,
1984
- :keys => [ssh_keydir+"/"+nat_ssh_key, ssh_keydir+"/"+@deploy.ssh_key_name],
1985
- :verify_host_key => false,
1986
- # :verbose => :info,
1987
- :host_key => "ssh-rsa",
1988
- :port => 22,
1989
- :auth_methods => ['publickey'],
1990
- :proxy => proxy
1991
- )
1992
- else
1993
-
1994
- MU.log "Attempting SSH to #{canonical_ip} (#{@mu_name}) as #{ssh_user} with key #{ssh_keydir}/#{@deploy.ssh_key_name}" if retries == 0
1995
- session = Net::SSH.start(
1996
- canonical_ip,
1997
- ssh_user,
1998
- :config => false,
1999
- :keys_only => true,
2000
- :keys => [ssh_keydir+"/"+@deploy.ssh_key_name],
2001
- :verify_host_key => false,
2002
- # :verbose => :info,
2003
- :host_key => "ssh-rsa",
2004
- :port => 22,
2005
- :auth_methods => ['publickey']
2006
- )
2007
- end
2008
- retries = 0
2009
- rescue Net::SSH::HostKeyMismatch => e
2010
- MU.log("Remembering new key: #{e.fingerprint}")
2011
- e.remember_host!
2012
- session.close
2013
- retry
2014
- rescue SystemCallError, Timeout::Error, Errno::ECONNRESET, Errno::EHOSTUNREACH, Net::SSH::Proxy::ConnectError, SocketError, Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, IOError, Net::SSH::ConnectionTimeout, Net::SSH::Proxy::ConnectError, MU::Cloud::NetSSHFail => e
2015
- begin
2016
- session.close if !session.nil?
2017
- rescue Net::SSH::Disconnect, IOError => e
2018
- if windows?
2019
- MU.log "Windows has probably closed the ssh session before we could. Waiting before trying again", MU::NOTICE
2020
- else
2021
- MU.log "ssh session was closed unexpectedly, waiting before trying again", MU::NOTICE
2022
- end
2023
- sleep 10
2024
- end
2025
-
2026
- if retries < max_retries
2027
- retries = retries + 1
2028
- msg = "ssh #{ssh_user}@#{@mu_name}: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})"
2029
- if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0)
2030
- MU.log msg, MU::NOTICE
2031
- if !vpc_class.haveRouteToInstance?(cloud_desc, credentials: @credentials) and
2032
- canonical_ip.match(/(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1$)|(^[fF][cCdD])/) and
2033
- !nat_ssh_host
2034
- MU.log "Node #{@mu_name} at #{canonical_ip} looks like it's in a private address space, and I don't appear to have a direct route to it. It may not be possible to connect with this routing!", MU::WARN
2035
- end
2036
- elsif retries/max_retries > 0.5
2037
- MU.log msg, MU::WARN, details: e.inspect
2038
- end
2039
- sleep retry_interval
2040
- retry
2041
- else
2042
- raise MuError, "#{@mu_name}: #{e.inspect} trying to connect with SSH, max_retries exceeded", e.backtrace
2043
- end
2044
- end
2045
- return session
2046
- end
2047
- end
2048
-
2049
- # Wrapper for the cleanup class method of underlying cloud object implementations.
2050
- def self.cleanup(*flags)
2051
- ok = true
2052
- params = flags.first
2053
- clouds = MU::Cloud.supportedClouds
2054
- if params[:cloud]
2055
- clouds = [params[:cloud]]
2056
- params.delete(:cloud)
2057
- end
2058
-
2059
- clouds.each { |cloud|
2060
- begin
2061
- cloudclass = MU::Cloud.loadCloudType(cloud, shortname)
2062
- raise MuCloudResourceNotImplemented if !cloudclass.respond_to?(:cleanup) or cloudclass.method(:cleanup).owner.to_s != "#<Class:#{cloudclass}>"
2063
- MU.log "Invoking #{cloudclass}.cleanup from #{shortname}", MU::DEBUG, details: flags
2064
- cloudclass.cleanup(params)
2065
- rescue MuCloudResourceNotImplemented
2066
- MU.log "No #{cloud} implementation of #{shortname}.cleanup, skipping", MU::DEBUG, details: flags
2067
- rescue Exception => e
2068
- in_msg = cloud
2069
- if params and params[:region]
2070
- in_msg += " "+params[:region]
2071
- end
2072
- if params and params[:flags] and params[:flags]["project"]
2073
- in_msg += " project "+params[:flags]["project"]
2074
- end
2075
- MU.log "Skipping #{shortname} cleanup method in #{in_msg} due to exception: #{e.message}", MU::WARN, details: e.backtrace
2076
- ok = false
2077
- end
2078
- }
2079
- MU::MommaCat.unlockAll
2080
-
2081
- ok
2082
- end
2083
-
2084
- # A hook that is always called just before each instance method is
2085
- # invoked, so that we can ensure that repetitive setup tasks (like
2086
- # resolving +:resource_group+ for Azure resources) have always been
2087
- # done.
2088
- def resourceInitHook
2089
- @cloud ||= cloud
2090
- if @cloudparentclass.respond_to?(:resourceInitHook)
2091
- @cloudparentclass.resourceInitHook(@cloudobj, @deploy)
2092
- end
2093
- end
2094
-
2095
- # Wrap the instance methods that this cloud resource type has to
2096
- # implement.
2097
- MU::Cloud.resource_types[name.to_sym][:instance].each { |method|
2098
- define_method method do |*args|
2099
- return nil if @cloudobj.nil?
2100
- MU.log "Invoking #{@cloudobj}.#{method}", MU::DEBUG
2101
-
2102
- # Go ahead and guarantee that we can't accidentally trigger these
2103
- # methods recursively.
2104
- @method_semaphore.synchronize {
2105
- # We're looking for recursion, not contention, so ignore some
2106
- # obviously harmless things.
2107
- if @method_locks.has_key?(method) and method != :findBastion and method != :cloud_id
2108
- MU.log "Double-call to cloud method #{method} for #{self}", MU::DEBUG, details: caller + ["competing call stack:"] + @method_locks[method]
2109
- end
2110
- @method_locks[method] = caller
2111
- }
2112
-
2113
- # Make sure the describe() caches are fresh
2114
- @cloudobj.describe if method != :describe
2115
-
2116
- # Don't run through dependencies on simple attr_reader lookups
2117
- if ![:dependencies, :cloud_id, :config, :mu_name].include?(method)
2118
- @cloudobj.dependencies
2119
- end
2120
-
2121
- retval = nil
2122
- if !args.nil? and args.size == 1
2123
- retval = @cloudobj.method(method).call(args.first)
2124
- elsif !args.nil? and args.size > 0
2125
- retval = @cloudobj.method(method).call(*args)
2126
- else
2127
- retval = @cloudobj.method(method).call
2128
- end
2129
- if (method == :create or method == :groom or method == :postBoot) and
2130
- (!@destroyed and !@cloudobj.destroyed)
2131
- deploydata = @cloudobj.method(:notify).call
2132
- @deploydata ||= deploydata # XXX I don't remember why we're not just doing this from the get-go; maybe because we prefer some mangling occurring in @deploy.notify?
2133
- if deploydata.nil? or !deploydata.is_a?(Hash)
2134
- MU.log "#{self} notify method did not return a Hash of deployment data, attempting to fill in with cloud descriptor #{@cloudobj.cloud_id}", MU::WARN
2135
- deploydata = MU.structToHash(@cloudobj.cloud_desc)
2136
- raise MuError, "Failed to collect metadata about #{self}" if deploydata.nil?
2137
- end
2138
- deploydata['cloud_id'] ||= @cloudobj.cloud_id if !@cloudobj.cloud_id.nil?
2139
- deploydata['mu_name'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
2140
- deploydata['nodename'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
2141
- deploydata.delete("#MUOBJECT")
2142
- @deploy.notify(self.class.cfg_plural, @config['name'], deploydata, triggering_node: @cloudobj, delayed_save: @delayed_save) if !@deploy.nil?
2143
- elsif method == :notify
2144
- retval['cloud_id'] = @cloudobj.cloud_id.to_s if !@cloudobj.cloud_id.nil?
2145
- retval['mu_name'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
2146
- @deploy.notify(self.class.cfg_plural, @config['name'], retval, triggering_node: @cloudobj, delayed_save: @delayed_save) if !@deploy.nil?
2147
- end
2148
- @method_semaphore.synchronize {
2149
- @method_locks.delete(method)
2150
- }
2151
-
2152
- @deploydata = @cloudobj.deploydata
2153
- @config = @cloudobj.config
2154
- retval
2155
- end
2156
- } # end instance method list
2157
- } # end dynamic class generation block
2158
- } # end resource type iteration
624
+ require 'mu/cloud/machine_images'
625
+ require 'mu/cloud/resource_base'
626
+ require 'mu/cloud/providers'
2159
627
 
2160
628
  end
2161
629