cloud-mu 3.1.5 → 3.3.2

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