cloud-mu 3.1.4 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +5 -1
  3. data/ansible/roles/mu-windows/README.md +33 -0
  4. data/ansible/roles/mu-windows/defaults/main.yml +2 -0
  5. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  6. data/ansible/roles/mu-windows/files/config.xml +76 -0
  7. data/ansible/roles/mu-windows/handlers/main.yml +2 -0
  8. data/ansible/roles/mu-windows/meta/main.yml +53 -0
  9. data/ansible/roles/mu-windows/tasks/main.yml +36 -0
  10. data/ansible/roles/mu-windows/tests/inventory +2 -0
  11. data/ansible/roles/mu-windows/tests/test.yml +5 -0
  12. data/ansible/roles/mu-windows/vars/main.yml +2 -0
  13. data/bin/mu-adopt +16 -12
  14. data/bin/mu-azure-tests +57 -0
  15. data/bin/mu-cleanup +2 -4
  16. data/bin/mu-configure +52 -0
  17. data/bin/mu-deploy +3 -3
  18. data/bin/mu-findstray-tests +25 -0
  19. data/bin/mu-gen-docs +2 -4
  20. data/bin/mu-load-config.rb +2 -1
  21. data/bin/mu-node-manage +15 -16
  22. data/bin/mu-run-tests +37 -12
  23. data/cloud-mu.gemspec +5 -3
  24. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  25. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  26. data/cookbooks/mu-tools/libraries/helper.rb +1 -1
  27. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  28. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  29. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  30. data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
  31. data/cookbooks/mu-tools/recipes/windows-client.rb +163 -164
  32. data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
  33. data/extras/clean-stock-amis +25 -19
  34. data/extras/generate-stock-images +1 -0
  35. data/extras/image-generators/AWS/win2k12.yaml +18 -13
  36. data/extras/image-generators/AWS/win2k16.yaml +18 -13
  37. data/extras/image-generators/AWS/win2k19.yaml +21 -0
  38. data/modules/mommacat.ru +1 -1
  39. data/modules/mu.rb +158 -107
  40. data/modules/mu/adoption.rb +386 -59
  41. data/modules/mu/cleanup.rb +214 -303
  42. data/modules/mu/cloud.rb +128 -1632
  43. data/modules/mu/cloud/database.rb +49 -0
  44. data/modules/mu/cloud/dnszone.rb +44 -0
  45. data/modules/mu/cloud/machine_images.rb +212 -0
  46. data/modules/mu/cloud/providers.rb +81 -0
  47. data/modules/mu/cloud/resource_base.rb +926 -0
  48. data/modules/mu/cloud/server.rb +40 -0
  49. data/modules/mu/cloud/server_pool.rb +1 -0
  50. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  51. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  52. data/modules/mu/cloud/wrappers.rb +169 -0
  53. data/modules/mu/config.rb +135 -82
  54. data/modules/mu/config/alarm.rb +2 -6
  55. data/modules/mu/config/bucket.rb +32 -3
  56. data/modules/mu/config/cache_cluster.rb +2 -2
  57. data/modules/mu/config/cdn.rb +100 -0
  58. data/modules/mu/config/collection.rb +1 -1
  59. data/modules/mu/config/container_cluster.rb +7 -2
  60. data/modules/mu/config/database.rb +84 -105
  61. data/modules/mu/config/database.yml +1 -2
  62. data/modules/mu/config/dnszone.rb +5 -4
  63. data/modules/mu/config/doc_helpers.rb +5 -6
  64. data/modules/mu/config/endpoint.rb +2 -1
  65. data/modules/mu/config/firewall_rule.rb +3 -19
  66. data/modules/mu/config/folder.rb +1 -1
  67. data/modules/mu/config/function.rb +17 -8
  68. data/modules/mu/config/group.rb +1 -1
  69. data/modules/mu/config/habitat.rb +1 -1
  70. data/modules/mu/config/job.rb +89 -0
  71. data/modules/mu/config/loadbalancer.rb +57 -11
  72. data/modules/mu/config/log.rb +1 -1
  73. data/modules/mu/config/msg_queue.rb +1 -1
  74. data/modules/mu/config/nosqldb.rb +1 -1
  75. data/modules/mu/config/notifier.rb +8 -19
  76. data/modules/mu/config/ref.rb +92 -14
  77. data/modules/mu/config/role.rb +1 -1
  78. data/modules/mu/config/schema_helpers.rb +38 -37
  79. data/modules/mu/config/search_domain.rb +1 -1
  80. data/modules/mu/config/server.rb +12 -13
  81. data/modules/mu/config/server.yml +1 -0
  82. data/modules/mu/config/server_pool.rb +3 -7
  83. data/modules/mu/config/storage_pool.rb +1 -1
  84. data/modules/mu/config/tail.rb +11 -0
  85. data/modules/mu/config/user.rb +1 -1
  86. data/modules/mu/config/vpc.rb +27 -23
  87. data/modules/mu/config/vpc.yml +0 -1
  88. data/modules/mu/defaults/AWS.yaml +91 -68
  89. data/modules/mu/defaults/Azure.yaml +1 -0
  90. data/modules/mu/defaults/Google.yaml +1 -0
  91. data/modules/mu/deploy.rb +33 -19
  92. data/modules/mu/groomer.rb +16 -1
  93. data/modules/mu/groomers/ansible.rb +123 -21
  94. data/modules/mu/groomers/chef.rb +64 -11
  95. data/modules/mu/logger.rb +120 -144
  96. data/modules/mu/master.rb +97 -4
  97. data/modules/mu/master/ssl.rb +0 -1
  98. data/modules/mu/mommacat.rb +154 -867
  99. data/modules/mu/mommacat/daemon.rb +23 -14
  100. data/modules/mu/mommacat/naming.rb +110 -3
  101. data/modules/mu/mommacat/search.rb +495 -0
  102. data/modules/mu/mommacat/storage.rb +225 -192
  103. data/modules/mu/{clouds → providers}/README.md +1 -1
  104. data/modules/mu/{clouds → providers}/aws.rb +281 -64
  105. data/modules/mu/{clouds → providers}/aws/alarm.rb +3 -3
  106. data/modules/mu/{clouds → providers}/aws/bucket.rb +275 -41
  107. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +14 -50
  108. data/modules/mu/providers/aws/cdn.rb +782 -0
  109. data/modules/mu/{clouds → providers}/aws/collection.rb +5 -5
  110. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +708 -749
  111. data/modules/mu/providers/aws/database.rb +1744 -0
  112. data/modules/mu/{clouds → providers}/aws/dnszone.rb +75 -57
  113. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  114. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +212 -242
  115. data/modules/mu/{clouds → providers}/aws/folder.rb +1 -1
  116. data/modules/mu/{clouds → providers}/aws/function.rb +289 -134
  117. data/modules/mu/{clouds → providers}/aws/group.rb +18 -20
  118. data/modules/mu/{clouds → providers}/aws/habitat.rb +3 -3
  119. data/modules/mu/providers/aws/job.rb +466 -0
  120. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +50 -41
  121. data/modules/mu/{clouds → providers}/aws/log.rb +5 -5
  122. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +14 -11
  123. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +96 -5
  124. data/modules/mu/{clouds → providers}/aws/notifier.rb +135 -63
  125. data/modules/mu/{clouds → providers}/aws/role.rb +94 -57
  126. data/modules/mu/{clouds → providers}/aws/search_domain.rb +173 -42
  127. data/modules/mu/{clouds → providers}/aws/server.rb +782 -1107
  128. data/modules/mu/{clouds → providers}/aws/server_pool.rb +36 -46
  129. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +21 -38
  130. data/modules/mu/{clouds → providers}/aws/user.rb +12 -16
  131. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  132. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  133. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
  134. data/modules/mu/{clouds → providers}/aws/vpc.rb +429 -849
  135. data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
  136. data/modules/mu/{clouds → providers}/azure.rb +13 -0
  137. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
  138. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
  139. data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
  140. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
  141. data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
  142. data/modules/mu/{clouds → providers}/azure/server.rb +32 -24
  143. data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
  144. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  145. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  146. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  147. data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
  148. data/modules/mu/{clouds → providers}/cloudformation.rb +10 -0
  149. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  150. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  151. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  152. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  153. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  154. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  155. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  156. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  157. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  158. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  159. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
  160. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  161. data/modules/mu/{clouds → providers}/google.rb +29 -6
  162. data/modules/mu/{clouds → providers}/google/bucket.rb +5 -5
  163. data/modules/mu/{clouds → providers}/google/container_cluster.rb +59 -37
  164. data/modules/mu/{clouds → providers}/google/database.rb +5 -12
  165. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +5 -5
  166. data/modules/mu/{clouds → providers}/google/folder.rb +5 -9
  167. data/modules/mu/{clouds → providers}/google/function.rb +14 -8
  168. data/modules/mu/{clouds → providers}/google/group.rb +9 -17
  169. data/modules/mu/{clouds → providers}/google/habitat.rb +4 -8
  170. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +5 -5
  171. data/modules/mu/{clouds → providers}/google/role.rb +50 -31
  172. data/modules/mu/{clouds → providers}/google/server.rb +142 -55
  173. data/modules/mu/{clouds → providers}/google/server_pool.rb +14 -14
  174. data/modules/mu/{clouds → providers}/google/user.rb +34 -24
  175. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  176. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  177. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  178. data/modules/mu/{clouds → providers}/google/vpc.rb +46 -15
  179. data/modules/tests/aws-jobs-functions.yaml +46 -0
  180. data/modules/tests/centos6.yaml +15 -0
  181. data/modules/tests/centos7.yaml +15 -0
  182. data/modules/tests/centos8.yaml +12 -0
  183. data/modules/tests/ecs.yaml +23 -0
  184. data/modules/tests/eks.yaml +1 -1
  185. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  186. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  187. data/modules/tests/includes-and-params.yaml +2 -1
  188. data/modules/tests/microservice_app.yaml +288 -0
  189. data/modules/tests/rds.yaml +108 -0
  190. data/modules/tests/regrooms/rds.yaml +123 -0
  191. data/modules/tests/server-with-scrub-muisms.yaml +2 -1
  192. data/modules/tests/super_complex_bok.yml +2 -2
  193. data/modules/tests/super_simple_bok.yml +3 -5
  194. data/modules/tests/win2k12.yaml +25 -0
  195. data/modules/tests/win2k16.yaml +25 -0
  196. data/modules/tests/win2k19.yaml +25 -0
  197. data/requirements.txt +1 -0
  198. data/spec/mu/clouds/azure_spec.rb +2 -2
  199. metadata +169 -93
  200. data/extras/image-generators/AWS/windows.yaml +0 -18
  201. data/modules/mu/clouds/aws/database.rb +0 -1974
  202. data/modules/mu/clouds/aws/endpoint.rb +0 -596
  203. data/modules/tests/needwork/win2k12.yaml +0 -13
@@ -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,1350 +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 = {} if @dependencies.nil?
1348
- @loadbalancers = [] if @loadbalancers.nil?
1349
- if @config.nil?
1350
- return [@dependencies, @vpc, @loadbalancers]
1351
- end
1352
- if use_cache and @dependencies.size > 0
1353
- return [@dependencies, @vpc, @loadbalancers]
1354
- end
1355
- @config['dependencies'] = [] if @config['dependencies'].nil?
1356
-
1357
- loglevel = debug ? MU::NOTICE : MU::DEBUG
1358
-
1359
- # First, general dependencies. These should all be fellow members of
1360
- # the current deployment.
1361
- @config['dependencies'].each { |dep|
1362
- @dependencies[dep['type']] ||= {}
1363
- next if @dependencies[dep['type']].has_key?(dep['name'])
1364
- handle = @deploy.findLitterMate(type: dep['type'], name: dep['name']) if !@deploy.nil?
1365
- if !handle.nil?
1366
- MU.log "Loaded dependency for #{self}: #{dep['name']} => #{handle}", loglevel
1367
- @dependencies[dep['type']][dep['name']] = handle
1368
- else
1369
- # XXX yell under circumstances where we should expect to have
1370
- # our stuff available already?
1371
- end
1372
- }
1373
-
1374
- # Special dependencies: my containing VPC
1375
- if self.class.can_live_in_vpc and !@config['vpc'].nil?
1376
- @config['vpc']["id"] ||= @config['vpc']["vpc_id"] # old deploys
1377
- @config['vpc']["name"] ||= @config['vpc']["vpc_name"] # old deploys
1378
- # If something hash-ified a MU::Config::Ref here, fix it
1379
- if !@config['vpc']["id"].nil? and @config['vpc']["id"].is_a?(Hash)
1380
- @config['vpc']["id"] = MU::Config::Ref.new(@config['vpc']["id"])
1381
- end
1382
- if !@config['vpc']["id"].nil?
1383
- if @config['vpc']["id"].is_a?(MU::Config::Ref) and !@config['vpc']["id"].kitten.nil?
1384
- @vpc = @config['vpc']["id"].kitten
1385
- else
1386
- if @config['vpc']['habitat']
1387
- @config['vpc']['habitat'] = MU::Config::Ref.get(@config['vpc']['habitat'])
1388
- end
1389
- vpc_ref = MU::Config::Ref.get(@config['vpc'])
1390
- @vpc = vpc_ref.kitten
1391
- end
1392
- elsif !@config['vpc']["name"].nil? and @deploy
1393
- MU.log "Attempting findLitterMate on VPC for #{self}", loglevel, details: @config['vpc']
1394
-
1395
- sib_by_name = @deploy.findLitterMate(name: @config['vpc']['name'], type: "vpcs", return_all: true, habitat: @config['vpc']['project'], debug: debug)
1396
- if sib_by_name.is_a?(Array)
1397
- if sib_by_name.size == 1
1398
- @vpc = matches.first
1399
- MU.log "Single VPC match for #{self}", loglevel, details: @vpc.to_s
1400
- else
1401
- # XXX ok but this is the wrong place for this really the config parser needs to sort this out somehow
1402
- # we got multiple matches, try to pick one by preferred subnet
1403
- # behavior
1404
- MU.log "Sorting a bunch of VPC matches for #{self}", loglevel, details: sib_by_name.map { |s| s.to_s }.join(", ")
1405
- sib_by_name.each { |sibling|
1406
- all_private = sibling.subnets.map { |s| s.private? }.all?(true)
1407
- all_public = sibling.subnets.map { |s| s.private? }.all?(false)
1408
- names = sibling.subnets.map { |s| s.name }
1409
- ids = sibling.subnets.map { |s| s.cloud_id }
1410
- if all_private and ["private", "all_private"].include?(@config['vpc']['subnet_pref'])
1411
- @vpc = sibling
1412
- break
1413
- elsif all_public and ["public", "all_public"].include?(@config['vpc']['subnet_pref'])
1414
- @vpc = sibling
1415
- break
1416
- elsif @config['vpc']['subnet_name'] and
1417
- names.include?(@config['vpc']['subnet_name'])
1418
- puts "CHOOSING #{@vpc.to_s} 'cause it has #{@config['vpc']['subnet_name']}"
1419
- @vpc = sibling
1420
- break
1421
- elsif @config['vpc']['subnet_id'] and
1422
- ids.include?(@config['vpc']['subnet_id'])
1423
- @vpc = sibling
1424
- break
1425
- end
1426
- }
1427
- if !@vpc
1428
- sibling = sib_by_name.sample
1429
- 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']
1430
- @vpc = sibling
1431
- end
1432
- end
1433
- else
1434
- @vpc = sib_by_name
1435
- MU.log "Found exact VPC match for #{self}", loglevel, details: sib_by_name.to_s
1436
- end
1437
- else
1438
- MU.log "No shortcuts available to fetch VPC for #{self}", loglevel, details: @config['vpc']
1439
- end
1440
-
1441
- if !@vpc and !@config['vpc']["name"].nil? and
1442
- @dependencies.has_key?("vpc") and
1443
- @dependencies["vpc"].has_key?(@config['vpc']["name"])
1444
- MU.log "Grabbing VPC I see in @dependencies['vpc']['#{@config['vpc']["name"]}'] for #{self}", loglevel, details: @config['vpc']
1445
- @vpc = @dependencies["vpc"][@config['vpc']["name"]]
1446
- elsif !@vpc
1447
- tag_key, tag_value = @config['vpc']['tag'].split(/=/, 2) if !@config['vpc']['tag'].nil?
1448
- if !@config['vpc'].has_key?("id") and
1449
- !@config['vpc'].has_key?("deploy_id") and !@deploy.nil?
1450
- @config['vpc']["deploy_id"] = @deploy.deploy_id
1451
- end
1452
- MU.log "Doing findStray for VPC for #{self}", loglevel, details: @config['vpc']
1453
- vpcs = MU::MommaCat.findStray(
1454
- @config['cloud'],
1455
- "vpc",
1456
- deploy_id: @config['vpc']["deploy_id"],
1457
- cloud_id: @config['vpc']["id"],
1458
- name: @config['vpc']["name"],
1459
- tag_key: tag_key,
1460
- tag_value: tag_value,
1461
- habitats: [@project_id],
1462
- region: @config['vpc']["region"],
1463
- calling_deploy: @deploy,
1464
- credentials: @credentials,
1465
- dummy_ok: true,
1466
- debug: debug
1467
- )
1468
- @vpc = vpcs.first if !vpcs.nil? and vpcs.size > 0
1469
- end
1470
- if @vpc and @vpc.config and @vpc.config['bastion'] and
1471
- @vpc.config['bastion'].to_h['name'] != @config['name']
1472
- refhash = @vpc.config['bastion'].to_h
1473
- refhash['deploy_id'] ||= @vpc.deploy.deploy_id
1474
- natref = MU::Config::Ref.get(refhash)
1475
- if natref and natref.kitten(@vpc.deploy)
1476
- @nat = natref.kitten(@vpc.deploy)
1477
- end
1478
- end
1479
- if @nat.nil? and !@vpc.nil? and (
1480
- @config['vpc'].has_key?("nat_host_id") or
1481
- @config['vpc'].has_key?("nat_host_tag") or
1482
- @config['vpc'].has_key?("nat_host_ip") or
1483
- @config['vpc'].has_key?("nat_host_name")
1484
- )
1485
-
1486
- nat_tag_key, nat_tag_value = @config['vpc']['nat_host_tag'].split(/=/, 2) if !@config['vpc']['nat_host_tag'].nil?
1487
-
1488
- @nat = @vpc.findBastion(
1489
- nat_name: @config['vpc']['nat_host_name'],
1490
- nat_cloud_id: @config['vpc']['nat_host_id'],
1491
- nat_tag_key: nat_tag_key,
1492
- nat_tag_value: nat_tag_value,
1493
- nat_ip: @config['vpc']['nat_host_ip']
1494
- )
1495
-
1496
- if @nat.nil?
1497
- if !@vpc.cloud_desc.nil?
1498
- @nat = @vpc.findNat(
1499
- nat_cloud_id: @config['vpc']['nat_host_id'],
1500
- nat_filter_key: "vpc-id",
1501
- region: @config['vpc']["region"],
1502
- nat_filter_value: @vpc.cloud_id,
1503
- credentials: @config['credentials']
1504
- )
1505
- else
1506
- @nat = @vpc.findNat(
1507
- nat_cloud_id: @config['vpc']['nat_host_id'],
1508
- region: @config['vpc']["region"],
1509
- credentials: @config['credentials']
1510
- )
1511
- end
1512
- end
1513
- end
1514
- elsif self.class.cfg_name == "vpc"
1515
- @vpc = self
1516
- end
1517
-
1518
- # Google accounts usually have a useful default VPC we can use
1519
- if @vpc.nil? and @project_id and @cloud == "Google" and
1520
- self.class.can_live_in_vpc
1521
- MU.log "Seeing about default VPC for #{self.to_s}", MU::NOTICE
1522
- vpcs = MU::MommaCat.findStray(
1523
- "Google",
1524
- "vpc",
1525
- cloud_id: "default",
1526
- habitats: [@project_id],
1527
- credentials: @credentials,
1528
- dummy_ok: true,
1529
- debug: debug
1530
- )
1531
- @vpc = vpcs.first if !vpcs.nil? and vpcs.size > 0
1532
- end
1533
-
1534
- # Special dependencies: LoadBalancers I've asked to attach to an
1535
- # instance.
1536
- if @config.has_key?("loadbalancers")
1537
- @loadbalancers = [] if !@loadbalancers
1538
- @config['loadbalancers'].each { |lb|
1539
- MU.log "Loading LoadBalancer for #{self}", MU::DEBUG, details: lb
1540
- if @dependencies.has_key?("loadbalancer") and
1541
- @dependencies["loadbalancer"].has_key?(lb['concurrent_load_balancer'])
1542
- @loadbalancers << @dependencies["loadbalancer"][lb['concurrent_load_balancer']]
1543
- else
1544
- if !lb.has_key?("existing_load_balancer") and
1545
- !lb.has_key?("deploy_id") and !@deploy.nil?
1546
- lb["deploy_id"] = @deploy.deploy_id
1547
- end
1548
- lbs = MU::MommaCat.findStray(
1549
- @config['cloud'],
1550
- "loadbalancer",
1551
- deploy_id: lb["deploy_id"],
1552
- cloud_id: lb['existing_load_balancer'],
1553
- name: lb['concurrent_load_balancer'],
1554
- region: @config["region"],
1555
- calling_deploy: @deploy,
1556
- dummy_ok: true
1557
- )
1558
- @loadbalancers << lbs.first if !lbs.nil? and lbs.size > 0
1559
- end
1560
- }
1561
- end
1562
-
1563
- return [@dependencies, @vpc, @loadbalancers]
1564
- end
1565
-
1566
- # Defaults any resources that don't declare their release-readiness to
1567
- # ALPHA. That'll learn 'em.
1568
- def self.quality
1569
- MU::Cloud::ALPHA
1570
- end
1571
-
1572
- # Return a list of "container" artifacts, by class, that apply to this
1573
- # resource type in a cloud provider. This is so methods that call find
1574
- # know whether to call +find+ with identifiers for parent resources.
1575
- # This is similar in purpose to the +isGlobal?+ resource class method,
1576
- # which tells our search functions whether or not a resource scopes to
1577
- # a region. In almost all cases this is one-entry list consisting of
1578
- # +:Habitat+. Notable exceptions include most implementations of
1579
- # +Habitat+, which either reside inside a +:Folder+ or nothing at all;
1580
- # whereas a +:Folder+ tends to not have any containing parent. Very few
1581
- # resource implementations will need to override this.
1582
- # A +nil+ entry in this list is interpreted as "this resource can be
1583
- # global."
1584
- # @return [Array<Symbol,nil>]
1585
- def self.canLiveIn
1586
- if self.shortname == "Folder"
1587
- [nil, :Folder]
1588
- elsif self.shortname == "Habitat"
1589
- [:Folder]
1590
- else
1591
- [:Habitat]
1592
- end
1593
- end
1594
-
1595
- def self.find(*flags)
1596
- allfound = {}
1597
-
1598
- MU::Cloud.availableClouds.each { |cloud|
1599
- begin
1600
- args = flags.first
1601
- next if args[:cloud] and args[:cloud] != cloud
1602
- # skip this cloud if we have a region argument that makes no
1603
- # sense there
1604
- cloudbase = Object.const_get("MU").const_get("Cloud").const_get(cloud)
1605
- next if cloudbase.listCredentials.nil? or cloudbase.listCredentials.empty? or cloudbase.credConfig(args[:credentials]).nil?
1606
- if args[:region] and cloudbase.respond_to?(:listRegions)
1607
- if !cloudbase.listRegions(credentials: args[:credentials])
1608
- MU.log "Failed to get region list for credentials #{args[:credentials]} in cloud #{cloud}", MU::ERR, details: caller
1609
- else
1610
- next if !cloudbase.listRegions(credentials: args[:credentials]).include?(args[:region])
1611
- end
1612
- end
1613
- begin
1614
- cloudclass = MU::Cloud.loadCloudType(cloud, shortname)
1615
- rescue MU::MuError
1616
- next
1617
- end
1618
-
1619
- found = cloudclass.find(args)
1620
- if !found.nil?
1621
- if found.is_a?(Hash)
1622
- allfound.merge!(found)
1623
- else
1624
- raise MuError, "#{cloudclass}.find returned a non-Hash result"
1625
- end
1626
- end
1627
- rescue MuCloudResourceNotImplemented
1628
- end
1629
- }
1630
- allfound
1631
- end
1632
-
1633
- if shortname == "DNSZone"
1634
- def self.genericMuDNSEntry(*flags)
1635
- # XXX have this switch on a global config for where Mu puts its DNS
1636
- cloudclass = MU::Cloud.loadCloudType(MU::Config.defaultCloud, "DNSZone")
1637
- cloudclass.genericMuDNSEntry(flags.first)
1638
- end
1639
- def self.createRecordsFromConfig(*flags)
1640
- cloudclass = MU::Cloud.loadCloudType(MU::Config.defaultCloud, "DNSZone")
1641
- if !flags.nil? and flags.size == 1
1642
- cloudclass.createRecordsFromConfig(flags.first)
1643
- else
1644
- cloudclass.createRecordsFromConfig(*flags)
1645
- end
1646
- end
1647
- end
1648
-
1649
- if shortname == "Server" or shortname == "ServerPool"
1650
- def windows?
1651
- return true if %w{win2k16 win2k12r2 win2k12 win2k8 win2k8r2 win2k19 windows}.include?(@config['platform'])
1652
- begin
1653
- return true if cloud_desc.respond_to?(:platform) and cloud_desc.platform == "Windows"
1654
- # 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?
1655
- rescue MU::MuError
1656
- return false
1657
- end
1658
- false
1659
- end
1660
-
1661
- # Gracefully message and attempt to accommodate the common transient errors peculiar to Windows nodes
1662
- # @param e [Exception]: The exception that we're handling
1663
- # @param retries [Integer]: The current number of retries, which we'll increment and pass back to the caller
1664
- # @param rebootable_fails [Integer]: The current number of reboot-worthy failures, which we'll increment and pass back to the caller
1665
- # @param max_retries [Integer]: Maximum number of retries to attempt; we'll raise an exception if this is exceeded
1666
- # @param reboot_on_problems [Boolean]: Whether we should try to reboot a "stuck" machine
1667
- # @param retry_interval [Integer]: How many seconds to wait before returning for another attempt
1668
- def handleWindowsFail(e, retries, rebootable_fails, max_retries: 30, reboot_on_problems: false, retry_interval: 45)
1669
- msg = "WinRM connection to https://"+@mu_name+":5986/wsman: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})"
1670
- if e.class.name == "WinRM::WinRMAuthorizationError" or e.message.match(/execution expired/) and reboot_on_problems
1671
- if rebootable_fails > 0 and (rebootable_fails % 5) == 0
1672
- MU.log "#{@mu_name} still misbehaving, forcing Stop and Start from API", MU::WARN
1673
- reboot(true) # vicious API stop/start
1674
- sleep retry_interval*3
1675
- rebootable_fails = 0
1676
- else
1677
- if rebootable_fails == 3
1678
- MU.log "#{@mu_name} misbehaving, attempting to reboot from API", MU::WARN
1679
- reboot # graceful API restart
1680
- sleep retry_interval*2
1681
- end
1682
- rebootable_fails = rebootable_fails + 1
1683
- end
1684
- end
1685
- if retries < max_retries
1686
- if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0 and retries != 0)
1687
- MU.log msg, MU::NOTICE
1688
- elsif retries/max_retries > 0.5
1689
- MU.log msg, MU::WARN, details: e.inspect
1690
- end
1691
- sleep retry_interval
1692
- retries = retries + 1
1693
- else
1694
- raise MuError, "#{@mu_name}: #{e.inspect} trying to connect with WinRM, max_retries exceeded", e.backtrace
1695
- end
1696
- return [retries, rebootable_fails]
1697
- end
1698
-
1699
- def windowsRebootPending?(shell = nil)
1700
- if shell.nil?
1701
- shell = getWinRMSession(1, 30)
1702
- end
1703
- # if (Get-Item "HKLM:/SOFTWARE/Microsoft/Windows/CurrentVersion/WindowsUpdate/Auto Update/RebootRequired" -EA Ignore) { exit 1 }
1704
- cmd = %Q{
1705
- if (Get-ChildItem "HKLM:/Software/Microsoft/Windows/CurrentVersion/Component Based Servicing/RebootPending" -EA Ignore) {
1706
- echo "Component Based Servicing/RebootPending is true"
1707
- exit 1
1708
- }
1709
- if (Get-ItemProperty "HKLM:/SYSTEM/CurrentControlSet/Control/Session Manager" -Name PendingFileRenameOperations -EA Ignore) {
1710
- echo "Control/Session Manager/PendingFileRenameOperations is true"
1711
- exit 1
1712
- }
1713
- try {
1714
- $util = [wmiclass]"\\\\.\\root\\ccm\\clientsdk:CCM_ClientUtilities"
1715
- $status = $util.DetermineIfRebootPending()
1716
- if(($status -ne $null) -and $status.RebootPending){
1717
- echo "WMI says RebootPending is true"
1718
- exit 1
1719
- }
1720
- } catch {
1721
- exit 0
1722
- }
1723
- exit 0
1724
- }
1725
- resp = shell.run(cmd)
1726
- returnval = resp.exitcode == 0 ? false : true
1727
- shell.close
1728
- returnval
1729
- end
1730
-
1731
- # Basic setup tasks performed on a new node during its first WinRM
1732
- # connection. Most of this is terrible Windows glue.
1733
- # @param shell [WinRM::Shells::Powershell]: An active Powershell session to the new node.
1734
- def initialWinRMTasks(shell)
1735
- retries = 0
1736
- rebootable_fails = 0
1737
- begin
1738
- if !@config['use_cloud_provider_windows_password']
1739
- pw = @groomer.getSecret(
1740
- vault: @config['mu_name'],
1741
- item: "windows_credentials",
1742
- field: "password"
1743
- )
1744
- 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}
1745
- resp = shell.run(win_check_for_pw)
1746
- if resp.stdout.chomp != "True"
1747
- win_set_pw = %Q{(([adsi]('WinNT://./#{@config["windows_admin_username"]}, user')).psbase.invoke('SetPassword', '#{pw}'))}
1748
- resp = shell.run(win_set_pw)
1749
- puts resp.stdout
1750
- MU.log "Resetting Windows host password", MU::NOTICE, details: resp.stdout
1751
- end
1752
- end
1753
-
1754
- # Install Cygwin here, because for some reason it breaks inside Chef
1755
- # XXX would love to not do this here
1756
- pkgs = ["bash", "mintty", "vim", "curl", "openssl", "wget", "lynx", "openssh"]
1757
- admin_home = "c:/bin/cygwin/home/#{@config["windows_admin_username"]}"
1758
- install_cygwin = %Q{
1759
- If (!(Test-Path "c:/bin/cygwin/Cygwin.bat")){
1760
- $WebClient = New-Object System.Net.WebClient
1761
- $WebClient.DownloadFile("http://cygwin.com/setup-x86_64.exe","$env:Temp/setup-x86_64.exe")
1762
- 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(',')}"
1763
- }
1764
- if(!(Test-Path #{admin_home})){
1765
- New-Item -type directory -path #{admin_home}
1766
- }
1767
- if(!(Test-Path #{admin_home}/.ssh)){
1768
- New-Item -type directory -path #{admin_home}/.ssh
1769
- }
1770
- if(!(Test-Path #{admin_home}/.ssh/authorized_keys)){
1771
- New-Item #{admin_home}/.ssh/authorized_keys -type file -force -value "#{@deploy.ssh_public_key}"
1772
- }
1773
- }
1774
- resp = shell.run(install_cygwin)
1775
- if resp.exitcode != 0
1776
- MU.log "Failed at installing Cygwin", MU::ERR, details: resp
1777
- end
1778
-
1779
- hostname = nil
1780
- if !@config['active_directory'].nil?
1781
- if @config['active_directory']['node_type'] == "domain_controller" && @config['active_directory']['domain_controller_hostname']
1782
- hostname = @config['active_directory']['domain_controller_hostname']
1783
- @mu_windows_name = hostname
1784
- else
1785
- # Do we have an AD specific hostname?
1786
- hostname = @mu_windows_name
1787
- end
1788
- else
1789
- hostname = @mu_windows_name
1790
- end
1791
- resp = shell.run(%Q{hostname})
1792
-
1793
- if resp.stdout.chomp != hostname
1794
- resp = shell.run(%Q{Rename-Computer -NewName '#{hostname}' -Force -PassThru -Restart; Restart-Computer -Force})
1795
- MU.log "Renaming Windows host to #{hostname}; this will trigger a reboot", MU::NOTICE, details: resp.stdout
1796
- reboot(true)
1797
- sleep 30
1798
- end
1799
- rescue WinRM::WinRMError, HTTPClient::ConnectTimeoutError => e
1800
- retries, rebootable_fails = handleWindowsFail(e, retries, rebootable_fails, max_retries: 10, reboot_on_problems: true, retry_interval: 30)
1801
- retry
1802
- end
1803
- end
1804
-
1805
-
1806
- # Basic setup tasks performed on a new node during its first initial
1807
- # ssh connection. Most of this is terrible Windows glue.
1808
- # @param ssh [Net::SSH::Connection::Session]: The active SSH session to the new node.
1809
- def initialSSHTasks(ssh)
1810
- 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" )}
1811
- win_installer_check = %q{ls /proc/registry/HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Installer/}
1812
- lnx_installer_check = %q{ps auxww | awk '{print $11}' | egrep '(/usr/bin/yum|apt-get|dpkg)'}
1813
- lnx_updates_check = %q{( test -f /.mu-installer-ran-updates || ! test -d /var/lib/cloud/instance ) || echo "userdata still running"}
1814
- win_set_pw = nil
1815
-
1816
- if windows? and !@config['use_cloud_provider_windows_password']
1817
- # 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
1818
- pw = @groomer.getSecret(
1819
- vault: @config['mu_name'],
1820
- item: "windows_credentials",
1821
- field: "password"
1822
- )
1823
- 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}'}
1824
- win_set_pw = %Q{powershell -Command "& {(([adsi]('WinNT://./#{@config["windows_admin_username"]}, user')).psbase.invoke('SetPassword', '#{pw}'))}"}
1825
- end
1826
-
1827
- # There shouldn't be a use case where a domain joined computer goes through initialSSHTasks. Removing Active Directory specific computer rename.
1828
- set_hostname = true
1829
- hostname = nil
1830
- if !@config['active_directory'].nil?
1831
- if @config['active_directory']['node_type'] == "domain_controller" && @config['active_directory']['domain_controller_hostname']
1832
- hostname = @config['active_directory']['domain_controller_hostname']
1833
- @mu_windows_name = hostname
1834
- set_hostname = true
1835
- else
1836
- # Do we have an AD specific hostname?
1837
- hostname = @mu_windows_name
1838
- set_hostname = true
1839
- end
1840
- else
1841
- hostname = @mu_windows_name
1842
- end
1843
- win_check_for_hostname = %Q{powershell -Command '& {hostname}'}
1844
- win_set_hostname = %Q{powershell -Command "& {Rename-Computer -NewName '#{hostname}' -Force -PassThru -Restart; Restart-Computer -Force }"}
1845
-
1846
- begin
1847
- # Set our admin password first, if we need to
1848
- if windows? and !win_set_pw.nil? and !win_check_for_pw.nil?
1849
- output = ssh.exec!(win_check_for_pw)
1850
- raise MU::Cloud::BootstrapTempFail, "Got nil output from ssh session, waiting and retrying" if output.nil?
1851
- if !output.match(/True/)
1852
- MU.log "Setting Windows password for user #{@config['windows_admin_username']}", details: ssh.exec!(win_set_pw)
1853
- end
1854
- end
1855
- if windows?
1856
- output = ssh.exec!(win_env_fix)
1857
- output += ssh.exec!(win_installer_check)
1858
- raise MU::Cloud::BootstrapTempFail, "Got nil output from ssh session, waiting and retrying" if output.nil?
1859
- if output.match(/InProgress/)
1860
- raise MU::Cloud::BootstrapTempFail, "Windows Installer service is still doing something, need to wait"
1861
- end
1862
- if set_hostname and !@hostname_set and @mu_windows_name
1863
- output = ssh.exec!(win_check_for_hostname)
1864
- raise MU::Cloud::BootstrapTempFail, "Got nil output from ssh session, waiting and retrying" if output.nil?
1865
- if !output.match(/#{@mu_windows_name}/)
1866
- MU.log "Setting Windows hostname to #{@mu_windows_name}", details: ssh.exec!(win_set_hostname)
1867
- @hostname_set = true
1868
- # Reboot from the API too, in case Windows is flailing
1869
- if !@cloudobj.nil?
1870
- @cloudobj.reboot
1871
- else
1872
- reboot
1873
- end
1874
- raise MU::Cloud::BootstrapTempFail, "Set hostname in Windows, waiting for reboot"
1875
- end
1876
- end
1877
- else
1878
- output = ssh.exec!(lnx_installer_check)
1879
- if !output.nil? and !output.empty?
1880
- raise MU::Cloud::BootstrapTempFail, "Linux package manager is still doing something, need to wait (#{output})"
1881
- end
1882
- if !@config['skipinitialupdates'] and
1883
- !@config['scrub_mu_isms'] and
1884
- !@config['userdata_script']
1885
- output = ssh.exec!(lnx_updates_check)
1886
- if !output.nil? and output.match(/userdata still running/)
1887
- raise MU::Cloud::BootstrapTempFail, "Waiting for initial userdata system updates to complete"
1888
- end
1889
- end
1890
- end
1891
- rescue RuntimeError => e
1892
- raise MU::Cloud::BootstrapTempFail, "Got #{e.inspect} performing initial SSH connect tasks, will try again"
1893
- end
1894
-
1895
- end
1896
-
1897
- # Get a privileged Powershell session on the server in question, using SSL-encrypted WinRM with certificate authentication.
1898
- # @param max_retries [Integer]:
1899
- # @param retry_interval [Integer]:
1900
- # @param timeout [Integer]:
1901
- # @param winrm_retries [Integer]:
1902
- # @param reboot_on_problems [Boolean]:
1903
- def getWinRMSession(max_retries = 40, retry_interval = 60, timeout: 30, winrm_retries: 5, reboot_on_problems: false)
1904
- _nat_ssh_key, _nat_ssh_user, _nat_ssh_host, canonical_ip, _ssh_user, _ssh_key_name = getSSHConfig
1905
- @mu_name ||= @config['mu_name']
1906
-
1907
- shell = nil
1908
- opts = nil
1909
- # and now, a thing I really don't want to do
1910
- MU::Master.addInstanceToEtcHosts(canonical_ip, @mu_name)
1911
-
1912
- # catch exceptions that circumvent our regular call stack
1913
- Thread.abort_on_exception = false
1914
- Thread.handle_interrupt(WinRM::WinRMWSManFault => :never) {
1915
- begin
1916
- Thread.handle_interrupt(WinRM::WinRMWSManFault => :immediate) {
1917
- MU.log "(Probably harmless) Caught a WinRM::WinRMWSManFault in #{Thread.current.inspect}", MU::DEBUG, details: Thread.current.backtrace
1918
- }
1919
- ensure
1920
- # Reraise something useful
1921
- end
1922
- }
1923
-
1924
- retries = 0
1925
- rebootable_fails = 0
1926
- begin
1927
- MU.log "Calling WinRM on #{@mu_name}", MU::DEBUG, details: opts
1928
- opts = {
1929
- endpoint: 'https://'+@mu_name+':5986/wsman',
1930
- retry_limit: winrm_retries,
1931
- 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
1932
- ca_trust_path: "#{MU.mySSLDir}/Mu_CA.pem",
1933
- transport: :ssl,
1934
- operation_timeout: timeout,
1935
- client_cert: "#{MU.mySSLDir}/#{@mu_name}-winrm.crt",
1936
- client_key: "#{MU.mySSLDir}/#{@mu_name}-winrm.key"
1937
- }
1938
- conn = WinRM::Connection.new(opts)
1939
- MU.log "WinRM connection to #{@mu_name} created", MU::DEBUG, details: conn
1940
- shell = conn.shell(:powershell)
1941
- shell.run('ipconfig') # verify that we can do something
1942
- rescue Errno::EHOSTUNREACH, Errno::ECONNREFUSED, HTTPClient::ConnectTimeoutError, OpenSSL::SSL::SSLError, SocketError, WinRM::WinRMError, Timeout::Error => e
1943
- retries, rebootable_fails = handleWindowsFail(e, retries, rebootable_fails, max_retries: max_retries, reboot_on_problems: reboot_on_problems, retry_interval: retry_interval)
1944
- retry
1945
- ensure
1946
- MU::Master.removeInstanceFromEtcHosts(@mu_name)
1947
- end
1948
-
1949
- shell
1950
- end
1951
-
1952
- # @param max_retries [Integer]: Number of connection attempts to make before giving up
1953
- # @param retry_interval [Integer]: Number of seconds to wait between connection attempts
1954
- # @return [Net::SSH::Connection::Session]
1955
- def getSSHSession(max_retries = 12, retry_interval = 30)
1956
- ssh_keydir = Etc.getpwnam(@deploy.mu_user).dir+"/.ssh"
1957
- nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, _ssh_key_name = getSSHConfig
1958
- session = nil
1959
- retries = 0
1960
-
1961
- vpc_class = Object.const_get("MU").const_get("Cloud").const_get(@cloud).const_get("VPC")
1962
-
1963
- # XXX WHY is this a thing
1964
- Thread.handle_interrupt(Errno::ECONNREFUSED => :never) {
1965
- }
1966
-
1967
- begin
1968
- MU::Cloud.handleNetSSHExceptions
1969
- if !nat_ssh_host.nil?
1970
- proxy_cmd = "ssh -q -o StrictHostKeyChecking=no -W %h:%p #{nat_ssh_user}@#{nat_ssh_host}"
1971
- 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
1972
- proxy = Net::SSH::Proxy::Command.new(proxy_cmd)
1973
- session = Net::SSH.start(
1974
- canonical_ip,
1975
- ssh_user,
1976
- :config => false,
1977
- :keys_only => true,
1978
- :keys => [ssh_keydir+"/"+nat_ssh_key, ssh_keydir+"/"+@deploy.ssh_key_name],
1979
- :verify_host_key => false,
1980
- # :verbose => :info,
1981
- :host_key => "ssh-rsa",
1982
- :port => 22,
1983
- :auth_methods => ['publickey'],
1984
- :proxy => proxy
1985
- )
1986
- else
1987
-
1988
- MU.log "Attempting SSH to #{canonical_ip} (#{@mu_name}) as #{ssh_user} with key #{ssh_keydir}/#{@deploy.ssh_key_name}" if retries == 0
1989
- session = Net::SSH.start(
1990
- canonical_ip,
1991
- ssh_user,
1992
- :config => false,
1993
- :keys_only => true,
1994
- :keys => [ssh_keydir+"/"+@deploy.ssh_key_name],
1995
- :verify_host_key => false,
1996
- # :verbose => :info,
1997
- :host_key => "ssh-rsa",
1998
- :port => 22,
1999
- :auth_methods => ['publickey']
2000
- )
2001
- end
2002
- retries = 0
2003
- rescue Net::SSH::HostKeyMismatch => e
2004
- MU.log("Remembering new key: #{e.fingerprint}")
2005
- e.remember_host!
2006
- session.close
2007
- retry
2008
- # 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
2009
- 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
2010
- begin
2011
- session.close if !session.nil?
2012
- rescue Net::SSH::Disconnect, IOError => e
2013
- if windows?
2014
- MU.log "Windows has probably closed the ssh session before we could. Waiting before trying again", MU::NOTICE
2015
- else
2016
- MU.log "ssh session was closed unexpectedly, waiting before trying again", MU::NOTICE
2017
- end
2018
- sleep 10
2019
- end
2020
-
2021
- if retries < max_retries
2022
- retries = retries + 1
2023
- msg = "ssh #{ssh_user}@#{@mu_name}: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})"
2024
- if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0)
2025
- MU.log msg, MU::NOTICE
2026
- if !vpc_class.haveRouteToInstance?(cloud_desc, credentials: @credentials) and
2027
- canonical_ip.match(/(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1$)|(^[fF][cCdD])/) and
2028
- !nat_ssh_host
2029
- 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
2030
- end
2031
- elsif retries/max_retries > 0.5
2032
- MU.log msg, MU::WARN, details: e.inspect
2033
- end
2034
- sleep retry_interval
2035
- retry
2036
- else
2037
- raise MuError, "#{@mu_name}: #{e.inspect} trying to connect with SSH, max_retries exceeded", e.backtrace
2038
- end
2039
- end
2040
- return session
2041
- end
2042
- end
2043
-
2044
- # Wrapper for the cleanup class method of underlying cloud object implementations.
2045
- def self.cleanup(*flags)
2046
- ok = true
2047
- params = flags.first
2048
- clouds = MU::Cloud.supportedClouds
2049
- if params[:cloud]
2050
- clouds = [params[:cloud]]
2051
- params.delete(:cloud)
2052
- end
2053
-
2054
- clouds.each { |cloud|
2055
- begin
2056
- cloudclass = MU::Cloud.loadCloudType(cloud, shortname)
2057
-
2058
- if cloudclass.isGlobal?
2059
- params.delete(:region)
2060
- end
2061
-
2062
- raise MuCloudResourceNotImplemented if !cloudclass.respond_to?(:cleanup) or cloudclass.method(:cleanup).owner.to_s != "#<Class:#{cloudclass}>"
2063
- MU.log "Invoking #{cloudclass}.cleanup from #{shortname}", MU::DEBUG, details: flags
2064
- cloudclass.cleanup(params)
2065
- rescue MuCloudResourceNotImplemented
2066
- MU.log "No #{cloud} implementation of #{shortname}.cleanup, skipping", MU::DEBUG, details: flags
2067
- rescue StandardError => e
2068
- in_msg = cloud
2069
- if params and params[:region]
2070
- in_msg += " "+params[:region]
2071
- end
2072
- if params and params[:flags] and params[:flags]["project"]
2073
- in_msg += " project "+params[:flags]["project"]
2074
- end
2075
- MU.log "Skipping #{shortname} cleanup method in #{in_msg} due to exception: #{e.message}", MU::WARN, details: e.backtrace
2076
- ok = false
2077
- end
2078
- }
2079
- MU::MommaCat.unlockAll
2080
-
2081
- ok
2082
- end
2083
-
2084
- # A hook that is always called just before each instance method is
2085
- # invoked, so that we can ensure that repetitive setup tasks (like
2086
- # resolving +:resource_group+ for Azure resources) have always been
2087
- # done.
2088
- def resourceInitHook
2089
- @cloud ||= cloud
2090
- if @cloudparentclass.respond_to?(:resourceInitHook)
2091
- @cloudparentclass.resourceInitHook(@cloudobj, @deploy)
2092
- end
2093
- end
2094
-
2095
- # Wrap the instance methods that this cloud resource type has to
2096
- # implement.
2097
- MU::Cloud.resource_types[name.to_sym][:instance].each { |method|
2098
- define_method method do |*args|
2099
- return nil if @cloudobj.nil?
2100
- MU.log "Invoking #{@cloudobj}.#{method}", MU::DEBUG
2101
-
2102
- # Go ahead and guarantee that we can't accidentally trigger these
2103
- # methods recursively.
2104
- @method_semaphore.synchronize {
2105
- # We're looking for recursion, not contention, so ignore some
2106
- # obviously harmless things.
2107
- if @method_locks.has_key?(method) and method != :findBastion and method != :cloud_id
2108
- MU.log "Double-call to cloud method #{method} for #{self}", MU::DEBUG, details: caller + ["competing call stack:"] + @method_locks[method]
2109
- end
2110
- @method_locks[method] = caller
2111
- }
2112
-
2113
- # Make sure the describe() caches are fresh
2114
- @cloudobj.describe if method != :describe
2115
-
2116
- # Don't run through dependencies on simple attr_reader lookups
2117
- if ![:dependencies, :cloud_id, :config, :mu_name].include?(method)
2118
- @cloudobj.dependencies
2119
- end
2120
-
2121
- retval = nil
2122
- if !args.nil? and args.size == 1
2123
- retval = @cloudobj.method(method).call(args.first)
2124
- elsif !args.nil? and args.size > 0
2125
- retval = @cloudobj.method(method).call(*args)
2126
- else
2127
- retval = @cloudobj.method(method).call
2128
- end
2129
- if (method == :create or method == :groom or method == :postBoot) and
2130
- (!@destroyed and !@cloudobj.destroyed)
2131
- deploydata = @cloudobj.method(:notify).call
2132
- @deploydata ||= deploydata # XXX I don't remember why we're not just doing this from the get-go; maybe because we prefer some mangling occurring in @deploy.notify?
2133
- if deploydata.nil? or !deploydata.is_a?(Hash)
2134
- MU.log "#{self} notify method did not return a Hash of deployment data, attempting to fill in with cloud descriptor #{@cloudobj.cloud_id}", MU::WARN
2135
- deploydata = MU.structToHash(@cloudobj.cloud_desc)
2136
- raise MuError, "Failed to collect metadata about #{self}" if deploydata.nil?
2137
- end
2138
- deploydata['cloud_id'] ||= @cloudobj.cloud_id if !@cloudobj.cloud_id.nil?
2139
- deploydata['mu_name'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
2140
- deploydata['nodename'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
2141
- deploydata.delete("#MUOBJECT")
2142
- @deploy.notify(self.class.cfg_plural, @config['name'], deploydata, triggering_node: @cloudobj, delayed_save: @delayed_save) if !@deploy.nil?
2143
- elsif method == :notify
2144
- retval['cloud_id'] = @cloudobj.cloud_id.to_s if !@cloudobj.cloud_id.nil?
2145
- retval['mu_name'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
2146
- @deploy.notify(self.class.cfg_plural, @config['name'], retval, triggering_node: @cloudobj, delayed_save: @delayed_save) if !@deploy.nil?
2147
- end
2148
- @method_semaphore.synchronize {
2149
- @method_locks.delete(method)
2150
- }
2151
-
2152
- @deploydata = @cloudobj.deploydata
2153
- @config = @cloudobj.config
2154
- retval
2155
- end
2156
- } # end instance method list
2157
- } # end dynamic class generation block
2158
- } # end resource type iteration
652
+ require 'mu/cloud/machine_images'
653
+ require 'mu/cloud/resource_base'
654
+ require 'mu/cloud/providers'
2159
655
 
2160
656
  end
2161
657