cloud-mu 3.1.5 → 3.3.2

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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +5 -1
  3. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  4. data/ansible/roles/mu-windows/files/config.xml +76 -0
  5. data/ansible/roles/mu-windows/tasks/main.yml +16 -0
  6. data/bin/mu-adopt +16 -12
  7. data/bin/mu-azure-tests +57 -0
  8. data/bin/mu-cleanup +2 -4
  9. data/bin/mu-configure +52 -0
  10. data/bin/mu-deploy +3 -3
  11. data/bin/mu-findstray-tests +25 -0
  12. data/bin/mu-gen-docs +2 -4
  13. data/bin/mu-load-config.rb +2 -1
  14. data/bin/mu-node-manage +15 -16
  15. data/bin/mu-run-tests +37 -12
  16. data/cloud-mu.gemspec +3 -3
  17. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  18. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  19. data/cookbooks/mu-tools/libraries/helper.rb +1 -1
  20. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  21. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  22. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  23. data/cookbooks/mu-tools/recipes/windows-client.rb +25 -22
  24. data/extras/clean-stock-amis +25 -19
  25. data/extras/generate-stock-images +1 -0
  26. data/extras/image-generators/AWS/win2k12.yaml +2 -0
  27. data/extras/image-generators/AWS/win2k16.yaml +2 -0
  28. data/extras/image-generators/AWS/win2k19.yaml +2 -0
  29. data/modules/mommacat.ru +1 -1
  30. data/modules/mu.rb +86 -98
  31. data/modules/mu/adoption.rb +373 -58
  32. data/modules/mu/cleanup.rb +214 -303
  33. data/modules/mu/cloud.rb +128 -1733
  34. data/modules/mu/cloud/database.rb +49 -0
  35. data/modules/mu/cloud/dnszone.rb +44 -0
  36. data/modules/mu/cloud/machine_images.rb +212 -0
  37. data/modules/mu/cloud/providers.rb +81 -0
  38. data/modules/mu/cloud/resource_base.rb +929 -0
  39. data/modules/mu/cloud/server.rb +40 -0
  40. data/modules/mu/cloud/server_pool.rb +1 -0
  41. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  42. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  43. data/modules/mu/cloud/wrappers.rb +169 -0
  44. data/modules/mu/config.rb +123 -81
  45. data/modules/mu/config/alarm.rb +2 -6
  46. data/modules/mu/config/bucket.rb +32 -3
  47. data/modules/mu/config/cache_cluster.rb +2 -2
  48. data/modules/mu/config/cdn.rb +100 -0
  49. data/modules/mu/config/collection.rb +1 -1
  50. data/modules/mu/config/container_cluster.rb +7 -2
  51. data/modules/mu/config/database.rb +84 -105
  52. data/modules/mu/config/database.yml +1 -2
  53. data/modules/mu/config/dnszone.rb +5 -4
  54. data/modules/mu/config/doc_helpers.rb +5 -6
  55. data/modules/mu/config/endpoint.rb +2 -1
  56. data/modules/mu/config/firewall_rule.rb +3 -19
  57. data/modules/mu/config/folder.rb +1 -1
  58. data/modules/mu/config/function.rb +17 -8
  59. data/modules/mu/config/group.rb +1 -1
  60. data/modules/mu/config/habitat.rb +1 -1
  61. data/modules/mu/config/job.rb +89 -0
  62. data/modules/mu/config/loadbalancer.rb +57 -11
  63. data/modules/mu/config/log.rb +1 -1
  64. data/modules/mu/config/msg_queue.rb +1 -1
  65. data/modules/mu/config/nosqldb.rb +1 -1
  66. data/modules/mu/config/notifier.rb +8 -19
  67. data/modules/mu/config/ref.rb +92 -14
  68. data/modules/mu/config/role.rb +1 -1
  69. data/modules/mu/config/schema_helpers.rb +38 -37
  70. data/modules/mu/config/search_domain.rb +1 -1
  71. data/modules/mu/config/server.rb +12 -13
  72. data/modules/mu/config/server_pool.rb +3 -7
  73. data/modules/mu/config/storage_pool.rb +1 -1
  74. data/modules/mu/config/tail.rb +11 -0
  75. data/modules/mu/config/user.rb +1 -1
  76. data/modules/mu/config/vpc.rb +27 -23
  77. data/modules/mu/config/vpc.yml +0 -1
  78. data/modules/mu/defaults/AWS.yaml +90 -90
  79. data/modules/mu/defaults/Azure.yaml +1 -0
  80. data/modules/mu/defaults/Google.yaml +1 -0
  81. data/modules/mu/deploy.rb +34 -20
  82. data/modules/mu/groomer.rb +16 -1
  83. data/modules/mu/groomers/ansible.rb +69 -4
  84. data/modules/mu/groomers/chef.rb +51 -4
  85. data/modules/mu/logger.rb +120 -144
  86. data/modules/mu/master.rb +97 -4
  87. data/modules/mu/mommacat.rb +160 -874
  88. data/modules/mu/mommacat/daemon.rb +23 -14
  89. data/modules/mu/mommacat/naming.rb +110 -3
  90. data/modules/mu/mommacat/search.rb +497 -0
  91. data/modules/mu/mommacat/storage.rb +252 -194
  92. data/modules/mu/{clouds → providers}/README.md +1 -1
  93. data/modules/mu/{clouds → providers}/aws.rb +258 -57
  94. data/modules/mu/{clouds → providers}/aws/alarm.rb +3 -3
  95. data/modules/mu/{clouds → providers}/aws/bucket.rb +275 -41
  96. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +14 -50
  97. data/modules/mu/providers/aws/cdn.rb +782 -0
  98. data/modules/mu/{clouds → providers}/aws/collection.rb +5 -5
  99. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +95 -84
  100. data/modules/mu/providers/aws/database.rb +1744 -0
  101. data/modules/mu/{clouds → providers}/aws/dnszone.rb +26 -12
  102. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  103. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +39 -32
  104. data/modules/mu/{clouds → providers}/aws/folder.rb +1 -1
  105. data/modules/mu/{clouds → providers}/aws/function.rb +289 -134
  106. data/modules/mu/{clouds → providers}/aws/group.rb +18 -20
  107. data/modules/mu/{clouds → providers}/aws/habitat.rb +3 -3
  108. data/modules/mu/providers/aws/job.rb +466 -0
  109. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +77 -47
  110. data/modules/mu/{clouds → providers}/aws/log.rb +5 -5
  111. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +14 -11
  112. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +96 -5
  113. data/modules/mu/{clouds → providers}/aws/notifier.rb +135 -63
  114. data/modules/mu/{clouds → providers}/aws/role.rb +76 -48
  115. data/modules/mu/{clouds → providers}/aws/search_domain.rb +172 -41
  116. data/modules/mu/{clouds → providers}/aws/server.rb +66 -98
  117. data/modules/mu/{clouds → providers}/aws/server_pool.rb +42 -60
  118. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +21 -38
  119. data/modules/mu/{clouds → providers}/aws/user.rb +12 -16
  120. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  121. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  122. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +0 -0
  123. data/modules/mu/{clouds → providers}/aws/vpc.rb +143 -74
  124. data/modules/mu/{clouds → providers}/aws/vpc_subnet.rb +0 -0
  125. data/modules/mu/{clouds → providers}/azure.rb +13 -0
  126. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
  127. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
  128. data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
  129. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
  130. data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
  131. data/modules/mu/{clouds → providers}/azure/server.rb +32 -24
  132. data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
  133. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  134. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  135. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  136. data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
  137. data/modules/mu/{clouds → providers}/cloudformation.rb +10 -0
  138. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  139. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  140. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  141. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  142. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  143. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  144. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  145. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  146. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  147. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  148. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
  149. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  150. data/modules/mu/{clouds → providers}/google.rb +29 -6
  151. data/modules/mu/{clouds → providers}/google/bucket.rb +4 -4
  152. data/modules/mu/{clouds → providers}/google/container_cluster.rb +38 -20
  153. data/modules/mu/{clouds → providers}/google/database.rb +5 -12
  154. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +5 -5
  155. data/modules/mu/{clouds → providers}/google/folder.rb +5 -9
  156. data/modules/mu/{clouds → providers}/google/function.rb +6 -6
  157. data/modules/mu/{clouds → providers}/google/group.rb +9 -17
  158. data/modules/mu/{clouds → providers}/google/habitat.rb +4 -8
  159. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +5 -5
  160. data/modules/mu/{clouds → providers}/google/role.rb +50 -31
  161. data/modules/mu/{clouds → providers}/google/server.rb +41 -24
  162. data/modules/mu/{clouds → providers}/google/server_pool.rb +14 -14
  163. data/modules/mu/{clouds → providers}/google/user.rb +34 -24
  164. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  165. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  166. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  167. data/modules/mu/{clouds → providers}/google/vpc.rb +45 -14
  168. data/modules/tests/aws-jobs-functions.yaml +46 -0
  169. data/modules/tests/centos6.yaml +15 -0
  170. data/modules/tests/centos7.yaml +15 -0
  171. data/modules/tests/centos8.yaml +12 -0
  172. data/modules/tests/ecs.yaml +2 -2
  173. data/modules/tests/eks.yaml +1 -1
  174. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  175. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  176. data/modules/tests/microservice_app.yaml +288 -0
  177. data/modules/tests/rds.yaml +108 -0
  178. data/modules/tests/regrooms/rds.yaml +123 -0
  179. data/modules/tests/server-with-scrub-muisms.yaml +1 -1
  180. data/modules/tests/super_complex_bok.yml +2 -2
  181. data/modules/tests/super_simple_bok.yml +3 -5
  182. data/spec/mu/clouds/azure_spec.rb +2 -2
  183. metadata +122 -92
  184. data/modules/mu/clouds/aws/database.rb +0 -1974
  185. data/modules/mu/clouds/aws/endpoint.rb +0 -596
@@ -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
  #
@@ -153,6 +148,12 @@ module MU
153
148
  # Stub base class; real implementations generated at runtime
154
149
  class NoSQLDB;
155
150
  end
151
+ # Stub base class; real implementations generated at runtime
152
+ class Job;
153
+ end
154
+ # Stub base class; real implementations generated at runtime
155
+ class CDN;
156
+ end
156
157
 
157
158
  # Denotes a resource implementation which is missing significant
158
159
  # functionality or is largely untested.
@@ -178,8 +179,8 @@ module MU
178
179
  :interface => self.const_get("Folder"),
179
180
  :deps_wait_on_my_creation => true,
180
181
  :waits_on_parent_completion => true,
181
- :class => generic_class_methods,
182
- :instance => generic_instance_methods
182
+ :class => @@generic_class_methods,
183
+ :instance => @@generic_instance_methods
183
184
  },
184
185
  :Habitat => {
185
186
  :has_multiples => false,
@@ -189,8 +190,8 @@ module MU
189
190
  :interface => self.const_get("Habitat"),
190
191
  :deps_wait_on_my_creation => true,
191
192
  :waits_on_parent_completion => true,
192
- :class => generic_class_methods + [:isLive?],
193
- :instance => generic_instance_methods + [:groom]
193
+ :class => @@generic_class_methods + [:isLive?],
194
+ :instance => @@generic_instance_methods + [:groom]
194
195
  },
195
196
  :Collection => {
196
197
  :has_multiples => false,
@@ -200,8 +201,8 @@ module MU
200
201
  :interface => self.const_get("Collection"),
201
202
  :deps_wait_on_my_creation => true,
202
203
  :waits_on_parent_completion => false,
203
- :class => generic_class_methods,
204
- :instance => generic_instance_methods
204
+ :class => @@generic_class_methods,
205
+ :instance => @@generic_instance_methods
205
206
  },
206
207
  :Database => {
207
208
  :has_multiples => true,
@@ -211,8 +212,8 @@ module MU
211
212
  :interface => self.const_get("Database"),
212
213
  :deps_wait_on_my_creation => true,
213
214
  :waits_on_parent_completion => false,
214
- :class => generic_class_methods,
215
- :instance => generic_instance_methods + [:groom, :allowHost]
215
+ :class => @@generic_class_methods,
216
+ :instance => @@generic_instance_methods + [:groom, :allowHost]
216
217
  },
217
218
  :DNSZone => {
218
219
  :has_multiples => false,
@@ -222,8 +223,8 @@ module MU
222
223
  :interface => self.const_get("DNSZone"),
223
224
  :deps_wait_on_my_creation => true,
224
225
  :waits_on_parent_completion => true,
225
- :class => generic_class_methods + [:genericMuDNSEntry, :createRecordsFromConfig],
226
- :instance => generic_instance_methods
226
+ :class => @@generic_class_methods + [:genericMuDNSEntry, :createRecordsFromConfig],
227
+ :instance => @@generic_instance_methods
227
228
  },
228
229
  :FirewallRule => {
229
230
  :has_multiples => false,
@@ -233,8 +234,8 @@ module MU
233
234
  :interface => self.const_get("FirewallRule"),
234
235
  :deps_wait_on_my_creation => true,
235
236
  :waits_on_parent_completion => false,
236
- :class => generic_class_methods,
237
- :instance => generic_instance_methods + [:groom, :addRule]
237
+ :class => @@generic_class_methods,
238
+ :instance => @@generic_instance_methods + [:groom, :addRule]
238
239
  },
239
240
  :LoadBalancer => {
240
241
  :has_multiples => false,
@@ -244,8 +245,8 @@ module MU
244
245
  :interface => self.const_get("LoadBalancer"),
245
246
  :deps_wait_on_my_creation => true,
246
247
  :waits_on_parent_completion => false,
247
- :class => generic_class_methods,
248
- :instance => generic_instance_methods + [:groom, :registerNode]
248
+ :class => @@generic_class_methods,
249
+ :instance => @@generic_instance_methods + [:groom, :registerNode]
249
250
  },
250
251
  :Server => {
251
252
  :has_multiples => true,
@@ -255,8 +256,8 @@ module MU
255
256
  :interface => self.const_get("Server"),
256
257
  :deps_wait_on_my_creation => false,
257
258
  :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]
259
+ :class => @@generic_class_methods + [:validateInstanceType, :imageTimeStamp],
260
+ :instance => @@generic_instance_methods + [:groom, :postBoot, :getSSHConfig, :canonicalIP, :getWindowsAdminPassword, :active?, :groomer, :mu_windows_name, :mu_windows_name=, :reboot, :addVolume, :genericNAT, :listIPs]
260
261
  },
261
262
  :ServerPool => {
262
263
  :has_multiples => false,
@@ -266,8 +267,8 @@ module MU
266
267
  :interface => self.const_get("ServerPool"),
267
268
  :deps_wait_on_my_creation => false,
268
269
  :waits_on_parent_completion => true,
269
- :class => generic_class_methods,
270
- :instance => generic_instance_methods + [:groom, :listNodes]
270
+ :class => @@generic_class_methods,
271
+ :instance => @@generic_instance_methods + [:groom, :listNodes]
271
272
  },
272
273
  :VPC => {
273
274
  :has_multiples => false,
@@ -277,8 +278,8 @@ module MU
277
278
  :interface => self.const_get("VPC"),
278
279
  :deps_wait_on_my_creation => true,
279
280
  :waits_on_parent_completion => false,
280
- :class => generic_class_methods,
281
- :instance => generic_instance_methods + [:groom, :subnets, :getSubnet, :listSubnets, :findBastion, :findNat]
281
+ :class => @@generic_class_methods,
282
+ :instance => @@generic_instance_methods + [:groom, :subnets, :getSubnet, :findBastion, :findNat]
282
283
  },
283
284
  :CacheCluster => {
284
285
  :has_multiples => true,
@@ -288,8 +289,8 @@ module MU
288
289
  :interface => self.const_get("CacheCluster"),
289
290
  :deps_wait_on_my_creation => true,
290
291
  :waits_on_parent_completion => false,
291
- :class => generic_class_methods,
292
- :instance => generic_instance_methods + [:groom]
292
+ :class => @@generic_class_methods,
293
+ :instance => @@generic_instance_methods + [:groom]
293
294
  },
294
295
  :Alarm => {
295
296
  :has_multiples => false,
@@ -299,8 +300,8 @@ module MU
299
300
  :interface => self.const_get("Alarm"),
300
301
  :deps_wait_on_my_creation => false,
301
302
  :waits_on_parent_completion => true,
302
- :class => generic_class_methods,
303
- :instance => generic_instance_methods + [:groom]
303
+ :class => @@generic_class_methods,
304
+ :instance => @@generic_instance_methods + [:groom]
304
305
  },
305
306
  :Notifier => {
306
307
  :has_multiples => false,
@@ -310,8 +311,8 @@ module MU
310
311
  :interface => self.const_get("Notifier"),
311
312
  :deps_wait_on_my_creation => false,
312
313
  :waits_on_parent_completion => false,
313
- :class => generic_class_methods,
314
- :instance => generic_instance_methods + [:groom]
314
+ :class => @@generic_class_methods,
315
+ :instance => @@generic_instance_methods + [:groom]
315
316
  },
316
317
  :Log => {
317
318
  :has_multiples => false,
@@ -321,8 +322,8 @@ module MU
321
322
  :interface => self.const_get("Log"),
322
323
  :deps_wait_on_my_creation => true,
323
324
  :waits_on_parent_completion => true,
324
- :class => generic_class_methods,
325
- :instance => generic_instance_methods + [:groom]
325
+ :class => @@generic_class_methods,
326
+ :instance => @@generic_instance_methods + [:groom]
326
327
  },
327
328
  :StoragePool => {
328
329
  :has_multiples => false,
@@ -332,8 +333,8 @@ module MU
332
333
  :interface => self.const_get("StoragePool"),
333
334
  :deps_wait_on_my_creation => true,
334
335
  :waits_on_parent_completion => false,
335
- :class => generic_class_methods,
336
- :instance => generic_instance_methods + [:groom]
336
+ :class => @@generic_class_methods,
337
+ :instance => @@generic_instance_methods + [:groom]
337
338
  },
338
339
  :Function => {
339
340
  :has_multiples => false,
@@ -343,8 +344,8 @@ module MU
343
344
  :interface => self.const_get("Function"),
344
345
  :deps_wait_on_my_creation => true,
345
346
  :waits_on_parent_completion => false,
346
- :class => generic_class_methods,
347
- :instance => generic_instance_methods + [:groom]
347
+ :class => @@generic_class_methods,
348
+ :instance => @@generic_instance_methods + [:groom]
348
349
  },
349
350
  :Endpoint => {
350
351
  :has_multiples => false,
@@ -354,8 +355,8 @@ module MU
354
355
  :interface => self.const_get("Endpoint"),
355
356
  :deps_wait_on_my_creation => true,
356
357
  :waits_on_parent_completion => false,
357
- :class => generic_class_methods,
358
- :instance => generic_instance_methods + [:groom]
358
+ :class => @@generic_class_methods,
359
+ :instance => @@generic_instance_methods + [:groom]
359
360
  },
360
361
  :ContainerCluster => {
361
362
  :has_multiples => false,
@@ -365,8 +366,8 @@ module MU
365
366
  :interface => self.const_get("ContainerCluster"),
366
367
  :deps_wait_on_my_creation => true,
367
368
  :waits_on_parent_completion => false,
368
- :class => generic_class_methods,
369
- :instance => generic_instance_methods + [:groom]
369
+ :class => @@generic_class_methods,
370
+ :instance => @@generic_instance_methods + [:groom]
370
371
  },
371
372
  :SearchDomain => {
372
373
  :has_multiples => false,
@@ -376,8 +377,8 @@ module MU
376
377
  :interface => self.const_get("SearchDomain"),
377
378
  :deps_wait_on_my_creation => true,
378
379
  :waits_on_parent_completion => false,
379
- :class => generic_class_methods,
380
- :instance => generic_instance_methods + [:groom]
380
+ :class => @@generic_class_methods,
381
+ :instance => @@generic_instance_methods + [:groom]
381
382
  },
382
383
  :MsgQueue => {
383
384
  :has_multiples => false,
@@ -387,8 +388,8 @@ module MU
387
388
  :interface => self.const_get("MsgQueue"),
388
389
  :deps_wait_on_my_creation => true,
389
390
  :waits_on_parent_completion => true,
390
- :class => generic_class_methods,
391
- :instance => generic_instance_methods + [:groom]
391
+ :class => @@generic_class_methods,
392
+ :instance => @@generic_instance_methods + [:groom]
392
393
  },
393
394
  :User => {
394
395
  :has_multiples => false,
@@ -398,8 +399,8 @@ module MU
398
399
  :interface => self.const_get("User"),
399
400
  :deps_wait_on_my_creation => true,
400
401
  :waits_on_parent_completion => true,
401
- :class => generic_class_methods,
402
- :instance => generic_instance_methods + [:groom]
402
+ :class => @@generic_class_methods,
403
+ :instance => @@generic_instance_methods + [:groom]
403
404
  },
404
405
  :Group => {
405
406
  :has_multiples => false,
@@ -409,8 +410,8 @@ module MU
409
410
  :interface => self.const_get("Group"),
410
411
  :deps_wait_on_my_creation => true,
411
412
  :waits_on_parent_completion => true,
412
- :class => generic_class_methods,
413
- :instance => generic_instance_methods + [:groom]
413
+ :class => @@generic_class_methods,
414
+ :instance => @@generic_instance_methods + [:groom]
414
415
  },
415
416
  :Role => {
416
417
  :has_multiples => false,
@@ -420,8 +421,8 @@ module MU
420
421
  :interface => self.const_get("Role"),
421
422
  :deps_wait_on_my_creation => true,
422
423
  :waits_on_parent_completion => true,
423
- :class => generic_class_methods,
424
- :instance => generic_instance_methods + [:groom]
424
+ :class => @@generic_class_methods,
425
+ :instance => @@generic_instance_methods + [:groom]
425
426
  },
426
427
  :Bucket => {
427
428
  :has_multiples => false,
@@ -431,8 +432,8 @@ module MU
431
432
  :interface => self.const_get("Bucket"),
432
433
  :deps_wait_on_my_creation => true,
433
434
  :waits_on_parent_completion => true,
434
- :class => generic_class_methods + [:upload],
435
- :instance => generic_instance_methods + [:groom, :upload]
435
+ :class => @@generic_class_methods + [:upload],
436
+ :instance => @@generic_instance_methods + [:groom, :upload]
436
437
  },
437
438
  :NoSQLDB => {
438
439
  :has_multiples => false,
@@ -441,202 +442,34 @@ module MU
441
442
  :cfg_plural => "nosqldbs",
442
443
  :interface => self.const_get("NoSQLDB"),
443
444
  :deps_wait_on_my_creation => true,
444
- :waits_on_parent_completion => true,
445
- :class => generic_class_methods,
446
- :instance => generic_instance_methods + [:groom]
445
+ :waits_on_parent_completion => false,
446
+ :class => @@generic_class_methods,
447
+ :instance => @@generic_instance_methods + [:groom]
448
+ },
449
+ :Job => {
450
+ :has_multiples => false,
451
+ :can_live_in_vpc => false,
452
+ :cfg_name => "job",
453
+ :cfg_plural => "jobs",
454
+ :interface => self.const_get("Job"),
455
+ :deps_wait_on_my_creation => true,
456
+ :waits_on_parent_completion => false,
457
+ :class => @@generic_class_methods,
458
+ :instance => @@generic_instance_methods + [:groom]
459
+ },
460
+ :CDN => {
461
+ :has_multiples => false,
462
+ :can_live_in_vpc => false,
463
+ :cfg_name => "cdn",
464
+ :cfg_plural => "cdns",
465
+ :interface => self.const_get("CDN"),
466
+ :deps_wait_on_my_creation => true,
467
+ :waits_on_parent_completion => false,
468
+ :class => @@generic_class_methods,
469
+ :instance => @@generic_instance_methods + [:groom]
447
470
  }
448
471
  }.freeze
449
472
 
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
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 StandardError
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.values.each { |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
473
  # A list of supported cloud resource types as Mu classes
641
474
  def self.resource_types;
642
475
  @@resource_types
@@ -644,79 +477,32 @@ module MU
644
477
 
645
478
  # 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
479
  # @param type [String]: A string that looks like our short or full class name or singular or plural configuration names.
480
+ # @param assert [Boolean]: Raise an exception if the type isn't valid
647
481
  # @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
482
+ def self.getResourceNames(type, assert = true)
483
+ if !type
484
+ if assert
485
+ raise MuError, "nil resource type requested in getResourceNames"
486
+ else
487
+ return [nil, nil, nil, nil, {}]
488
+ end
489
+ end
650
490
  @@resource_types.each_pair { |name, cloudclass|
651
491
  if name == type.to_sym or
652
492
  cloudclass[:cfg_name] == type or
653
493
  cloudclass[:cfg_plural] == type or
654
- Object.const_get("MU").const_get("Cloud").const_get(name) == type
494
+ MU::Cloud.const_get(name) == type
655
495
  type = name
656
- return [type.to_sym, cloudclass[:cfg_name], cloudclass[:cfg_plural], Object.const_get("MU").const_get("Cloud").const_get(name), cloudclass]
657
- end
658
- }
659
- [nil, nil, nil, nil, {}]
660
- end
661
-
662
- # Net::SSH exceptions seem to have their own behavior vis a vis threads,
663
- # and our regular call stack gets circumvented when they're thrown. Cheat
664
- # here to catch them gracefully.
665
- def self.handleNetSSHExceptions
666
- Thread.handle_interrupt(Net::SSH::Exception => :never) {
667
- begin
668
- Thread.handle_interrupt(Net::SSH::Exception => :immediate) {
669
- MU.log "(Probably harmless) Caught a Net::SSH Exception in #{Thread.current.inspect}", MU::DEBUG, details: Thread.current.backtrace
670
- }
671
- ensure
672
- # raise NetSSHFail, "Net::SSH had a nutty"
673
- end
674
- }
675
- end
676
-
677
- # List of known/supported Cloud providers. This may be modified at runtime
678
- # if an implemention is defective or missing required methods.
679
- @@supportedCloudList = ['AWS', 'CloudFormation', 'Google', 'Azure']
680
-
681
- # List of known/supported Cloud providers
682
- # @return [Array<String>]
683
- def self.supportedClouds
684
- @@supportedCloudList
685
- end
686
-
687
- # List of known/supported Cloud providers for which we have at least one
688
- # set of credentials configured.
689
- # @return [Array<String>]
690
- def self.availableClouds
691
- available = []
692
- MU::Cloud.supportedClouds.each { |cloud|
693
- begin
694
- cloudbase = Object.const_get("MU").const_get("Cloud").const_get(cloud)
695
- next if cloudbase.listCredentials.nil? or cloudbase.listCredentials.empty?
696
- available << cloud
697
- rescue NameError
496
+ return [type.to_sym, cloudclass[:cfg_name], cloudclass[:cfg_plural], MU::Cloud.const_get(name), cloudclass]
698
497
  end
699
498
  }
499
+ if assert
500
+ raise MuError, "Invalid resource type #{type} requested in getResourceNames"
501
+ end
700
502
 
701
- available
503
+ [nil, nil, nil, nil, {}]
702
504
  end
703
505
 
704
- # Load the container class for each cloud we know about, and inject autoload
705
- # code for each of its supported resource type classes.
706
- failed = []
707
- MU::Cloud.supportedClouds.each { |cloud|
708
- require "mu/clouds/#{cloud.downcase}"
709
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
710
- generic_class_methods_toplevel.each { |method|
711
- if !cloudclass.respond_to?(method)
712
- MU.log "MU::Cloud::#{cloud} has not implemented required class method #{method}, disabling", MU::ERR
713
- failed << cloud
714
- end
715
- }
716
- }
717
- failed.uniq!
718
- @@supportedCloudList = @@supportedCloudList - failed
719
-
720
506
  # @return [Mutex]
721
507
  def self.userdata_mutex
722
508
  @userdata_mutex ||= Mutex.new
@@ -740,7 +526,7 @@ module MU
740
526
  end
741
527
  template_variables["credentials"] ||= credentials
742
528
  $mu = OpenStruct.new(template_variables)
743
- userdata_dir = File.expand_path(MU.myRoot+"/modules/mu/clouds/#{cloud.downcase}/userdata")
529
+ userdata_dir = File.expand_path(MU.myRoot+"/modules/mu/providers/#{cloud.downcase}/userdata")
744
530
 
745
531
  platform = if %w{win2k12r2 win2k12 win2k8 win2k8r2 win2k16 windows win2k19}.include?(platform)
746
532
  "windows"
@@ -794,13 +580,25 @@ module MU
794
580
  }
795
581
  end
796
582
 
583
+ # Given a resource type, validate that it's legit and return its base class from the {MU::Cloud} module
584
+ # @param type [String]
585
+ # @return [MU::Cloud]
586
+ def self.loadBaseType(type)
587
+ raise MuError, "Argument to MU::Cloud.loadBaseType cannot be nil" if type.nil?
588
+ shortclass, cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(type)
589
+ if !shortclass
590
+ raise MuCloudResourceNotImplemented, "#{type} does not appear to be a valid resource type"
591
+ end
592
+ Object.const_get("MU").const_get("Cloud").const_get(shortclass)
593
+ end
594
+
797
595
  @cloud_class_cache = {}
798
596
  # Given a cloud layer and resource type, return the class which implements it.
799
597
  # @param cloud [String]: The Cloud layer
800
598
  # @param type [String]: The resource type. Can be the full class name, symbolic name, or Basket of Kittens configuration shorthand for the resource type.
801
599
  # @return [Class]: The cloud-specific class implementing this resource
802
- def self.loadCloudType(cloud, type)
803
- raise MuError, "cloud argument to MU::Cloud.loadCloudType cannot be nil" if cloud.nil?
600
+ def self.resourceClass(cloud, type)
601
+ raise MuError, "cloud argument to MU::Cloud.resourceClass cannot be nil" if cloud.nil?
804
602
  shortclass, cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(type)
805
603
  if @cloud_class_cache.has_key?(cloud) and @cloud_class_cache[cloud].has_key?(type)
806
604
  if @cloud_class_cache[cloud][type].nil?
@@ -812,1451 +610,48 @@ module MU
812
610
  if cfg_name.nil?
813
611
  raise MuError, "Can't find a cloud resource type named '#{type}'"
814
612
  end
815
- if !File.size?(MU.myRoot+"/modules/mu/clouds/#{cloud.downcase}.rb")
613
+ if !File.size?(MU.myRoot+"/modules/mu/providers/#{cloud.downcase}.rb")
816
614
  raise MuError, "Requested to use unsupported provisioning layer #{cloud}"
817
615
  end
818
616
  begin
819
- require "mu/clouds/#{cloud.downcase}/#{cfg_name}"
617
+ require "mu/providers/#{cloud.downcase}/#{cfg_name}"
820
618
  rescue LoadError => e
821
619
  raise MuCloudResourceNotImplemented, "MU::Cloud::#{cloud} does not currently implement #{shortclass}, or implementation does not load correctly (#{e.message})"
822
620
  end
621
+
823
622
  @cloud_class_cache[cloud] = {} if !@cloud_class_cache.has_key?(cloud)
824
623
  begin
825
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
826
- myclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(type)
827
- @@resource_types[type.to_sym][:class].each { |class_method|
624
+ cloudclass = const_get("MU").const_get("Cloud").const_get(cloud)
625
+ myclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(shortclass)
626
+
627
+ @@resource_types[shortclass.to_sym][:class].each { |class_method|
828
628
  if !myclass.respond_to?(class_method) or myclass.method(class_method).owner.to_s != "#<Class:#{myclass}>"
829
- raise MuError, "MU::Cloud::#{cloud}::#{type} has not implemented required class method #{class_method}"
629
+ raise MuError, "MU::Cloud::#{cloud}::#{shortclass} has not implemented required class method #{class_method}"
830
630
  end
831
631
  }
832
- @@resource_types[type.to_sym][:instance].each { |instance_method|
632
+ @@resource_types[shortclass.to_sym][:instance].each { |instance_method|
833
633
  if !myclass.public_instance_methods.include?(instance_method)
834
- raise MuCloudResourceNotImplemented, "MU::Cloud::#{cloud}::#{type} has not implemented required instance method #{instance_method}"
634
+ raise MuCloudResourceNotImplemented, "MU::Cloud::#{cloud}::#{shortclass} has not implemented required instance method #{instance_method}"
835
635
  end
836
636
  }
837
637
  cloudclass.required_instance_methods.each { |instance_method|
838
638
  if !myclass.public_instance_methods.include?(instance_method)
839
- MU.log "MU::Cloud::#{cloud}::#{type} has not implemented required instance method #{instance_method}, will declare as attr_accessor", MU::DEBUG
639
+ MU.log "MU::Cloud::#{cloud}::#{shortclass} has not implemented required instance method #{instance_method}, will declare as attr_accessor", MU::DEBUG
840
640
  end
841
641
  }
842
642
 
843
643
  @cloud_class_cache[cloud][type] = myclass
644
+
844
645
  return myclass
845
646
  rescue NameError => e
846
647
  @cloud_class_cache[cloud][type] = nil
847
- raise MuCloudResourceNotImplemented, "The '#{type}' resource is not supported in cloud #{cloud} (tried MU::#{cloud}::#{type})", e.backtrace
648
+ raise MuCloudResourceNotImplemented, "The '#{type}' resource is not supported in cloud #{cloud} (tried MU::Cloud::#{cloud}::#{shortclass})", e.backtrace
848
649
  end
849
650
  end
850
651
 
851
- MU::Cloud.supportedClouds.each { |cloud|
852
- Object.const_get("MU").const_get("Cloud").const_get(cloud).class_eval {
853
-
854
- # Automatically load supported cloud resource classes when they're
855
- # referenced.
856
- def self.const_missing(symbol)
857
- if MU::Cloud.resource_types.has_key?(symbol.to_sym)
858
- return MU::Cloud.loadCloudType(name.sub(/.*?::([^:]+)$/, '\1'), symbol)
859
- else
860
- raise MuCloudResourceNotImplemented, "No such cloud resource #{name}:#{symbol}"
861
- end
862
- end
863
- }
864
- }
865
-
866
- @@resource_types.keys.each { |name|
867
- Object.const_get("MU").const_get("Cloud").const_get(name).class_eval {
868
- attr_reader :cloudclass
869
- attr_reader :cloudobj
870
- attr_reader :destroyed
871
- attr_reader :delayed_save
872
-
873
- def self.shortname
874
- name.sub(/.*?::([^:]+)$/, '\1')
875
- end
876
-
877
- def self.cfg_plural
878
- MU::Cloud.resource_types[shortname.to_sym][:cfg_plural]
879
- end
880
-
881
- def self.has_multiples
882
- MU::Cloud.resource_types[shortname.to_sym][:has_multiples]
883
- end
884
-
885
- def self.cfg_name
886
- MU::Cloud.resource_types[shortname.to_sym][:cfg_name]
887
- end
888
-
889
- def self.can_live_in_vpc
890
- MU::Cloud.resource_types[shortname.to_sym][:can_live_in_vpc]
891
- end
892
-
893
- def self.waits_on_parent_completion
894
- MU::Cloud.resource_types[shortname.to_sym][:waits_on_parent_completion]
895
- end
896
-
897
- def self.deps_wait_on_my_creation
898
- MU::Cloud.resource_types[shortname.to_sym][:deps_wait_on_my_creation]
899
- end
900
-
901
- # Print something palatable when we're called in a string context.
902
- def to_s
903
- fullname = "#{self.class.shortname}"
904
- if !@cloudobj.nil? and !@cloudobj.mu_name.nil?
905
- @mu_name ||= @cloudobj.mu_name
906
- end
907
- if !@mu_name.nil? and !@mu_name.empty?
908
- fullname = fullname + " '#{@mu_name}'"
909
- end
910
- if !@cloud_id.nil?
911
- fullname = fullname + " (#{@cloud_id})"
912
- end
913
- return fullname
914
- end
915
-
916
- # Set our +deploy+ and +deploy_id+ attributes, optionally doing so even
917
- # if they have already been set.
918
- #
919
- # @param mommacat [MU::MommaCat]: The deploy to which we're being told we belong
920
- # @param force [Boolean]: Set even if we already have a deploy object
921
- # @return [String]: Our new +deploy_id+
922
- def intoDeploy(mommacat, force: false)
923
- if force or (!@deploy)
924
- MU.log "Inserting #{self} (#{self.object_id}) into #{mommacat.deploy_id}", MU::DEBUG
925
- @deploy = mommacat
926
- @deploy_id = @deploy.deploy_id
927
- @cloudobj.intoDeploy(mommacat, force: force) if @cloudobj
928
- end
929
- @deploy_id
930
- end
931
-
932
- # @param mommacat [MU::MommaCat]: The deployment containing this cloud resource
933
- # @param mu_name [String]: Optional- specify the full Mu resource name of an existing resource to load, instead of creating a new one
934
- # @param cloud_id [String]: Optional- specify the cloud provider's identifier for an existing resource to load, instead of creating a new one
935
- # @param kitten_cfg [Hash]: The parse configuration for this object from {MU::Config}
936
- def initialize(**args)
937
- raise MuError, "Cannot invoke Cloud objects without a configuration" if args[:kitten_cfg].nil?
938
-
939
- # We are a parent wrapper object. Initialize our child object and
940
- # housekeeping bits accordingly.
941
- if self.class.name.match(/^MU::Cloud::([^:]+)$/)
942
- @live = true
943
- @delayed_save = args[:delayed_save]
944
- @method_semaphore = Mutex.new
945
- @method_locks = {}
946
- if args[:mommacat]
947
- MU.log "Initializing an instance of #{self.class.name} in #{args[:mommacat].deploy_id} #{mu_name}", MU::DEBUG, details: args[:kitten_cfg]
948
- elsif args[:mu_name].nil?
949
- raise MuError, "Can't instantiate a MU::Cloud object with a live deploy or giving us a mu_name"
950
- else
951
- MU.log "Initializing a detached #{self.class.name} named #{args[:mu_name]}", MU::DEBUG, details: args[:kitten_cfg]
952
- end
953
-
954
- my_cloud = args[:kitten_cfg]['cloud'].to_s || MU::Config.defaultCloud
955
- if my_cloud.nil? or !MU::Cloud.supportedClouds.include?(my_cloud)
956
- raise MuError, "Can't instantiate a MU::Cloud object without a valid cloud (saw '#{my_cloud}')"
957
- end
958
-
959
- @cloudclass = MU::Cloud.loadCloudType(my_cloud, self.class.shortname)
960
- @cloudparentclass = Object.const_get("MU").const_get("Cloud").const_get(my_cloud)
961
- @cloudobj = @cloudclass.new(
962
- mommacat: args[:mommacat],
963
- kitten_cfg: args[:kitten_cfg],
964
- cloud_id: args[:cloud_id],
965
- mu_name: args[:mu_name]
966
- )
967
- raise MuError, "Unknown error instantiating #{self}" if @cloudobj.nil?
968
- # These should actually call the method live instead of caching a static value
969
- PUBLIC_ATTRS.each { |a|
970
- instance_variable_set(("@"+a.to_s).to_sym, @cloudobj.send(a))
971
- }
972
- @deploy ||= args[:mommacat]
973
- @deploy_id ||= @deploy.deploy_id if @deploy
974
-
975
- # Register with the containing deployment
976
- if !@deploy.nil? and !@cloudobj.mu_name.nil? and
977
- !@cloudobj.mu_name.empty? and !args[:delay_descriptor_load]
978
- describe # XXX is this actually safe here?
979
- @deploy.addKitten(self.class.cfg_name, @config['name'], self)
980
- elsif !@deploy.nil? and @cloudobj.mu_name.nil?
981
- 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]
982
- end
983
-
984
-
985
- # We are actually a child object invoking this via super() from its
986
- # own initialize(), so initialize all the attributes and instance
987
- # variables we know to be universal.
988
- else
989
-
990
- # Declare the attributes that everyone should have
991
- class << self
992
- PUBLIC_ATTRS.each { |a|
993
- attr_reader a
994
- }
995
- end
996
-
997
- # 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
998
- @config = MU::Config.manxify(args[:kitten_cfg]) || MU::Config.manxify(args[:config])
999
-
1000
- if !@config
1001
- MU.log "Missing config arguments in setInstanceVariables, can't initialize a cloud object without it", MU::ERR, details: args.keys
1002
- raise MuError, "Missing config arguments in setInstanceVariables"
1003
- end
1004
-
1005
- @deploy = args[:mommacat] || args[:deploy]
1006
-
1007
- @credentials = args[:credentials]
1008
- @credentials ||= @config['credentials']
1009
-
1010
- @cloud = @config['cloud']
1011
- if !@cloud
1012
- if self.class.name.match(/^MU::Cloud::([^:]+)(?:::.+|$)/)
1013
- cloudclass_name = Regexp.last_match[1]
1014
- if MU::Cloud.supportedClouds.include?(cloudclass_name)
1015
- @cloud = cloudclass_name
1016
- end
1017
- end
1018
- end
1019
- if !@cloud
1020
- raise MuError, "Failed to determine what cloud #{self} should be in!"
1021
- end
1022
-
1023
- @environment = @config['environment']
1024
- if @deploy
1025
- @deploy_id = @deploy.deploy_id
1026
- @appname = @deploy.appname
1027
- end
1028
-
1029
- @cloudclass = MU::Cloud.loadCloudType(@cloud, self.class.shortname)
1030
- @cloudparentclass = Object.const_get("MU").const_get("Cloud").const_get(@cloud)
1031
-
1032
- # A pre-existing object, you say?
1033
- if args[:cloud_id]
1034
-
1035
- # TODO implement ::Id for every cloud... and they should know how to get from
1036
- # cloud_desc to a fully-resolved ::Id object, not just the short string
1037
-
1038
- @cloud_id = args[:cloud_id]
1039
- describe(cloud_id: @cloud_id)
1040
- @habitat_id = habitat_id # effectively, cache this
1041
-
1042
- # If we can build us an ::Id object for @cloud_id instead of a
1043
- # string, do so.
1044
- begin
1045
- idclass = Object.const_get("MU").const_get("Cloud").const_get(@cloud).const_get("Id")
1046
- long_id = if @deploydata and @deploydata[idclass.idattr.to_s]
1047
- @deploydata[idclass.idattr.to_s]
1048
- elsif self.respond_to?(idclass.idattr)
1049
- self.send(idclass.idattr)
1050
- end
1051
-
1052
- @cloud_id = idclass.new(long_id) if !long_id.nil? and !long_id.empty?
1053
- # 1 see if we have the value on the object directly or in deploy data
1054
- # 2 set an attr_reader with the value
1055
- # 3 rewrite our @cloud_id attribute with a ::Id object
1056
- rescue NameError, MU::Cloud::MuCloudResourceNotImplemented
1057
- end
1058
-
1059
- end
1060
-
1061
- # Use pre-existing mu_name (we're probably loading an extant deploy)
1062
- # if available
1063
- if args[:mu_name]
1064
- @mu_name = args[:mu_name].dup
1065
- # If scrub_mu_isms is set, our mu_name is always just the bare name
1066
- # field of the resource.
1067
- elsif @config['scrub_mu_isms']
1068
- @mu_name = @config['name'].dup
1069
- # XXX feck it insert an inheritable method right here? Set a default? How should resource implementations determine whether they're instantiating a new object?
1070
- end
1071
-
1072
- @tags = {}
1073
- if !@config['scrub_mu_isms']
1074
- @tags = @deploy ? @deploy.listStandardTags : MU::MommaCat.listStandardTags
1075
- end
1076
- if @config['tags']
1077
- @config['tags'].each { |tag|
1078
- @tags[tag['key']] = tag['value']
1079
- }
1080
- end
1081
-
1082
- if @cloudparentclass.respond_to?(:resourceInitHook)
1083
- @cloudparentclass.resourceInitHook(self, @deploy)
1084
- end
1085
-
1086
- # Add cloud-specific instance methods for our resource objects to
1087
- # inherit.
1088
- if @cloudparentclass.const_defined?(:AdditionalResourceMethods)
1089
- self.extend @cloudparentclass.const_get(:AdditionalResourceMethods)
1090
- end
1091
-
1092
- if ["Server", "ServerPool"].include?(self.class.shortname) and @deploy
1093
- @mu_name ||= @deploy.getResourceName(@config['name'], need_unique_string: @config.has_key?("basis"))
1094
- if self.class.shortname == "Server"
1095
- @groomer = MU::Groomer.new(self)
1096
- end
1097
-
1098
- @groomclass = MU::Groomer.loadGroomer(@config["groomer"])
1099
-
1100
- if windows? or @config['active_directory'] and !@mu_windows_name
1101
- if !@deploydata.nil? and !@deploydata['mu_windows_name'].nil?
1102
- @mu_windows_name = @deploydata['mu_windows_name']
1103
- else
1104
- # Use the same random differentiator as the "real" name if we're
1105
- # from a ServerPool. Helpful for admin sanity.
1106
- unq = @mu_name.sub(/^.*?-(...)$/, '\1')
1107
- if @config['basis'] and !unq.nil? and !unq.empty?
1108
- @mu_windows_name = @deploy.getResourceName(@config['name'], max_length: 15, need_unique_string: true, use_unique_string: unq, reuse_unique_string: true)
1109
- else
1110
- @mu_windows_name = @deploy.getResourceName(@config['name'], max_length: 15, need_unique_string: true)
1111
- end
1112
- end
1113
- end
1114
- class << self
1115
- attr_reader :groomer
1116
- attr_reader :groomerclass
1117
- attr_accessor :mu_windows_name # XXX might be ok as reader now
1118
- end
1119
- end
1120
- end
1121
-
1122
- end
1123
-
1124
- def cloud
1125
- if @cloud
1126
- @cloud
1127
- elsif @config and @config['cloud']
1128
- @config['cloud']
1129
- elsif self.class.name.match(/^MU::Cloud::([^:]+)::.+/)
1130
- cloudclass_name = Regexp.last_match[1]
1131
- if MU::Cloud.supportedClouds.include?(cloudclass_name)
1132
- cloudclass_name
1133
- else
1134
- nil
1135
- end
1136
- else
1137
- nil
1138
- end
1139
- end
1140
-
1141
-
1142
- # Remove all metadata and cloud resources associated with this object
1143
- def destroy
1144
- if self.class.cfg_name == "server"
1145
- begin
1146
- ip = canonicalIP
1147
- MU::Master.removeIPFromSSHKnownHosts(ip) if ip
1148
- if @deploy and @deploy.deployment and
1149
- @deploy.deployment['servers'] and @config['name']
1150
- me = @deploy.deployment['servers'][@config['name']][@mu_name]
1151
- if me
1152
- ["private_ip_address", "public_ip_address"].each { |field|
1153
- if me[field]
1154
- MU::Master.removeIPFromSSHKnownHosts(me[field])
1155
- end
1156
- }
1157
- if me["private_ip_list"]
1158
- me["private_ip_list"].each { |private_ip|
1159
- MU::Master.removeIPFromSSHKnownHosts(private_ip)
1160
- }
1161
- end
1162
- end
1163
- end
1164
- rescue MU::MuError => e
1165
- MU.log e.message, MU::WARN
1166
- end
1167
- end
1168
- if !@cloudobj.nil? and !@cloudobj.groomer.nil?
1169
- @cloudobj.groomer.cleanup
1170
- elsif !@groomer.nil?
1171
- @groomer.cleanup
1172
- end
1173
- if !@deploy.nil?
1174
- if !@cloudobj.nil? and !@config.nil? and !@cloudobj.mu_name.nil?
1175
- @deploy.notify(self.class.cfg_plural, @config['name'], nil, mu_name: @cloudobj.mu_name, remove: true, triggering_node: @cloudobj, delayed_save: @delayed_save)
1176
- elsif !@mu_name.nil?
1177
- @deploy.notify(self.class.cfg_plural, @config['name'], nil, mu_name: @mu_name, remove: true, triggering_node: self, delayed_save: @delayed_save)
1178
- end
1179
- @deploy.removeKitten(self)
1180
- end
1181
- # Make sure that if notify gets called again it won't go returning a
1182
- # bunch of now-bogus metadata.
1183
- @destroyed = true
1184
- if !@cloudobj.nil?
1185
- def @cloudobj.notify
1186
- {}
1187
- end
1188
- else
1189
- def notify
1190
- {}
1191
- end
1192
- end
1193
- end
1194
-
1195
- # Return the cloud object's idea of where it lives (project, account,
1196
- # etc) in the form of an identifier. If not applicable for this object,
1197
- # we expect to return +nil+.
1198
- # @return [String,nil]
1199
- def habitat(nolookup: true)
1200
- return nil if ["folder", "habitat"].include?(self.class.cfg_name)
1201
- if @cloudobj
1202
- @cloudparentclass.habitat(@cloudobj, nolookup: nolookup, deploy: @deploy)
1203
- else
1204
- @cloudparentclass.habitat(self, nolookup: nolookup, deploy: @deploy)
1205
- end
1206
- end
1207
-
1208
- def habitat_id(nolookup: false)
1209
- @habitat_id ||= habitat(nolookup: nolookup)
1210
- @habitat_id
1211
- end
1212
-
1213
- # We're fundamentally a wrapper class, so go ahead and reroute requests
1214
- # that are meant for our wrapped object.
1215
- def method_missing(method_sym, *arguments)
1216
- if @cloudobj
1217
- MU.log "INVOKING #{method_sym.to_s} FROM PARENT CLOUD OBJECT #{self}", MU::DEBUG, details: arguments
1218
- @cloudobj.method(method_sym).call(*arguments)
1219
- else
1220
- raise NoMethodError, "No such instance method #{method_sym.to_s} available on #{self.class.name}"
1221
- end
1222
- end
1223
-
1224
- # Merge the passed hash into the existing configuration hash of this
1225
- # cloud object. Currently this is only used by the {MU::Adoption}
1226
- # module. I don't love exposing this to the whole internal API, but I'm
1227
- # probably overthinking that.
1228
- # @param newcfg [Hash]
1229
- def config!(newcfg)
1230
- @config.merge!(newcfg)
1231
- end
1232
-
1233
- def cloud_desc(use_cache: true)
1234
- describe
1235
-
1236
- if !@cloudobj.nil?
1237
- if @cloudobj.class.instance_methods(false).include?(:cloud_desc)
1238
- @cloud_desc_cache ||= @cloudobj.cloud_desc
1239
- end
1240
- end
1241
- if !@config.nil? and !@cloud_id.nil? and (!use_cache or @cloud_desc_cache.nil?)
1242
- # The find() method should be returning a Hash with the cloud_id
1243
- # as a key and a cloud platform descriptor as the value.
1244
- begin
1245
- args = {
1246
- :region => @config['region'],
1247
- :cloud => @config['cloud'],
1248
- :cloud_id => @cloud_id,
1249
- :credentials => @credentials,
1250
- :project => habitat_id, # XXX this belongs in our required_instance_methods hack
1251
- :flags => @config
1252
- }
1253
- @cloudparentclass.required_instance_methods.each { |m|
1254
- # if respond_to?(m)
1255
- # args[m] = method(m).call
1256
- # else
1257
- args[m] = instance_variable_get(("@"+m.to_s).to_sym)
1258
- # end
1259
- }
1260
-
1261
- matches = self.class.find(args)
1262
- if !matches.nil? and matches.is_a?(Hash)
1263
- # XXX or if the hash is keyed with an ::Id element, oh boy
1264
- # puts matches[@cloud_id][:self_link]
1265
- # puts matches[@cloud_id][:url]
1266
- # if matches[@cloud_id][:self_link]
1267
- # @url ||= matches[@cloud_id][:self_link]
1268
- # elsif matches[@cloud_id][:url]
1269
- # @url ||= matches[@cloud_id][:url]
1270
- # elsif matches[@cloud_id][:arn]
1271
- # @arn ||= matches[@cloud_id][:arn]
1272
- # end
1273
- if matches[@cloud_id]
1274
- @cloud_desc_cache = matches[@cloud_id]
1275
- else
1276
- matches.each_pair { |k, v| # flatten out ::Id objects just in case
1277
- if @cloud_id.to_s == k.to_s
1278
- @cloud_desc_cache = v
1279
- break
1280
- end
1281
- }
1282
- end
1283
- end
1284
-
1285
- if !@cloud_desc_cache
1286
- MU.log "cloud_desc via #{self.class.name}.find() failed to locate a live object.\nWas called by #{caller[0]}", MU::WARN, details: args
1287
- end
1288
- rescue StandardError => e
1289
- MU.log "Got #{e.inspect} trying to find cloud handle for #{self.class.shortname} #{@mu_name} (#{@cloud_id})", MU::WARN
1290
- raise e
1291
- end
1292
- end
1293
-
1294
- return @cloud_desc_cache
1295
- end
1296
-
1297
- # Retrieve all of the known metadata for this resource.
1298
- # @param cloud_id [String]: The cloud platform's identifier for the resource we're describing. Makes lookups more efficient.
1299
- # @return [Array<Hash>]: mu_name, config, deploydata
1300
- def describe(cloud_id: nil)
1301
- if cloud_id.nil? and !@cloudobj.nil?
1302
- @cloud_id ||= @cloudobj.cloud_id
1303
- end
1304
- res_type = self.class.cfg_plural
1305
- res_name = @config['name'] if !@config.nil?
1306
- @credentials ||= @config['credentials'] if !@config.nil?
1307
- deploydata = nil
1308
- if !@deploy.nil? and @deploy.is_a?(MU::MommaCat) and
1309
- !@deploy.deployment.nil? and
1310
- !@deploy.deployment[res_type].nil? and
1311
- !@deploy.deployment[res_type][res_name].nil?
1312
- deploydata = @deploy.deployment[res_type][res_name]
1313
- else
1314
- # XXX This should only happen on a brand new resource, but we should
1315
- # probably complain under other circumstances, if we can
1316
- # differentiate them.
1317
- end
1318
-
1319
- if self.class.has_multiples and !@mu_name.nil? and deploydata.is_a?(Hash) and deploydata.has_key?(@mu_name)
1320
- @deploydata = deploydata[@mu_name]
1321
- elsif deploydata.is_a?(Hash)
1322
- @deploydata = deploydata
1323
- end
1324
-
1325
- if @cloud_id.nil? and @deploydata.is_a?(Hash)
1326
- if @mu_name.nil? and @deploydata.has_key?('#MU_NAME')
1327
- @mu_name = @deploydata['#MU_NAME']
1328
- end
1329
- if @deploydata.has_key?('cloud_id')
1330
- @cloud_id ||= @deploydata['cloud_id']
1331
- end
1332
- end
1333
-
1334
- return [@mu_name, @config, @deploydata]
1335
- end
1336
-
1337
- # Fetch MU::Cloud objects for each of this object's dependencies, and
1338
- # return in an easily-navigable Hash. This can include things listed in
1339
- # @config['dependencies'], implicitly-defined dependencies such as
1340
- # add_firewall_rules or vpc stanzas, and may refer to objects internal
1341
- # to this deployment or external. Will populate the instance variables
1342
- # @dependencies (general dependencies, which can only be sibling
1343
- # resources in this deployment), as well as for certain config stanzas
1344
- # which can refer to external resources (@vpc, @loadbalancers,
1345
- # @add_firewall_rules)
1346
- def dependencies(use_cache: false, debug: false)
1347
- @dependencies ||= {}
1348
- @loadbalancers ||= []
1349
- @firewall_rules ||= []
1350
-
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
- # Munge in external resources referenced by the existing_deploys
1566
- # keyword
1567
- if @config["existing_deploys"] && !@config["existing_deploys"].empty?
1568
- @config["existing_deploys"].each { |ext_deploy|
1569
- if ext_deploy["cloud_id"]
1570
- found = MU::MommaCat.findStray(
1571
- @config['cloud'],
1572
- ext_deploy["cloud_type"],
1573
- cloud_id: ext_deploy["cloud_id"],
1574
- region: @config['region'],
1575
- dummy_ok: false
1576
- ).first
1577
-
1578
- MU.log "Couldn't find existing resource #{ext_deploy["cloud_id"]}, #{ext_deploy["cloud_type"]}", MU::ERR if found.nil?
1579
- @deploy.notify(ext_deploy["cloud_type"], found.config["name"], found.deploydata, mu_name: found.mu_name, triggering_node: @mu_name)
1580
- elsif ext_deploy["mu_name"] && ext_deploy["deploy_id"]
1581
- MU.log "#{ext_deploy["mu_name"]} / #{ext_deploy["deploy_id"]}"
1582
- found = MU::MommaCat.findStray(
1583
- @config['cloud'],
1584
- ext_deploy["cloud_type"],
1585
- deploy_id: ext_deploy["deploy_id"],
1586
- mu_name: ext_deploy["mu_name"],
1587
- region: @config['region'],
1588
- dummy_ok: false
1589
- ).first
1590
-
1591
- MU.log "Couldn't find existing resource #{ext_deploy["mu_name"]}/#{ext_deploy["deploy_id"]}, #{ext_deploy["cloud_type"]}", MU::ERR if found.nil?
1592
- @deploy.notify(ext_deploy["cloud_type"], found.config["name"], found.deploydata, mu_name: ext_deploy["mu_name"], triggering_node: @mu_name)
1593
- else
1594
- MU.log "Trying to find existing deploy, but either the cloud_id is not valid or no mu_name and deploy_id where provided", MU::ERR
1595
- end
1596
- }
1597
- end
1598
-
1599
- if @config['dns_records'] && !@config['dns_records'].empty?
1600
- @config['dns_records'].each { |dnsrec|
1601
- if dnsrec.has_key?("name")
1602
- if dnsrec['name'].start_with?(@deploy.deploy_id.downcase) && !dnsrec['name'].start_with?(@mu_name.downcase)
1603
- MU.log "DNS records for #{@mu_name} seem to be wrong, deleting from current config", MU::WARN, details: dnsrec
1604
- dnsrec.delete('name')
1605
- dnsrec.delete('target')
1606
- end
1607
- end
1608
- }
1609
- end
1610
-
1611
- return [@dependencies, @vpc, @loadbalancers]
1612
- end
1613
-
1614
- # Using the automatically-defined +@vpc+ from {dependencies} in
1615
- # conjunction with our config, return our configured subnets.
1616
- # @return [Array<MU::Cloud::VPC::Subnet>]
1617
- def mySubnets
1618
- dependencies
1619
- if !@vpc or !@config["vpc"]
1620
- return nil
1621
- end
1622
-
1623
- if @config["vpc"]["subnet_id"] or @config["vpc"]["subnet_name"]
1624
- @config["vpc"]["subnets"] ||= []
1625
- subnet_block = {}
1626
- subnet_block["subnet_id"] = @config["vpc"]["subnet_id"] if @config["vpc"]["subnet_id"]
1627
- subnet_block["subnet_name"] = @config["vpc"]["subnet_name"] if @config["vpc"]["subnet_name"]
1628
- @config["vpc"]["subnets"] << subnet_block
1629
- @config["vpc"]["subnets"].uniq!
1630
- end
1631
-
1632
- if (!@config["vpc"]["subnets"] or @config["vpc"]["subnets"].empty?) and
1633
- !@config["vpc"]["subnet_id"]
1634
- return @vpc.subnets
1635
- end
1636
-
1637
- subnets = []
1638
- @config["vpc"]["subnets"].each { |subnet|
1639
- subnet_obj = @vpc.getSubnet(cloud_id: subnet["subnet_id"].to_s, name: subnet["subnet_name"].to_s)
1640
- raise MuError, "Couldn't find a live subnet for #{self.to_s} matching #{subnet} in #{@vpc.to_s} (#{@vpc.subnets.map { |s| s.name }.join(",")})" if subnet_obj.nil?
1641
- subnets << subnet_obj
1642
- }
1643
-
1644
- subnets
1645
- end
1646
-
1647
- # @return [Array<MU::Cloud::FirewallRule>]
1648
- def myFirewallRules
1649
- dependencies
1650
-
1651
- rules = []
1652
- if @dependencies.has_key?("firewall_rule")
1653
- rules = @dependencies['firewall_rule'].values
1654
- end
1655
- # XXX what other ways are these specified?
1656
-
1657
- rules
1658
- end
1659
-
1660
- # Defaults any resources that don't declare their release-readiness to
1661
- # ALPHA. That'll learn 'em.
1662
- def self.quality
1663
- MU::Cloud::ALPHA
1664
- end
1665
-
1666
- # Return a list of "container" artifacts, by class, that apply to this
1667
- # resource type in a cloud provider. This is so methods that call find
1668
- # know whether to call +find+ with identifiers for parent resources.
1669
- # This is similar in purpose to the +isGlobal?+ resource class method,
1670
- # which tells our search functions whether or not a resource scopes to
1671
- # a region. In almost all cases this is one-entry list consisting of
1672
- # +:Habitat+. Notable exceptions include most implementations of
1673
- # +Habitat+, which either reside inside a +:Folder+ or nothing at all;
1674
- # whereas a +:Folder+ tends to not have any containing parent. Very few
1675
- # resource implementations will need to override this.
1676
- # A +nil+ entry in this list is interpreted as "this resource can be
1677
- # global."
1678
- # @return [Array<Symbol,nil>]
1679
- def self.canLiveIn
1680
- if self.shortname == "Folder"
1681
- [nil, :Folder]
1682
- elsif self.shortname == "Habitat"
1683
- [:Folder]
1684
- else
1685
- [:Habitat]
1686
- end
1687
- end
1688
-
1689
- def self.find(*flags)
1690
- allfound = {}
1691
-
1692
- MU::Cloud.availableClouds.each { |cloud|
1693
- begin
1694
- args = flags.first
1695
- next if args[:cloud] and args[:cloud] != cloud
1696
- # skip this cloud if we have a region argument that makes no
1697
- # sense there
1698
- cloudbase = Object.const_get("MU").const_get("Cloud").const_get(cloud)
1699
- next if cloudbase.listCredentials.nil? or cloudbase.listCredentials.empty? or cloudbase.credConfig(args[:credentials]).nil?
1700
- if args[:region] and cloudbase.respond_to?(:listRegions)
1701
- if !cloudbase.listRegions(credentials: args[:credentials])
1702
- MU.log "Failed to get region list for credentials #{args[:credentials]} in cloud #{cloud}", MU::ERR, details: caller
1703
- else
1704
- next if !cloudbase.listRegions(credentials: args[:credentials]).include?(args[:region])
1705
- end
1706
- end
1707
- begin
1708
- cloudclass = MU::Cloud.loadCloudType(cloud, shortname)
1709
- rescue MU::MuError
1710
- next
1711
- end
1712
-
1713
- found = cloudclass.find(args)
1714
- if !found.nil?
1715
- if found.is_a?(Hash)
1716
- allfound.merge!(found)
1717
- else
1718
- raise MuError, "#{cloudclass}.find returned a non-Hash result"
1719
- end
1720
- end
1721
- rescue MuCloudResourceNotImplemented
1722
- end
1723
- }
1724
- allfound
1725
- end
1726
-
1727
- if shortname == "DNSZone"
1728
- def self.genericMuDNSEntry(*flags)
1729
- # XXX have this switch on a global config for where Mu puts its DNS
1730
- cloudclass = MU::Cloud.loadCloudType(MU::Config.defaultCloud, "DNSZone")
1731
- cloudclass.genericMuDNSEntry(flags.first)
1732
- end
1733
- def self.createRecordsFromConfig(*flags)
1734
- cloudclass = MU::Cloud.loadCloudType(MU::Config.defaultCloud, "DNSZone")
1735
- if !flags.nil? and flags.size == 1
1736
- cloudclass.createRecordsFromConfig(flags.first)
1737
- else
1738
- cloudclass.createRecordsFromConfig(*flags)
1739
- end
1740
- end
1741
- end
1742
-
1743
- if shortname == "Server" or shortname == "ServerPool"
1744
- def windows?
1745
- return true if %w{win2k16 win2k12r2 win2k12 win2k8 win2k8r2 win2k19 windows}.include?(@config['platform'])
1746
- begin
1747
- return true if cloud_desc.respond_to?(:platform) and cloud_desc.platform == "Windows"
1748
- # 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?
1749
- rescue MU::MuError
1750
- return false
1751
- end
1752
- false
1753
- end
1754
-
1755
- # Gracefully message and attempt to accommodate the common transient errors peculiar to Windows nodes
1756
- # @param e [Exception]: The exception that we're handling
1757
- # @param retries [Integer]: The current number of retries, which we'll increment and pass back to the caller
1758
- # @param rebootable_fails [Integer]: The current number of reboot-worthy failures, which we'll increment and pass back to the caller
1759
- # @param max_retries [Integer]: Maximum number of retries to attempt; we'll raise an exception if this is exceeded
1760
- # @param reboot_on_problems [Boolean]: Whether we should try to reboot a "stuck" machine
1761
- # @param retry_interval [Integer]: How many seconds to wait before returning for another attempt
1762
- def handleWindowsFail(e, retries, rebootable_fails, max_retries: 30, reboot_on_problems: false, retry_interval: 45)
1763
- msg = "WinRM connection to https://"+@mu_name+":5986/wsman: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})"
1764
- if e.class.name == "WinRM::WinRMAuthorizationError" or e.message.match(/execution expired/) and reboot_on_problems
1765
- if rebootable_fails > 0 and (rebootable_fails % 7) == 0
1766
- MU.log "#{@mu_name} still misbehaving, forcing Stop and Start from API", MU::WARN
1767
- reboot(true) # vicious API stop/start
1768
- sleep retry_interval*3
1769
- rebootable_fails = 0
1770
- else
1771
- if rebootable_fails == 5
1772
- MU.log "#{@mu_name} misbehaving, attempting to reboot from API", MU::WARN
1773
- reboot # graceful API restart
1774
- sleep retry_interval*2
1775
- end
1776
- rebootable_fails = rebootable_fails + 1
1777
- end
1778
- end
1779
- if retries < max_retries
1780
- if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0 and retries != 0)
1781
- MU.log msg, MU::NOTICE
1782
- elsif retries/max_retries > 0.5
1783
- MU.log msg, MU::WARN, details: e.inspect
1784
- end
1785
- sleep retry_interval
1786
- retries = retries + 1
1787
- else
1788
- raise MuError, "#{@mu_name}: #{e.inspect} trying to connect with WinRM, max_retries exceeded", e.backtrace
1789
- end
1790
- return [retries, rebootable_fails]
1791
- end
1792
-
1793
- def windowsRebootPending?(shell = nil)
1794
- if shell.nil?
1795
- shell = getWinRMSession(1, 30)
1796
- end
1797
- # if (Get-Item "HKLM:/SOFTWARE/Microsoft/Windows/CurrentVersion/WindowsUpdate/Auto Update/RebootRequired" -EA Ignore) { exit 1 }
1798
- cmd = %Q{
1799
- if (Get-ChildItem "HKLM:/Software/Microsoft/Windows/CurrentVersion/Component Based Servicing/RebootPending" -EA Ignore) {
1800
- echo "Component Based Servicing/RebootPending is true"
1801
- exit 1
1802
- }
1803
- if (Get-ItemProperty "HKLM:/SYSTEM/CurrentControlSet/Control/Session Manager" -Name PendingFileRenameOperations -EA Ignore) {
1804
- echo "Control/Session Manager/PendingFileRenameOperations is true"
1805
- exit 1
1806
- }
1807
- try {
1808
- $util = [wmiclass]"\\\\.\\root\\ccm\\clientsdk:CCM_ClientUtilities"
1809
- $status = $util.DetermineIfRebootPending()
1810
- if(($status -ne $null) -and $status.RebootPending){
1811
- echo "WMI says RebootPending is true"
1812
- exit 1
1813
- }
1814
- } catch {
1815
- exit 0
1816
- }
1817
- exit 0
1818
- }
1819
- resp = shell.run(cmd)
1820
- returnval = resp.exitcode == 0 ? false : true
1821
- shell.close
1822
- returnval
1823
- end
1824
-
1825
- # Basic setup tasks performed on a new node during its first WinRM
1826
- # connection. Most of this is terrible Windows glue.
1827
- # @param shell [WinRM::Shells::Powershell]: An active Powershell session to the new node.
1828
- def initialWinRMTasks(shell)
1829
- retries = 0
1830
- rebootable_fails = 0
1831
- begin
1832
- if !@config['use_cloud_provider_windows_password']
1833
- pw = @groomer.getSecret(
1834
- vault: @config['mu_name'],
1835
- item: "windows_credentials",
1836
- field: "password"
1837
- )
1838
- 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}
1839
- resp = shell.run(win_check_for_pw)
1840
- if resp.stdout.chomp != "True"
1841
- win_set_pw = %Q{(([adsi]('WinNT://./#{@config["windows_admin_username"]}, user')).psbase.invoke('SetPassword', '#{pw}'))}
1842
- resp = shell.run(win_set_pw)
1843
- puts resp.stdout
1844
- MU.log "Resetting Windows host password", MU::NOTICE, details: resp.stdout
1845
- end
1846
- end
1847
-
1848
- # Install Cygwin here, because for some reason it breaks inside Chef
1849
- # XXX would love to not do this here
1850
- pkgs = ["bash", "mintty", "vim", "curl", "openssl", "wget", "lynx", "openssh"]
1851
- admin_home = "c:/bin/cygwin/home/#{@config["windows_admin_username"]}"
1852
- install_cygwin = %Q{
1853
- If (!(Test-Path "c:/bin/cygwin/Cygwin.bat")){
1854
- $WebClient = New-Object System.Net.WebClient
1855
- $WebClient.DownloadFile("http://cygwin.com/setup-x86_64.exe","$env:Temp/setup-x86_64.exe")
1856
- 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(',')}"
1857
- }
1858
- if(!(Test-Path #{admin_home})){
1859
- New-Item -type directory -path #{admin_home}
1860
- }
1861
- if(!(Test-Path #{admin_home}/.ssh)){
1862
- New-Item -type directory -path #{admin_home}/.ssh
1863
- }
1864
- if(!(Test-Path #{admin_home}/.ssh/authorized_keys)){
1865
- New-Item #{admin_home}/.ssh/authorized_keys -type file -force -value "#{@deploy.ssh_public_key}"
1866
- }
1867
- }
1868
- resp = shell.run(install_cygwin)
1869
- if resp.exitcode != 0
1870
- MU.log "Failed at installing Cygwin", MU::ERR, details: resp
1871
- end
1872
-
1873
- hostname = nil
1874
- if !@config['active_directory'].nil?
1875
- if @config['active_directory']['node_type'] == "domain_controller" && @config['active_directory']['domain_controller_hostname']
1876
- hostname = @config['active_directory']['domain_controller_hostname']
1877
- @mu_windows_name = hostname
1878
- else
1879
- # Do we have an AD specific hostname?
1880
- hostname = @mu_windows_name
1881
- end
1882
- else
1883
- hostname = @mu_windows_name
1884
- end
1885
- resp = shell.run(%Q{hostname})
1886
-
1887
- if resp.stdout.chomp != hostname
1888
- resp = shell.run(%Q{Rename-Computer -NewName '#{hostname}' -Force -PassThru -Restart; Restart-Computer -Force})
1889
- MU.log "Renaming Windows host to #{hostname}; this will trigger a reboot", MU::NOTICE, details: resp.stdout
1890
- reboot(true)
1891
- sleep 30
1892
- end
1893
- rescue WinRM::WinRMError, HTTPClient::ConnectTimeoutError => e
1894
- retries, rebootable_fails = handleWindowsFail(e, retries, rebootable_fails, max_retries: 10, reboot_on_problems: true, retry_interval: 30)
1895
- retry
1896
- end
1897
- end
1898
-
1899
-
1900
- # Basic setup tasks performed on a new node during its first initial
1901
- # ssh connection. Most of this is terrible Windows glue.
1902
- # @param ssh [Net::SSH::Connection::Session]: The active SSH session to the new node.
1903
- def initialSSHTasks(ssh)
1904
- 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" )}
1905
- win_installer_check = %q{ls /proc/registry/HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Installer/}
1906
- lnx_installer_check = %q{ps auxww | awk '{print $11}' | egrep '(/usr/bin/yum|apt-get|dpkg)'}
1907
- lnx_updates_check = %q{( test -f /.mu-installer-ran-updates || ! test -d /var/lib/cloud/instance ) || echo "userdata still running"}
1908
- win_set_pw = nil
1909
-
1910
- if windows? and !@config['use_cloud_provider_windows_password']
1911
- # 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
1912
- pw = @groomer.getSecret(
1913
- vault: @config['mu_name'],
1914
- item: "windows_credentials",
1915
- field: "password"
1916
- )
1917
- 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}'}
1918
- win_set_pw = %Q{powershell -Command "& {(([adsi]('WinNT://./#{@config["windows_admin_username"]}, user')).psbase.invoke('SetPassword', '#{pw}'))}"}
1919
- end
1920
-
1921
- # There shouldn't be a use case where a domain joined computer goes through initialSSHTasks. Removing Active Directory specific computer rename.
1922
- set_hostname = true
1923
- hostname = nil
1924
- if !@config['active_directory'].nil?
1925
- if @config['active_directory']['node_type'] == "domain_controller" && @config['active_directory']['domain_controller_hostname']
1926
- hostname = @config['active_directory']['domain_controller_hostname']
1927
- @mu_windows_name = hostname
1928
- set_hostname = true
1929
- else
1930
- # Do we have an AD specific hostname?
1931
- hostname = @mu_windows_name
1932
- set_hostname = true
1933
- end
1934
- else
1935
- hostname = @mu_windows_name
1936
- end
1937
- win_check_for_hostname = %Q{powershell -Command '& {hostname}'}
1938
- win_set_hostname = %Q{powershell -Command "& {Rename-Computer -NewName '#{hostname}' -Force -PassThru -Restart; Restart-Computer -Force }"}
1939
-
1940
- begin
1941
- # Set our admin password first, if we need to
1942
- if windows? and !win_set_pw.nil? and !win_check_for_pw.nil?
1943
- output = ssh.exec!(win_check_for_pw)
1944
- raise MU::Cloud::BootstrapTempFail, "Got nil output from ssh session, waiting and retrying" if output.nil?
1945
- if !output.match(/True/)
1946
- MU.log "Setting Windows password for user #{@config['windows_admin_username']}", details: ssh.exec!(win_set_pw)
1947
- end
1948
- end
1949
- if windows?
1950
- output = ssh.exec!(win_env_fix)
1951
- output += ssh.exec!(win_installer_check)
1952
- raise MU::Cloud::BootstrapTempFail, "Got nil output from ssh session, waiting and retrying" if output.nil?
1953
- if output.match(/InProgress/)
1954
- raise MU::Cloud::BootstrapTempFail, "Windows Installer service is still doing something, need to wait"
1955
- end
1956
- if set_hostname and !@hostname_set and @mu_windows_name
1957
- output = ssh.exec!(win_check_for_hostname)
1958
- raise MU::Cloud::BootstrapTempFail, "Got nil output from ssh session, waiting and retrying" if output.nil?
1959
- if !output.match(/#{@mu_windows_name}/)
1960
- MU.log "Setting Windows hostname to #{@mu_windows_name}", details: ssh.exec!(win_set_hostname)
1961
- @hostname_set = true
1962
- # Reboot from the API too, in case Windows is flailing
1963
- if !@cloudobj.nil?
1964
- @cloudobj.reboot
1965
- else
1966
- reboot
1967
- end
1968
- raise MU::Cloud::BootstrapTempFail, "Set hostname in Windows, waiting for reboot"
1969
- end
1970
- end
1971
- else
1972
- output = ssh.exec!(lnx_installer_check)
1973
- if !output.nil? and !output.empty?
1974
- raise MU::Cloud::BootstrapTempFail, "Linux package manager is still doing something, need to wait (#{output})"
1975
- end
1976
- if !@config['skipinitialupdates'] and
1977
- !@config['scrub_mu_isms'] and
1978
- !@config['userdata_script']
1979
- output = ssh.exec!(lnx_updates_check)
1980
- if !output.nil? and output.match(/userdata still running/)
1981
- raise MU::Cloud::BootstrapTempFail, "Waiting for initial userdata system updates to complete"
1982
- end
1983
- end
1984
- end
1985
- rescue RuntimeError => e
1986
- raise MU::Cloud::BootstrapTempFail, "Got #{e.inspect} performing initial SSH connect tasks, will try again"
1987
- end
1988
-
1989
- end
1990
-
1991
- # Get a privileged Powershell session on the server in question, using SSL-encrypted WinRM with certificate authentication.
1992
- # @param max_retries [Integer]:
1993
- # @param retry_interval [Integer]:
1994
- # @param timeout [Integer]:
1995
- # @param winrm_retries [Integer]:
1996
- # @param reboot_on_problems [Boolean]:
1997
- def getWinRMSession(max_retries = 40, retry_interval = 60, timeout: 30, winrm_retries: 2, reboot_on_problems: false)
1998
- _nat_ssh_key, _nat_ssh_user, _nat_ssh_host, canonical_ip, _ssh_user, _ssh_key_name = getSSHConfig
1999
- @mu_name ||= @config['mu_name']
2000
-
2001
- shell = nil
2002
- opts = nil
2003
- # and now, a thing I really don't want to do
2004
- MU::Master.addInstanceToEtcHosts(canonical_ip, @mu_name)
2005
-
2006
- # catch exceptions that circumvent our regular call stack
2007
- Thread.abort_on_exception = false
2008
- Thread.handle_interrupt(WinRM::WinRMWSManFault => :never) {
2009
- begin
2010
- Thread.handle_interrupt(WinRM::WinRMWSManFault => :immediate) {
2011
- MU.log "(Probably harmless) Caught a WinRM::WinRMWSManFault in #{Thread.current.inspect}", MU::DEBUG, details: Thread.current.backtrace
2012
- }
2013
- ensure
2014
- # Reraise something useful
2015
- end
2016
- }
2017
-
2018
- retries = 0
2019
- rebootable_fails = 0
2020
- begin
2021
- loglevel = retries > 4 ? MU::NOTICE : MU::DEBUG
2022
- MU.log "Calling WinRM on #{@mu_name}", loglevel, details: opts
2023
- opts = {
2024
- endpoint: 'https://'+@mu_name+':5986/wsman',
2025
- retry_limit: winrm_retries,
2026
- 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
2027
- ca_trust_path: "#{MU.mySSLDir}/Mu_CA.pem",
2028
- transport: :ssl,
2029
- operation_timeout: timeout,
2030
- }
2031
- if retries % 2 == 0
2032
- opts[:user] = @config['windows_admin_username']
2033
- opts[:password] = getWindowsAdminPassword
2034
- else
2035
- opts[:client_cert] = "#{MU.mySSLDir}/#{@mu_name}-winrm.crt"
2036
- opts[:client_key] = "#{MU.mySSLDir}/#{@mu_name}-winrm.key"
2037
- end
2038
- conn = WinRM::Connection.new(opts)
2039
- conn.logger.level = :debug if retries > 2
2040
- MU.log "WinRM connection to #{@mu_name} created", MU::DEBUG, details: conn
2041
- shell = conn.shell(:powershell)
2042
- shell.run('ipconfig') # verify that we can do something
2043
- rescue Errno::EHOSTUNREACH, Errno::ECONNREFUSED, HTTPClient::ConnectTimeoutError, OpenSSL::SSL::SSLError, SocketError, WinRM::WinRMError, Timeout::Error => e
2044
- retries, rebootable_fails = handleWindowsFail(e, retries, rebootable_fails, max_retries: max_retries, reboot_on_problems: reboot_on_problems, retry_interval: retry_interval)
2045
- retry
2046
- ensure
2047
- MU::Master.removeInstanceFromEtcHosts(@mu_name)
2048
- end
2049
-
2050
- shell
2051
- end
2052
-
2053
- # @param max_retries [Integer]: Number of connection attempts to make before giving up
2054
- # @param retry_interval [Integer]: Number of seconds to wait between connection attempts
2055
- # @return [Net::SSH::Connection::Session]
2056
- def getSSHSession(max_retries = 12, retry_interval = 30)
2057
- ssh_keydir = Etc.getpwnam(@deploy.mu_user).dir+"/.ssh"
2058
- nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, _ssh_key_name = getSSHConfig
2059
- session = nil
2060
- retries = 0
2061
-
2062
- vpc_class = Object.const_get("MU").const_get("Cloud").const_get(@cloud).const_get("VPC")
2063
-
2064
- # XXX WHY is this a thing
2065
- Thread.handle_interrupt(Errno::ECONNREFUSED => :never) {
2066
- }
2067
-
2068
- begin
2069
- MU::Cloud.handleNetSSHExceptions
2070
- if !nat_ssh_host.nil?
2071
- proxy_cmd = "ssh -q -o StrictHostKeyChecking=no -W %h:%p #{nat_ssh_user}@#{nat_ssh_host}"
2072
- 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
2073
- proxy = Net::SSH::Proxy::Command.new(proxy_cmd)
2074
- session = Net::SSH.start(
2075
- canonical_ip,
2076
- ssh_user,
2077
- :config => false,
2078
- :keys_only => true,
2079
- :keys => [ssh_keydir+"/"+nat_ssh_key, ssh_keydir+"/"+@deploy.ssh_key_name],
2080
- :verify_host_key => false,
2081
- # :verbose => :info,
2082
- :host_key => "ssh-rsa",
2083
- :port => 22,
2084
- :auth_methods => ['publickey'],
2085
- :proxy => proxy
2086
- )
2087
- else
2088
-
2089
- MU.log "Attempting SSH to #{canonical_ip} (#{@mu_name}) as #{ssh_user} with key #{ssh_keydir}/#{@deploy.ssh_key_name}" if retries == 0
2090
- session = Net::SSH.start(
2091
- canonical_ip,
2092
- ssh_user,
2093
- :config => false,
2094
- :keys_only => true,
2095
- :keys => [ssh_keydir+"/"+@deploy.ssh_key_name],
2096
- :verify_host_key => false,
2097
- # :verbose => :info,
2098
- :host_key => "ssh-rsa",
2099
- :port => 22,
2100
- :auth_methods => ['publickey']
2101
- )
2102
- end
2103
- retries = 0
2104
- rescue Net::SSH::HostKeyMismatch => e
2105
- MU.log("Remembering new key: #{e.fingerprint}")
2106
- e.remember_host!
2107
- session.close
2108
- retry
2109
- # 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
2110
- rescue SystemExit, Timeout::Error, Net::SSH::AuthenticationFailed, Net::SSH::Disconnect, Net::SSH::ConnectionTimeout, Net::SSH::Proxy::ConnectError, Net::SSH::Exception, Errno::ECONNRESET, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Errno::EPIPE, SocketError, IOError => e
2111
- begin
2112
- session.close if !session.nil?
2113
- rescue Net::SSH::Disconnect, IOError => e
2114
- if windows?
2115
- MU.log "Windows has probably closed the ssh session before we could. Waiting before trying again", MU::NOTICE
2116
- else
2117
- MU.log "ssh session was closed unexpectedly, waiting before trying again", MU::NOTICE
2118
- end
2119
- sleep 10
2120
- end
2121
-
2122
- if retries < max_retries
2123
- retries = retries + 1
2124
- msg = "ssh #{ssh_user}@#{@mu_name}: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})"
2125
- if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0)
2126
- MU.log msg, MU::NOTICE
2127
- if !vpc_class.haveRouteToInstance?(cloud_desc, credentials: @credentials) and
2128
- canonical_ip.match(/(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1$)|(^[fF][cCdD])/) and
2129
- !nat_ssh_host
2130
- 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
2131
- end
2132
- elsif retries/max_retries > 0.5
2133
- MU.log msg, MU::WARN, details: e.inspect
2134
- end
2135
- sleep retry_interval
2136
- retry
2137
- else
2138
- raise MuError, "#{@mu_name}: #{e.inspect} trying to connect with SSH, max_retries exceeded", e.backtrace
2139
- end
2140
- end
2141
- return session
2142
- end
2143
- end
2144
-
2145
- # Wrapper for the cleanup class method of underlying cloud object implementations.
2146
- def self.cleanup(*flags)
2147
- ok = true
2148
- params = flags.first
2149
- clouds = MU::Cloud.supportedClouds
2150
- if params[:cloud]
2151
- clouds = [params[:cloud]]
2152
- params.delete(:cloud)
2153
- end
2154
-
2155
- clouds.each { |cloud|
2156
- begin
2157
- cloudclass = MU::Cloud.loadCloudType(cloud, shortname)
2158
-
2159
- if cloudclass.isGlobal?
2160
- params.delete(:region)
2161
- end
2162
-
2163
- raise MuCloudResourceNotImplemented if !cloudclass.respond_to?(:cleanup) or cloudclass.method(:cleanup).owner.to_s != "#<Class:#{cloudclass}>"
2164
- MU.log "Invoking #{cloudclass}.cleanup from #{shortname}", MU::DEBUG, details: flags
2165
- cloudclass.cleanup(params)
2166
- rescue MuCloudResourceNotImplemented
2167
- MU.log "No #{cloud} implementation of #{shortname}.cleanup, skipping", MU::DEBUG, details: flags
2168
- rescue StandardError => e
2169
- in_msg = cloud
2170
- if params and params[:region]
2171
- in_msg += " "+params[:region]
2172
- end
2173
- if params and params[:flags] and params[:flags]["project"] and !params[:flags]["project"].empty?
2174
- in_msg += " project "+params[:flags]["project"]
2175
- end
2176
- MU.log "Skipping #{shortname} cleanup method in #{in_msg} due to #{e.class.name}: #{e.message}", MU::WARN, details: e.backtrace
2177
- ok = false
2178
- end
2179
- }
2180
- MU::MommaCat.unlockAll
2181
-
2182
- ok
2183
- end
2184
-
2185
- # A hook that is always called just before each instance method is
2186
- # invoked, so that we can ensure that repetitive setup tasks (like
2187
- # resolving +:resource_group+ for Azure resources) have always been
2188
- # done.
2189
- def resourceInitHook
2190
- @cloud ||= cloud
2191
- if @cloudparentclass.respond_to?(:resourceInitHook)
2192
- @cloudparentclass.resourceInitHook(@cloudobj, @deploy)
2193
- end
2194
- end
2195
-
2196
- # Wrap the instance methods that this cloud resource type has to
2197
- # implement.
2198
- MU::Cloud.resource_types[name.to_sym][:instance].each { |method|
2199
- define_method method do |*args|
2200
- return nil if @cloudobj.nil?
2201
- MU.log "Invoking #{@cloudobj}.#{method}", MU::DEBUG
2202
-
2203
- # Go ahead and guarantee that we can't accidentally trigger these
2204
- # methods recursively.
2205
- @method_semaphore.synchronize {
2206
- # We're looking for recursion, not contention, so ignore some
2207
- # obviously harmless things.
2208
- if @method_locks.has_key?(method) and method != :findBastion and method != :cloud_id
2209
- MU.log "Double-call to cloud method #{method} for #{self}", MU::DEBUG, details: caller + ["competing call stack:"] + @method_locks[method]
2210
- end
2211
- @method_locks[method] = caller
2212
- }
2213
-
2214
- # Make sure the describe() caches are fresh
2215
- @cloudobj.describe if method != :describe
2216
-
2217
- # Don't run through dependencies on simple attr_reader lookups
2218
- if ![:dependencies, :cloud_id, :config, :mu_name].include?(method)
2219
- @cloudobj.dependencies
2220
- end
2221
-
2222
- retval = nil
2223
- if !args.nil? and args.size == 1
2224
- retval = @cloudobj.method(method).call(args.first)
2225
- elsif !args.nil? and args.size > 0
2226
- retval = @cloudobj.method(method).call(*args)
2227
- else
2228
- retval = @cloudobj.method(method).call
2229
- end
2230
- if (method == :create or method == :groom or method == :postBoot) and
2231
- (!@destroyed and !@cloudobj.destroyed)
2232
- deploydata = @cloudobj.method(:notify).call
2233
- @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?
2234
- if deploydata.nil? or !deploydata.is_a?(Hash)
2235
- 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
2236
- deploydata = MU.structToHash(@cloudobj.cloud_desc)
2237
- raise MuError, "Failed to collect metadata about #{self}" if deploydata.nil?
2238
- end
2239
- deploydata['cloud_id'] ||= @cloudobj.cloud_id if !@cloudobj.cloud_id.nil?
2240
- deploydata['mu_name'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
2241
- deploydata['nodename'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
2242
- deploydata.delete("#MUOBJECT")
2243
- @deploy.notify(self.class.cfg_plural, @config['name'], deploydata, triggering_node: @cloudobj, delayed_save: @delayed_save) if !@deploy.nil?
2244
- elsif method == :notify
2245
- retval['cloud_id'] = @cloudobj.cloud_id.to_s if !@cloudobj.cloud_id.nil?
2246
- retval['mu_name'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
2247
- @deploy.notify(self.class.cfg_plural, @config['name'], retval, triggering_node: @cloudobj, delayed_save: @delayed_save) if !@deploy.nil?
2248
- end
2249
- @method_semaphore.synchronize {
2250
- @method_locks.delete(method)
2251
- }
2252
-
2253
- @deploydata = @cloudobj.deploydata
2254
- @config = @cloudobj.config
2255
- retval
2256
- end
2257
- } # end instance method list
2258
- } # end dynamic class generation block
2259
- } # end resource type iteration
652
+ require 'mu/cloud/machine_images'
653
+ require 'mu/cloud/resource_base'
654
+ require 'mu/cloud/providers'
2260
655
 
2261
656
  end
2262
657