cloud-mu 3.1.6 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/bin/mu-adopt +15 -12
  4. data/bin/mu-azure-tests +57 -0
  5. data/bin/mu-cleanup +2 -4
  6. data/bin/mu-configure +37 -1
  7. data/bin/mu-deploy +3 -3
  8. data/bin/mu-findstray-tests +25 -0
  9. data/bin/mu-gen-docs +2 -4
  10. data/bin/mu-load-config.rb +2 -1
  11. data/bin/mu-run-tests +37 -12
  12. data/cloud-mu.gemspec +4 -4
  13. data/cookbooks/mu-tools/attributes/default.rb +7 -0
  14. data/cookbooks/mu-tools/libraries/helper.rb +87 -3
  15. data/cookbooks/mu-tools/recipes/apply_security.rb +39 -23
  16. data/cookbooks/mu-tools/recipes/aws_api.rb +13 -0
  17. data/cookbooks/mu-tools/recipes/google_api.rb +4 -0
  18. data/cookbooks/mu-tools/recipes/rsyslog.rb +8 -1
  19. data/cookbooks/mu-tools/resources/disk.rb +33 -12
  20. data/cookbooks/mu-tools/resources/mommacat_request.rb +1 -2
  21. data/cookbooks/mu-tools/templates/centos-8/sshd_config.erb +215 -0
  22. data/extras/clean-stock-amis +10 -2
  23. data/extras/generate-stock-images +7 -3
  24. data/extras/image-generators/AWS/centos7.yaml +19 -16
  25. data/extras/image-generators/AWS/{rhel7.yaml → rhel71.yaml} +0 -0
  26. data/extras/image-generators/AWS/{win2k12.yaml → win2k12r2.yaml} +0 -0
  27. data/modules/mommacat.ru +2 -2
  28. data/modules/mu.rb +84 -97
  29. data/modules/mu/adoption.rb +359 -59
  30. data/modules/mu/cleanup.rb +67 -44
  31. data/modules/mu/cloud.rb +108 -1754
  32. data/modules/mu/cloud/database.rb +49 -0
  33. data/modules/mu/cloud/dnszone.rb +44 -0
  34. data/modules/mu/cloud/machine_images.rb +212 -0
  35. data/modules/mu/cloud/providers.rb +81 -0
  36. data/modules/mu/cloud/resource_base.rb +929 -0
  37. data/modules/mu/cloud/server.rb +40 -0
  38. data/modules/mu/cloud/server_pool.rb +1 -0
  39. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  40. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  41. data/modules/mu/cloud/wrappers.rb +178 -0
  42. data/modules/mu/config.rb +122 -80
  43. data/modules/mu/config/alarm.rb +2 -6
  44. data/modules/mu/config/bucket.rb +32 -3
  45. data/modules/mu/config/cache_cluster.rb +2 -2
  46. data/modules/mu/config/cdn.rb +100 -0
  47. data/modules/mu/config/collection.rb +1 -1
  48. data/modules/mu/config/container_cluster.rb +2 -2
  49. data/modules/mu/config/database.rb +84 -105
  50. data/modules/mu/config/database.yml +1 -2
  51. data/modules/mu/config/dnszone.rb +5 -4
  52. data/modules/mu/config/doc_helpers.rb +4 -5
  53. data/modules/mu/config/endpoint.rb +2 -1
  54. data/modules/mu/config/firewall_rule.rb +3 -19
  55. data/modules/mu/config/folder.rb +1 -1
  56. data/modules/mu/config/function.rb +17 -8
  57. data/modules/mu/config/group.rb +1 -1
  58. data/modules/mu/config/habitat.rb +1 -1
  59. data/modules/mu/config/job.rb +89 -0
  60. data/modules/mu/config/loadbalancer.rb +57 -11
  61. data/modules/mu/config/log.rb +1 -1
  62. data/modules/mu/config/msg_queue.rb +1 -1
  63. data/modules/mu/config/nosqldb.rb +1 -1
  64. data/modules/mu/config/notifier.rb +8 -19
  65. data/modules/mu/config/ref.rb +81 -9
  66. data/modules/mu/config/role.rb +1 -1
  67. data/modules/mu/config/schema_helpers.rb +30 -34
  68. data/modules/mu/config/search_domain.rb +1 -1
  69. data/modules/mu/config/server.rb +5 -13
  70. data/modules/mu/config/server_pool.rb +3 -7
  71. data/modules/mu/config/storage_pool.rb +1 -1
  72. data/modules/mu/config/tail.rb +10 -0
  73. data/modules/mu/config/user.rb +1 -1
  74. data/modules/mu/config/vpc.rb +13 -17
  75. data/modules/mu/defaults/AWS.yaml +106 -106
  76. data/modules/mu/defaults/Azure.yaml +1 -0
  77. data/modules/mu/defaults/Google.yaml +1 -0
  78. data/modules/mu/deploy.rb +33 -19
  79. data/modules/mu/groomer.rb +15 -0
  80. data/modules/mu/groomers/chef.rb +3 -0
  81. data/modules/mu/logger.rb +120 -144
  82. data/modules/mu/master.rb +22 -1
  83. data/modules/mu/mommacat.rb +71 -26
  84. data/modules/mu/mommacat/daemon.rb +23 -14
  85. data/modules/mu/mommacat/naming.rb +82 -3
  86. data/modules/mu/mommacat/search.rb +59 -16
  87. data/modules/mu/mommacat/storage.rb +119 -48
  88. data/modules/mu/{clouds → providers}/README.md +1 -1
  89. data/modules/mu/{clouds → providers}/aws.rb +248 -62
  90. data/modules/mu/{clouds → providers}/aws/alarm.rb +3 -3
  91. data/modules/mu/{clouds → providers}/aws/bucket.rb +275 -41
  92. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +14 -50
  93. data/modules/mu/providers/aws/cdn.rb +782 -0
  94. data/modules/mu/{clouds → providers}/aws/collection.rb +5 -5
  95. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +65 -63
  96. data/modules/mu/providers/aws/database.rb +1747 -0
  97. data/modules/mu/{clouds → providers}/aws/dnszone.rb +26 -12
  98. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  99. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +39 -32
  100. data/modules/mu/{clouds → providers}/aws/folder.rb +1 -1
  101. data/modules/mu/{clouds → providers}/aws/function.rb +291 -133
  102. data/modules/mu/{clouds → providers}/aws/group.rb +18 -20
  103. data/modules/mu/{clouds → providers}/aws/habitat.rb +3 -3
  104. data/modules/mu/providers/aws/job.rb +469 -0
  105. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +77 -47
  106. data/modules/mu/{clouds → providers}/aws/log.rb +5 -5
  107. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +14 -11
  108. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +96 -5
  109. data/modules/mu/{clouds → providers}/aws/notifier.rb +135 -63
  110. data/modules/mu/{clouds → providers}/aws/role.rb +112 -78
  111. data/modules/mu/{clouds → providers}/aws/search_domain.rb +172 -41
  112. data/modules/mu/{clouds → providers}/aws/server.rb +120 -145
  113. data/modules/mu/{clouds → providers}/aws/server_pool.rb +42 -60
  114. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +21 -38
  115. data/modules/mu/{clouds → providers}/aws/user.rb +12 -16
  116. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  117. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  118. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +0 -0
  119. data/modules/mu/{clouds → providers}/aws/vpc.rb +141 -73
  120. data/modules/mu/{clouds → providers}/aws/vpc_subnet.rb +0 -0
  121. data/modules/mu/{clouds → providers}/azure.rb +4 -1
  122. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
  123. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
  124. data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
  125. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
  126. data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
  127. data/modules/mu/{clouds → providers}/azure/server.rb +32 -24
  128. data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
  129. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  130. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  131. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  132. data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
  133. data/modules/mu/{clouds → providers}/cloudformation.rb +1 -1
  134. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  135. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  136. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  137. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  138. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  139. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  140. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  141. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  142. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  143. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  144. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
  145. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  146. data/modules/mu/{clouds → providers}/google.rb +15 -6
  147. data/modules/mu/{clouds → providers}/google/bucket.rb +2 -2
  148. data/modules/mu/{clouds → providers}/google/container_cluster.rb +29 -14
  149. data/modules/mu/{clouds → providers}/google/database.rb +2 -9
  150. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +3 -3
  151. data/modules/mu/{clouds → providers}/google/folder.rb +5 -9
  152. data/modules/mu/{clouds → providers}/google/function.rb +4 -4
  153. data/modules/mu/{clouds → providers}/google/group.rb +9 -17
  154. data/modules/mu/{clouds → providers}/google/habitat.rb +4 -8
  155. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +2 -2
  156. data/modules/mu/{clouds → providers}/google/role.rb +46 -35
  157. data/modules/mu/{clouds → providers}/google/server.rb +26 -11
  158. data/modules/mu/{clouds → providers}/google/server_pool.rb +11 -11
  159. data/modules/mu/{clouds → providers}/google/user.rb +32 -22
  160. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  161. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  162. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  163. data/modules/mu/{clouds → providers}/google/vpc.rb +38 -3
  164. data/modules/tests/aws-jobs-functions.yaml +46 -0
  165. data/modules/tests/centos6.yaml +15 -0
  166. data/modules/tests/centos7.yaml +15 -0
  167. data/modules/tests/centos8.yaml +12 -0
  168. data/modules/tests/ecs.yaml +2 -2
  169. data/modules/tests/eks.yaml +1 -1
  170. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  171. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  172. data/modules/tests/microservice_app.yaml +288 -0
  173. data/modules/tests/rds.yaml +108 -0
  174. data/modules/tests/regrooms/rds.yaml +123 -0
  175. data/modules/tests/server-with-scrub-muisms.yaml +1 -1
  176. data/modules/tests/super_complex_bok.yml +2 -2
  177. data/modules/tests/super_simple_bok.yml +2 -2
  178. data/spec/mu/clouds/azure_spec.rb +2 -2
  179. metadata +126 -98
  180. data/modules/mu/clouds/aws/database.rb +0 -1974
  181. data/modules/mu/clouds/aws/endpoint.rb +0 -596
@@ -30,7 +30,9 @@ module MU
30
30
  @onlycloud = false
31
31
  @skipcloud = false
32
32
 
33
- TYPES_IN_ORDER = ["Collection", "Endpoint", "Function", "ServerPool", "ContainerCluster", "SearchDomain", "Server", "MsgQueue", "Database", "CacheCluster", "StoragePool", "LoadBalancer", "NoSQLDB", "FirewallRule", "Alarm", "Notifier", "Log", "VPC", "Role", "Group", "User", "Bucket", "DNSZone", "Collection"]
33
+ # Resource types, in the order in which we generally have to clean them up
34
+ # to disentangle them from one another.
35
+ TYPES_IN_ORDER = ["Collection", "CDN", "Endpoint", "Function", "ServerPool", "ContainerCluster", "SearchDomain", "Server", "MsgQueue", "Database", "CacheCluster", "StoragePool", "LoadBalancer", "NoSQLDB", "FirewallRule", "Alarm", "Notifier", "Log", "Job", "VPC", "Role", "Group", "User", "Bucket", "DNSZone", "Collection"]
34
36
 
35
37
  # Purge all resources associated with a deployment.
36
38
  # @param deploy_id [String]: The identifier of the deployment to remove (typically seen in the MU-ID tag on a resource).
@@ -50,6 +52,7 @@ module MU
50
52
  @onlycloud = onlycloud
51
53
  @skipcloud = skipcloud
52
54
  @ignoremaster = ignoremaster
55
+ @deploy_id = deploy_id
53
56
 
54
57
  if @skipcloud and @onlycloud # you actually mean noop
55
58
  @onlycloud = @skipcloud = false
@@ -120,7 +123,7 @@ module MU
120
123
  }
121
124
 
122
125
  creds.each_pair { |provider, credsets_inner|
123
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(provider)
126
+ cloudclass = MU::Cloud.cloudClass(provider)
124
127
  credsets_inner.keys.each { |c|
125
128
  cloudclass.cleanDeploy(MU.deploy_id, credentials: c, noop: @noop)
126
129
  }
@@ -158,7 +161,7 @@ module MU
158
161
  def self.listUsedCredentials(credsets)
159
162
  creds = {}
160
163
  MU::Cloud.availableClouds.each { |cloud|
161
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
164
+ cloudclass = MU::Cloud.cloudClass(cloud)
162
165
  if $MU_CFG[cloud.downcase] and $MU_CFG[cloud.downcase].size > 0
163
166
  creds[cloud] ||= {}
164
167
  cloudclass.listCredentials.each { |credset|
@@ -179,7 +182,7 @@ module MU
179
182
  private_class_method :listUsedCredentials
180
183
 
181
184
  def self.cleanCloud(cloud, habitats, regions, credsets)
182
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
185
+ cloudclass = MU::Cloud.cloudClass(cloud)
183
186
  credsets.each_pair { |credset, acct_regions|
184
187
  next if @credsused and !@credsused.include?(credset)
185
188
  global_vs_region_semaphore = Mutex.new
@@ -212,54 +215,72 @@ module MU
212
215
 
213
216
  def self.cleanRegion(cloud, credset, region, global_vs_region_semaphore, global_done, habitats)
214
217
  had_failures = false
215
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
216
- habitatclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get("Habitat")
218
+ cloudclass = MU::Cloud.cloudClass(cloud)
219
+ habitatclass = MU::Cloud.resourceClass(cloud, "Habitat")
217
220
 
218
- projects = []
219
- if habitats
220
- projects = habitats
221
- else
221
+ if !habitats
222
+ habitats = []
222
223
  if $MU_CFG and $MU_CFG[cloud.downcase] and
223
224
  $MU_CFG[cloud.downcase][credset] and
224
225
  $MU_CFG[cloud.downcase][credset]["project"]
225
226
  # XXX GCP credential schema needs an array for projects
226
- projects << $MU_CFG[cloud.downcase][credset]["project"]
227
+ habitats << $MU_CFG[cloud.downcase][credset]["project"]
227
228
  end
228
229
  begin
229
- projects.concat(cloudclass.listHabitats(credset))
230
+ habitats.concat(cloudclass.listHabitats(credset, use_cache: false))
230
231
  rescue NoMethodError
231
232
  end
232
233
  end
233
234
 
234
- if projects == []
235
- projects << "" # dummy
235
+ if habitats == []
236
+ habitats << "" # dummy
236
237
  MU.log "Checking for #{cloud}/#{credset} resources from #{MU.deploy_id} in #{region}", MU::NOTICE
237
238
  end
238
- projects.uniq!
239
+ habitats.uniq!
239
240
 
240
241
  # We do these in an order that unrolls dependent resources
241
242
  # sensibly, and we hit :Collection twice because AWS
242
243
  # CloudFormation sometimes fails internally.
243
- projectthreads = []
244
- projects.each { |project|
245
- if habitats and !habitats.empty? and project != ""
246
- next if !habitats.include?(project)
244
+ habitat_threads = []
245
+ habitats.each { |habitat|
246
+ if habitats and !habitats.empty? and habitat != ""
247
+ next if !habitats.include?(habitat)
247
248
  end
248
- if @habitatsused and !@habitatsused.empty? and project != ""
249
- next if !@habitatsused.include?(project)
249
+ if @habitatsused and !@habitatsused.empty? and habitat != ""
250
+ next if !@habitatsused.include?(habitat)
250
251
  end
251
- next if !habitatclass.isLive?(project, credset)
252
+ next if !habitatclass.isLive?(habitat, credset)
252
253
 
253
- projectthreads << Thread.new {
254
+ habitat_threads << Thread.new {
255
+ Thread.current.thread_variable_set("name", "#{cloud}/#{credset}/#{habitat}/#{region}")
254
256
  Thread.abort_on_exception = false
255
- if !cleanHabitat(cloud, credset, region, project, global_vs_region_semaphore, global_done)
257
+ if !cleanHabitat(cloud, credset, region, habitat, global_vs_region_semaphore, global_done)
256
258
  had_failures = true
257
259
  end
258
260
  } # TYPES_IN_ORDER.each { |t|
259
- } # projects.each { |project|
260
- projectthreads.each do |t|
261
- t.join
262
- end
261
+ } # habitats.each { |habitat|
262
+
263
+ last_checkin = Time.now
264
+ begin
265
+ deletia = []
266
+ habitat_threads.each { |t|
267
+ if !t.status
268
+ t.join
269
+ deletia << t
270
+ end
271
+ }
272
+ deletia.each { |t|
273
+ habitat_threads.delete(t)
274
+ }
275
+ if (Time.now - last_checkin) > 120
276
+ list = habitat_threads.map { |t|
277
+ t.thread_variable_get("name") + (t.thread_variable_get("type") ? "/"+t.thread_variable_get("type") : "")
278
+ }
279
+ MU.log "Waiting on #{habitat_threads.size.to_s} habitat#{habitat_threads.size > 1 ? "s" : ""} in region #{region}", MU::NOTICE, details: list
280
+ last_checkin = Time.now
281
+ end
282
+ sleep 10 if !habitat_threads.empty?
283
+ end while !habitat_threads.empty?
263
284
 
264
285
  had_failures
265
286
  end
@@ -280,8 +301,7 @@ module MU
280
301
  begin
281
302
  skipme = false
282
303
  global_vs_region_semaphore.synchronize {
283
- MU::Cloud.loadCloudType(cloud, t)
284
- if Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(t).isGlobal?
304
+ if MU::Cloud.resourceClass(cloud, t).isGlobal?
285
305
  global_done[habitat] ||= []
286
306
  if !global_done[habitat].include?(t)
287
307
  global_done[habitat] << t
@@ -295,7 +315,7 @@ module MU
295
315
  rescue MU::Cloud::MuDefunctHabitat, MU::Cloud::MuCloudResourceNotImplemented
296
316
  next
297
317
  rescue MU::MuError, NoMethodError => e
298
- MU.log "While checking mu/clouds/#{cloud.downcase}/#{cloudclass.cfg_name} for global-ness in cleanup: "+e.message, MU::WARN
318
+ MU.log "While checking mu/providers/#{cloud.downcase}/#{cloudclass.cfg_name} for global-ness in cleanup: "+e.message, MU::WARN
299
319
  next
300
320
  rescue ::Aws::EC2::Errors::AuthFailure, ::Google::Apis::ClientError => e
301
321
  MU.log e.message+" in "+region, MU::ERR
@@ -310,7 +330,8 @@ module MU
310
330
  next
311
331
  end
312
332
  }
313
- had_failures = true
333
+
334
+ had_failures
314
335
  end
315
336
  private_class_method :cleanHabitat
316
337
 
@@ -321,29 +342,31 @@ module MU
321
342
  # @param flags [Hash]:
322
343
  # @param region [String]:
323
344
  def self.call_cleanup(type, credset, provider, flags, region)
345
+ Thread.current.thread_variable_set("type", type)
324
346
  if @mommacat.nil? or @mommacat.numKittens(types: [type]) > 0
325
347
  if @mommacat
348
+
326
349
  found = @mommacat.findLitterMate(type: type, return_all: true, credentials: credset)
327
- flags['known'] ||= []
328
- if found.is_a?(Array)
329
- found.each { |k|
330
- flags['known'] << k.cloud_id
331
- }
332
- elsif found and found.is_a?(Hash)
333
- flags['known'] << found['cloud_id']
334
- elsif found
335
- flags['known'] << found.cloud_id
350
+
351
+ if found
352
+ flags['known'] = if found.is_a?(Array)
353
+ found.map { |k| k.cloud_id }
354
+ elsif found.is_a?(Hash)
355
+ found.each_value.map { |k| k.cloud_id }
356
+ else
357
+ [found.cloud_id]
358
+ end
336
359
  end
337
360
  end
338
- resclass = Object.const_get("MU").const_get("Cloud").const_get(type)
339
361
 
340
- resclass.cleanup(
362
+ MU::Cloud.loadBaseType(type).cleanup(
341
363
  noop: @noop,
342
364
  ignoremaster: @ignoremaster,
343
365
  region: region,
344
366
  cloud: provider,
345
367
  flags: flags,
346
- credentials: credset
368
+ credentials: credset,
369
+ deploy_id: @deploy_id
347
370
  )
348
371
  else
349
372
  true
@@ -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, :listHabitats, :habitat, :virtual?]
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 => e
533
- if fail_hard
534
- raise MuError, "Failed to fetch stock images from #{base_url}/#{cloud}.yaml (#{e.message})"
535
- else
536
- MU.log "Failed to fetch stock images from #{base_url}/#{cloud}.yaml (#{e.message})", MU::WARN if !quiet
537
- end
538
- end
539
- end
540
- }
541
- }
542
-
543
- @@image_fetch_semaphore.synchronize {
544
- @@image_fetch_cache[cloud] = {
545
- 'contents' => images.dup,
546
- 'time' => Time.now
547
- }
548
- }
549
-
550
- backwards_compat = {
551
- "AWS" => "amazon_images",
552
- "Google" => "google_images",
553
- }
554
-
555
- # Load from inside our repository, if we didn't get images elsewise
556
- if images.nil?
557
- [backwards_compat[cloud], cloud].each { |file|
558
- next if file.nil?
559
- if File.exist?("#{MU.myRoot}/modules/mu/defaults/#{file}.yaml")
560
- images = YAML.load(File.read("#{MU.myRoot}/modules/mu/defaults/#{file}.yaml"))
561
- break
562
- end
563
- }
564
- end
565
-
566
- # Now overlay local overrides, both of the systemwide (/opt/mu/etc) and
567
- # per-user (~/.mu/etc) variety.
568
- [backwards_compat[cloud], cloud].each { |file|
569
- next if file.nil?
570
- if File.exist?("#{MU.etcDir}/#{file}.yaml")
571
- images ||= {}
572
- images.deep_merge!(YAML.load(File.read("#{MU.etcDir}/#{file}.yaml")))
573
- end
574
- if Process.uid != 0
575
- basepath = Etc.getpwuid(Process.uid).dir+"/.mu/etc"
576
- if File.exist?("#{basepath}/#{file}.yaml")
577
- images ||= {}
578
- images.deep_merge!(YAML.load(File.read("#{basepath}/#{file}.yaml")))
579
- end
580
- end
581
- }
582
-
583
- if images.nil?
584
- if fail_hard
585
- raise MuError, "Failed to find any base images for #{cloud}"
586
- else
587
- MU.log "Failed to find any base images for #{cloud}", MU::WARN if !quiet
588
- return nil
589
- end
590
- end
591
-
592
- PLATFORM_ALIASES.each_pair { |a, t|
593
- if images[t] and !images[a]
594
- images[a] = images[t]
595
- end
596
- }
597
-
598
- if platform
599
- if !images[platform]
600
- if fail_hard
601
- raise MuError, "No base image for platform #{platform} in cloud #{cloud}"
602
- else
603
- MU.log "No base image for platform #{platform} in cloud #{cloud}", MU::WARN if !quiet
604
- return nil
605
- end
606
- end
607
- images = images[platform]
608
-
609
- if region
610
- # We won't fuss about the region argument if this isn't a cloud that
611
- # has regions, just quietly don't bother.
612
- if images.is_a?(Hash)
613
- if images[region]
614
- images = images[region]
615
- else
616
- if fail_hard
617
- raise MuError, "No base image for platform #{platform} in cloud #{cloud} region #{region} found"
618
- else
619
- MU.log "No base image for platform #{platform} in cloud #{cloud} region #{region} found", MU::WARN if !quiet
620
- return nil
621
- end
622
- end
623
- end
624
- end
625
- else
626
- if region
627
- images.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
@@ -658,9 +491,9 @@ module MU
658
491
  if name == type.to_sym or
659
492
  cloudclass[:cfg_name] == type or
660
493
  cloudclass[:cfg_plural] == type or
661
- Object.const_get("MU").const_get("Cloud").const_get(name) == type
494
+ MU::Cloud.const_get(name) == type
662
495
  type = name
663
- return [type.to_sym, cloudclass[:cfg_name], cloudclass[:cfg_plural], Object.const_get("MU").const_get("Cloud").const_get(name), cloudclass]
496
+ return [type.to_sym, cloudclass[:cfg_name], cloudclass[:cfg_plural], MU::Cloud.const_get(name), cloudclass]
664
497
  end
665
498
  }
666
499
  if assert
@@ -670,80 +503,6 @@ module MU
670
503
  [nil, nil, nil, nil, {}]
671
504
  end
672
505
 
673
- # Net::SSH exceptions seem to have their own behavior vis a vis threads,
674
- # and our regular call stack gets circumvented when they're thrown. Cheat
675
- # here to catch them gracefully.
676
- def self.handleNetSSHExceptions
677
- Thread.handle_interrupt(Net::SSH::Exception => :never) {
678
- begin
679
- Thread.handle_interrupt(Net::SSH::Exception => :immediate) {
680
- MU.log "(Probably harmless) Caught a Net::SSH Exception in #{Thread.current.inspect}", MU::DEBUG, details: Thread.current.backtrace
681
- }
682
- ensure
683
- # raise NetSSHFail, "Net::SSH had a nutty"
684
- end
685
- }
686
- end
687
-
688
- # List of known/supported Cloud providers. This may be modified at runtime
689
- # if an implemention is defective or missing required methods.
690
- @@supportedCloudList = ['AWS', 'CloudFormation', 'Google', 'Azure']
691
-
692
- # List of known/supported Cloud providers
693
- # @return [Array<String>]
694
- def self.supportedClouds
695
- @@supportedCloudList
696
- end
697
-
698
- # Raise an exception if the cloud provider specified isn't valid
699
- def self.assertSupportedCloud(cloud)
700
- if cloud.nil? or !supportedClouds.include?(cloud.to_s)
701
- raise MuError, "Cloud provider #{cloud} is not supported"
702
- end
703
- Object.const_get("MU").const_get("Cloud").const_get(cloud.to_s)
704
- end
705
-
706
- # List of known/supported Cloud providers for which we have at least one
707
- # set of credentials configured.
708
- # @return [Array<String>]
709
- def self.availableClouds
710
- available = []
711
- MU::Cloud.supportedClouds.each { |cloud|
712
- begin
713
- cloudbase = Object.const_get("MU").const_get("Cloud").const_get(cloud)
714
- next if cloudbase.listCredentials.nil? or cloudbase.listCredentials.empty?
715
- available << cloud
716
- rescue NameError
717
- end
718
- }
719
-
720
- available
721
- end
722
-
723
- # Raise an exception if the cloud provider specified isn't valid or we
724
- # don't have any credentials configured for it.
725
- def self.assertAvailableCloud(cloud)
726
- if cloud.nil? or availableClouds.include?(cloud.to_s)
727
- raise MuError, "Cloud provider #{cloud} is not available"
728
- end
729
- end
730
-
731
- # Load the container class for each cloud we know about, and inject autoload
732
- # code for each of its supported resource type classes.
733
- failed = []
734
- MU::Cloud.supportedClouds.each { |cloud|
735
- require "mu/clouds/#{cloud.downcase}"
736
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
737
- generic_class_methods_toplevel.each { |method|
738
- if !cloudclass.respond_to?(method)
739
- MU.log "MU::Cloud::#{cloud} has not implemented required class method #{method}, disabling", MU::ERR
740
- failed << cloud
741
- end
742
- }
743
- }
744
- failed.uniq!
745
- @@supportedCloudList = @@supportedCloudList - failed
746
-
747
506
  # @return [Mutex]
748
507
  def self.userdata_mutex
749
508
  @userdata_mutex ||= Mutex.new
@@ -767,7 +526,7 @@ module MU
767
526
  end
768
527
  template_variables["credentials"] ||= credentials
769
528
  $mu = OpenStruct.new(template_variables)
770
- 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")
771
530
 
772
531
  platform = if %w{win2k12r2 win2k12 win2k8 win2k8r2 win2k16 windows win2k19}.include?(platform)
773
532
  "windows"
@@ -821,13 +580,25 @@ module MU
821
580
  }
822
581
  end
823
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
+
824
595
  @cloud_class_cache = {}
825
596
  # Given a cloud layer and resource type, return the class which implements it.
826
597
  # @param cloud [String]: The Cloud layer
827
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.
828
599
  # @return [Class]: The cloud-specific class implementing this resource
829
- def self.loadCloudType(cloud, type)
830
- 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?
831
602
  shortclass, cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(type)
832
603
  if @cloud_class_cache.has_key?(cloud) and @cloud_class_cache[cloud].has_key?(type)
833
604
  if @cloud_class_cache[cloud][type].nil?
@@ -839,18 +610,20 @@ module MU
839
610
  if cfg_name.nil?
840
611
  raise MuError, "Can't find a cloud resource type named '#{type}'"
841
612
  end
842
- if !File.size?(MU.myRoot+"/modules/mu/clouds/#{cloud.downcase}.rb")
613
+ if !File.size?(MU.myRoot+"/modules/mu/providers/#{cloud.downcase}.rb")
843
614
  raise MuError, "Requested to use unsupported provisioning layer #{cloud}"
844
615
  end
845
616
  begin
846
- require "mu/clouds/#{cloud.downcase}/#{cfg_name}"
617
+ require "mu/providers/#{cloud.downcase}/#{cfg_name}"
847
618
  rescue LoadError => e
848
619
  raise MuCloudResourceNotImplemented, "MU::Cloud::#{cloud} does not currently implement #{shortclass}, or implementation does not load correctly (#{e.message})"
849
620
  end
621
+
850
622
  @cloud_class_cache[cloud] = {} if !@cloud_class_cache.has_key?(cloud)
851
623
  begin
852
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
624
+ cloudclass = const_get("MU").const_get("Cloud").const_get(cloud)
853
625
  myclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(shortclass)
626
+
854
627
  @@resource_types[shortclass.to_sym][:class].each { |class_method|
855
628
  if !myclass.respond_to?(class_method) or myclass.method(class_method).owner.to_s != "#<Class:#{myclass}>"
856
629
  raise MuError, "MU::Cloud::#{cloud}::#{shortclass} has not implemented required class method #{class_method}"
@@ -868,6 +641,7 @@ module MU
868
641
  }
869
642
 
870
643
  @cloud_class_cache[cloud][type] = myclass
644
+
871
645
  return myclass
872
646
  rescue NameError => e
873
647
  @cloud_class_cache[cloud][type] = nil
@@ -875,1429 +649,9 @@ module MU
875
649
  end
876
650
  end
877
651
 
878
- MU::Cloud.supportedClouds.each { |cloud|
879
- Object.const_get("MU").const_get("Cloud").const_get(cloud).class_eval {
880
-
881
- # Automatically load supported cloud resource classes when they're
882
- # referenced.
883
- def self.const_missing(symbol)
884
- if MU::Cloud.resource_types.has_key?(symbol.to_sym)
885
- return MU::Cloud.loadCloudType(name.sub(/.*?::([^:]+)$/, '\1'), symbol)
886
- else
887
- raise MuCloudResourceNotImplemented, "No such cloud resource #{name}:#{symbol}"
888
- end
889
- end
890
- }
891
- }
892
-
893
- @@resource_types.keys.each { |name|
894
- Object.const_get("MU").const_get("Cloud").const_get(name).class_eval {
895
- attr_reader :cloudclass
896
- attr_reader :cloudobj
897
- attr_reader :credentials
898
- attr_reader :config
899
- attr_reader :destroyed
900
- attr_reader :delayed_save
901
-
902
- def self.shortname
903
- name.sub(/.*?::([^:]+)$/, '\1')
904
- end
905
-
906
- def self.cfg_plural
907
- MU::Cloud.resource_types[shortname.to_sym][:cfg_plural]
908
- end
909
-
910
- def self.has_multiples
911
- MU::Cloud.resource_types[shortname.to_sym][:has_multiples]
912
- end
913
-
914
- def self.cfg_name
915
- MU::Cloud.resource_types[shortname.to_sym][:cfg_name]
916
- end
917
-
918
- def self.can_live_in_vpc
919
- MU::Cloud.resource_types[shortname.to_sym][:can_live_in_vpc]
920
- end
921
-
922
- def self.waits_on_parent_completion
923
- MU::Cloud.resource_types[shortname.to_sym][:waits_on_parent_completion]
924
- end
925
-
926
- def self.deps_wait_on_my_creation
927
- MU::Cloud.resource_types[shortname.to_sym][:deps_wait_on_my_creation]
928
- end
929
-
930
- # Print something palatable when we're called in a string context.
931
- def to_s
932
- fullname = "#{self.class.shortname}"
933
- if !@cloudobj.nil? and !@cloudobj.mu_name.nil?
934
- @mu_name ||= @cloudobj.mu_name
935
- end
936
- if !@mu_name.nil? and !@mu_name.empty?
937
- fullname = fullname + " '#{@mu_name}'"
938
- end
939
- if !@cloud_id.nil?
940
- fullname = fullname + " (#{@cloud_id})"
941
- end
942
- return fullname
943
- end
944
-
945
- # Set our +deploy+ and +deploy_id+ attributes, optionally doing so even
946
- # if they have already been set.
947
- #
948
- # @param mommacat [MU::MommaCat]: The deploy to which we're being told we belong
949
- # @param force [Boolean]: Set even if we already have a deploy object
950
- # @return [String]: Our new +deploy_id+
951
- def intoDeploy(mommacat, force: false)
952
- if force or (!@deploy)
953
- MU.log "Inserting #{self} [#{self.object_id}] into #{mommacat.deploy_id} as a #{@config['name']}", MU::DEBUG
954
-
955
- @deploy = mommacat
956
- @deploy.addKitten(@cloudclass.cfg_plural, @config['name'], self)
957
- @deploy_id = @deploy.deploy_id
958
- @cloudobj.intoDeploy(mommacat, force: force) if @cloudobj
959
- end
960
- @deploy_id
961
- end
962
-
963
- # Return the +virtual_name+ config field, if it is set.
964
- # @param name [String]: If set, will only return a value if +virtual_name+ matches this string
965
- # @return [String,nil]
966
- def virtual_name(name = nil)
967
- if @config and @config['virtual_name'] and
968
- (!name or name == @config['virtual_name'])
969
- return @config['virtual_name']
970
- end
971
- nil
972
- end
973
-
974
- # @param mommacat [MU::MommaCat]: The deployment containing this cloud resource
975
- # @param mu_name [String]: Optional- specify the full Mu resource name of an existing resource to load, instead of creating a new one
976
- # @param cloud_id [String]: Optional- specify the cloud provider's identifier for an existing resource to load, instead of creating a new one
977
- # @param kitten_cfg [Hash]: The parse configuration for this object from {MU::Config}
978
- def initialize(**args)
979
- raise MuError, "Cannot invoke Cloud objects without a configuration" if args[:kitten_cfg].nil?
980
-
981
- # We are a parent wrapper object. Initialize our child object and
982
- # housekeeping bits accordingly.
983
- if self.class.name.match(/^MU::Cloud::([^:]+)$/)
984
- @live = true
985
- @delayed_save = args[:delayed_save]
986
- @method_semaphore = Mutex.new
987
- @method_locks = {}
988
- if args[:mommacat]
989
- MU.log "Initializing an instance of #{self.class.name} in #{args[:mommacat].deploy_id} #{mu_name}", MU::DEBUG, details: args[:kitten_cfg]
990
- elsif args[:mu_name].nil?
991
- raise MuError, "Can't instantiate a MU::Cloud object with a live deploy or giving us a mu_name"
992
- else
993
- MU.log "Initializing a detached #{self.class.name} named #{args[:mu_name]}", MU::DEBUG, details: args[:kitten_cfg]
994
- end
995
-
996
- my_cloud = args[:kitten_cfg]['cloud'].to_s || MU::Config.defaultCloud
997
- if my_cloud.nil? or !MU::Cloud.supportedClouds.include?(my_cloud)
998
- raise MuError, "Can't instantiate a MU::Cloud object without a valid cloud (saw '#{my_cloud}')"
999
- end
1000
- @cloudclass = MU::Cloud.loadCloudType(my_cloud, self.class.shortname)
1001
- @cloudparentclass = Object.const_get("MU").const_get("Cloud").const_get(my_cloud)
1002
- @cloudobj = @cloudclass.new(
1003
- mommacat: args[:mommacat],
1004
- kitten_cfg: args[:kitten_cfg],
1005
- cloud_id: args[:cloud_id],
1006
- mu_name: args[:mu_name]
1007
- )
1008
- raise MuError, "Unknown error instantiating #{self}" if @cloudobj.nil?
1009
- # These should actually call the method live instead of caching a static value
1010
- PUBLIC_ATTRS.each { |a|
1011
- instance_variable_set(("@"+a.to_s).to_sym, @cloudobj.send(a))
1012
- }
1013
- @deploy ||= args[:mommacat]
1014
- @deploy_id ||= @deploy.deploy_id if @deploy
1015
-
1016
- # Register with the containing deployment
1017
- if !@deploy.nil? and !@cloudobj.mu_name.nil? and
1018
- !@cloudobj.mu_name.empty? and !args[:delay_descriptor_load]
1019
- describe # XXX is this actually safe here?
1020
- @deploy.addKitten(self.class.cfg_name, @config['name'], self)
1021
- elsif !@deploy.nil? and @cloudobj.mu_name.nil?
1022
- 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]
1023
- end
1024
-
1025
- # We are actually a child object invoking this via super() from its
1026
- # own initialize(), so initialize all the attributes and instance
1027
- # variables we know to be universal.
1028
- else
1029
-
1030
- # Declare the attributes that everyone should have
1031
- class << self
1032
- PUBLIC_ATTRS.each { |a|
1033
- attr_reader a
1034
- }
1035
- end
1036
-
1037
- # 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
1038
- @config = MU::Config.manxify(args[:kitten_cfg]) || MU::Config.manxify(args[:config])
1039
-
1040
- if !@config
1041
- MU.log "Missing config arguments in setInstanceVariables, can't initialize a cloud object without it", MU::ERR, details: args.keys
1042
- raise MuError, "Missing config arguments in setInstanceVariables"
1043
- end
1044
-
1045
- @deploy = args[:mommacat] || args[:deploy]
1046
-
1047
- @credentials = args[:credentials]
1048
- @credentials ||= @config['credentials']
1049
-
1050
- @cloud = @config['cloud']
1051
- if !@cloud
1052
- if self.class.name.match(/^MU::Cloud::([^:]+)(?:::.+|$)/)
1053
- cloudclass_name = Regexp.last_match[1]
1054
- if MU::Cloud.supportedClouds.include?(cloudclass_name)
1055
- @cloud = cloudclass_name
1056
- end
1057
- end
1058
- end
1059
- if !@cloud
1060
- raise MuError, "Failed to determine what cloud #{self} should be in!"
1061
- end
1062
-
1063
- @environment = @config['environment']
1064
- if @deploy
1065
- @deploy_id = @deploy.deploy_id
1066
- @appname = @deploy.appname
1067
- end
1068
-
1069
- @cloudclass = MU::Cloud.loadCloudType(@cloud, self.class.shortname)
1070
- @cloudparentclass = Object.const_get("MU").const_get("Cloud").const_get(@cloud)
1071
-
1072
- # A pre-existing object, you say?
1073
- if args[:cloud_id]
1074
-
1075
- # TODO implement ::Id for every cloud... and they should know how to get from
1076
- # cloud_desc to a fully-resolved ::Id object, not just the short string
1077
-
1078
- @cloud_id = args[:cloud_id]
1079
- describe(cloud_id: @cloud_id)
1080
- @habitat_id = habitat_id # effectively, cache this
1081
-
1082
- # If we can build us an ::Id object for @cloud_id instead of a
1083
- # string, do so.
1084
- begin
1085
- idclass = Object.const_get("MU").const_get("Cloud").const_get(@cloud).const_get("Id")
1086
- long_id = if @deploydata and @deploydata[idclass.idattr.to_s]
1087
- @deploydata[idclass.idattr.to_s]
1088
- elsif self.respond_to?(idclass.idattr)
1089
- self.send(idclass.idattr)
1090
- end
1091
-
1092
- @cloud_id = idclass.new(long_id) if !long_id.nil? and !long_id.empty?
1093
- # 1 see if we have the value on the object directly or in deploy data
1094
- # 2 set an attr_reader with the value
1095
- # 3 rewrite our @cloud_id attribute with a ::Id object
1096
- rescue NameError, MU::Cloud::MuCloudResourceNotImplemented
1097
- end
1098
-
1099
- end
1100
-
1101
- # Use pre-existing mu_name (we're probably loading an extant deploy)
1102
- # if available
1103
- if args[:mu_name]
1104
- @mu_name = args[:mu_name].dup
1105
- # If scrub_mu_isms is set, our mu_name is always just the bare name
1106
- # field of the resource.
1107
- elsif @config['scrub_mu_isms']
1108
- @mu_name = @config['name'].dup
1109
- # XXX feck it insert an inheritable method right here? Set a default? How should resource implementations determine whether they're instantiating a new object?
1110
- end
1111
-
1112
- @tags = {}
1113
- if !@config['scrub_mu_isms']
1114
- @tags = @deploy ? @deploy.listStandardTags : MU::MommaCat.listStandardTags
1115
- end
1116
- if @config['tags']
1117
- @config['tags'].each { |tag|
1118
- @tags[tag['key']] = tag['value']
1119
- }
1120
- end
1121
-
1122
- if @cloudparentclass.respond_to?(:resourceInitHook)
1123
- @cloudparentclass.resourceInitHook(self, @deploy)
1124
- end
1125
-
1126
- # Add cloud-specific instance methods for our resource objects to
1127
- # inherit.
1128
- if @cloudparentclass.const_defined?(:AdditionalResourceMethods)
1129
- self.extend @cloudparentclass.const_get(:AdditionalResourceMethods)
1130
- end
1131
-
1132
- if ["Server", "ServerPool"].include?(self.class.shortname) and @deploy
1133
- @mu_name ||= @deploy.getResourceName(@config['name'], need_unique_string: @config.has_key?("basis"))
1134
- if self.class.shortname == "Server"
1135
- @groomer = MU::Groomer.new(self)
1136
- end
1137
-
1138
- @groomclass = MU::Groomer.loadGroomer(@config["groomer"])
1139
-
1140
- if windows? or @config['active_directory'] and !@mu_windows_name
1141
- if !@deploydata.nil? and !@deploydata['mu_windows_name'].nil?
1142
- @mu_windows_name = @deploydata['mu_windows_name']
1143
- else
1144
- # Use the same random differentiator as the "real" name if we're
1145
- # from a ServerPool. Helpful for admin sanity.
1146
- unq = @mu_name.sub(/^.*?-(...)$/, '\1')
1147
- if @config['basis'] and !unq.nil? and !unq.empty?
1148
- @mu_windows_name = @deploy.getResourceName(@config['name'], max_length: 15, need_unique_string: true, use_unique_string: unq, reuse_unique_string: true)
1149
- else
1150
- @mu_windows_name = @deploy.getResourceName(@config['name'], max_length: 15, need_unique_string: true)
1151
- end
1152
- end
1153
- end
1154
- class << self
1155
- attr_reader :groomer
1156
- attr_reader :groomerclass
1157
- attr_accessor :mu_windows_name # XXX might be ok as reader now
1158
- end
1159
- end
1160
- end
1161
-
1162
- end
1163
-
1164
- def cloud
1165
- if @cloud
1166
- @cloud
1167
- elsif @config and @config['cloud']
1168
- @config['cloud']
1169
- elsif self.class.name.match(/^MU::Cloud::([^:]+)::.+/)
1170
- cloudclass_name = Regexp.last_match[1]
1171
- if MU::Cloud.supportedClouds.include?(cloudclass_name)
1172
- cloudclass_name
1173
- else
1174
- nil
1175
- end
1176
- else
1177
- nil
1178
- end
1179
- end
1180
-
1181
-
1182
- # Remove all metadata and cloud resources associated with this object
1183
- def destroy
1184
- if self.class.cfg_name == "server"
1185
- begin
1186
- ip = canonicalIP
1187
- MU::Master.removeIPFromSSHKnownHosts(ip) if ip
1188
- if @deploy and @deploy.deployment and
1189
- @deploy.deployment['servers'] and @config['name']
1190
- me = @deploy.deployment['servers'][@config['name']][@mu_name]
1191
- if me
1192
- ["private_ip_address", "public_ip_address"].each { |field|
1193
- if me[field]
1194
- MU::Master.removeIPFromSSHKnownHosts(me[field])
1195
- end
1196
- }
1197
- if me["private_ip_list"]
1198
- me["private_ip_list"].each { |private_ip|
1199
- MU::Master.removeIPFromSSHKnownHosts(private_ip)
1200
- }
1201
- end
1202
- end
1203
- end
1204
- rescue MU::MuError => e
1205
- MU.log e.message, MU::WARN
1206
- end
1207
- end
1208
- if !@cloudobj.nil? and !@cloudobj.groomer.nil?
1209
- @cloudobj.groomer.cleanup
1210
- elsif !@groomer.nil?
1211
- @groomer.cleanup
1212
- end
1213
- if !@deploy.nil?
1214
- if !@cloudobj.nil? and !@config.nil? and !@cloudobj.mu_name.nil?
1215
- @deploy.notify(self.class.cfg_plural, @config['name'], nil, mu_name: @cloudobj.mu_name, remove: true, triggering_node: @cloudobj, delayed_save: @delayed_save)
1216
- elsif !@mu_name.nil?
1217
- @deploy.notify(self.class.cfg_plural, @config['name'], nil, mu_name: @mu_name, remove: true, triggering_node: self, delayed_save: @delayed_save)
1218
- end
1219
- @deploy.removeKitten(self)
1220
- end
1221
- # Make sure that if notify gets called again it won't go returning a
1222
- # bunch of now-bogus metadata.
1223
- @destroyed = true
1224
- if !@cloudobj.nil?
1225
- def @cloudobj.notify
1226
- {}
1227
- end
1228
- else
1229
- def notify
1230
- {}
1231
- end
1232
- end
1233
- end
1234
-
1235
- # Return the cloud object's idea of where it lives (project, account,
1236
- # etc) in the form of an identifier. If not applicable for this object,
1237
- # we expect to return +nil+.
1238
- # @return [String,nil]
1239
- def habitat(nolookup: true)
1240
- return nil if ["folder", "habitat"].include?(self.class.cfg_name)
1241
- if @cloudobj
1242
- @cloudparentclass.habitat(@cloudobj, nolookup: nolookup, deploy: @deploy)
1243
- else
1244
- @cloudparentclass.habitat(self, nolookup: nolookup, deploy: @deploy)
1245
- end
1246
- end
1247
-
1248
- def habitat_id(nolookup: false)
1249
- @habitat_id ||= habitat(nolookup: nolookup)
1250
- @habitat_id
1251
- end
1252
-
1253
- # We're fundamentally a wrapper class, so go ahead and reroute requests
1254
- # that are meant for our wrapped object.
1255
- def method_missing(method_sym, *arguments)
1256
- if @cloudobj
1257
- MU.log "INVOKING #{method_sym.to_s} FROM PARENT CLOUD OBJECT #{self}", MU::DEBUG, details: arguments
1258
- @cloudobj.method(method_sym).call(*arguments)
1259
- else
1260
- raise NoMethodError, "No such instance method #{method_sym.to_s} available on #{self.class.name}"
1261
- end
1262
- end
1263
-
1264
- # Merge the passed hash into the existing configuration hash of this
1265
- # cloud object. Currently this is only used by the {MU::Adoption}
1266
- # module. I don't love exposing this to the whole internal API, but I'm
1267
- # probably overthinking that.
1268
- # @param newcfg [Hash]
1269
- def config!(newcfg)
1270
- @config.merge!(newcfg)
1271
- end
1272
-
1273
- def cloud_desc(use_cache: true)
1274
- describe
1275
-
1276
- if !@cloudobj.nil?
1277
- if @cloudobj.class.instance_methods(false).include?(:cloud_desc)
1278
- @cloud_desc_cache ||= @cloudobj.cloud_desc
1279
- end
1280
- end
1281
- if !@config.nil? and !@cloud_id.nil? and (!use_cache or @cloud_desc_cache.nil?)
1282
- # The find() method should be returning a Hash with the cloud_id
1283
- # as a key and a cloud platform descriptor as the value.
1284
- begin
1285
- args = {
1286
- :region => @config['region'],
1287
- :cloud => @config['cloud'],
1288
- :cloud_id => @cloud_id,
1289
- :credentials => @credentials,
1290
- :project => habitat_id, # XXX this belongs in our required_instance_methods hack
1291
- :flags => @config
1292
- }
1293
- @cloudparentclass.required_instance_methods.each { |m|
1294
- # if respond_to?(m)
1295
- # args[m] = method(m).call
1296
- # else
1297
- args[m] = instance_variable_get(("@"+m.to_s).to_sym)
1298
- # end
1299
- }
1300
-
1301
- matches = self.class.find(args)
1302
- if !matches.nil? and matches.is_a?(Hash)
1303
- # XXX or if the hash is keyed with an ::Id element, oh boy
1304
- # puts matches[@cloud_id][:self_link]
1305
- # puts matches[@cloud_id][:url]
1306
- # if matches[@cloud_id][:self_link]
1307
- # @url ||= matches[@cloud_id][:self_link]
1308
- # elsif matches[@cloud_id][:url]
1309
- # @url ||= matches[@cloud_id][:url]
1310
- # elsif matches[@cloud_id][:arn]
1311
- # @arn ||= matches[@cloud_id][:arn]
1312
- # end
1313
- if matches[@cloud_id]
1314
- @cloud_desc_cache = matches[@cloud_id]
1315
- else
1316
- matches.each_pair { |k, v| # flatten out ::Id objects just in case
1317
- if @cloud_id.to_s == k.to_s
1318
- @cloud_desc_cache = v
1319
- break
1320
- end
1321
- }
1322
- end
1323
- end
1324
-
1325
- if !@cloud_desc_cache
1326
- MU.log "cloud_desc via #{self.class.name}.find() failed to locate a live object.\nWas called by #{caller[0]}", MU::WARN, details: args
1327
- end
1328
- rescue StandardError => e
1329
- MU.log "Got #{e.inspect} trying to find cloud handle for #{self.class.shortname} #{@mu_name} (#{@cloud_id})", MU::WARN
1330
- raise e
1331
- end
1332
- end
1333
-
1334
- return @cloud_desc_cache
1335
- end
1336
-
1337
- # Retrieve all of the known metadata for this resource.
1338
- # @param cloud_id [String]: The cloud platform's identifier for the resource we're describing. Makes lookups more efficient.
1339
- # @return [Array<Hash>]: mu_name, config, deploydata
1340
- def describe(cloud_id: nil)
1341
- if cloud_id.nil? and !@cloudobj.nil?
1342
- @cloud_id ||= @cloudobj.cloud_id
1343
- end
1344
- res_type = self.class.cfg_plural
1345
- res_name = @config['name'] if !@config.nil?
1346
- @credentials ||= @config['credentials'] if !@config.nil?
1347
- deploydata = nil
1348
- if !@deploy.nil? and @deploy.is_a?(MU::MommaCat) and
1349
- !@deploy.deployment.nil? and
1350
- !@deploy.deployment[res_type].nil? and
1351
- !@deploy.deployment[res_type][res_name].nil?
1352
- deploydata = @deploy.deployment[res_type][res_name]
1353
- else
1354
- # XXX This should only happen on a brand new resource, but we should
1355
- # probably complain under other circumstances, if we can
1356
- # differentiate them.
1357
- end
1358
-
1359
- if self.class.has_multiples and !@mu_name.nil? and deploydata.is_a?(Hash) and deploydata.has_key?(@mu_name)
1360
- @deploydata = deploydata[@mu_name]
1361
- elsif deploydata.is_a?(Hash)
1362
- @deploydata = deploydata
1363
- end
1364
-
1365
- if @cloud_id.nil? and @deploydata.is_a?(Hash)
1366
- if @mu_name.nil? and @deploydata.has_key?('#MU_NAME')
1367
- @mu_name = @deploydata['#MU_NAME']
1368
- end
1369
- if @deploydata.has_key?('cloud_id')
1370
- @cloud_id ||= @deploydata['cloud_id']
1371
- end
1372
- end
1373
-
1374
- return [@mu_name, @config, @deploydata]
1375
- end
1376
-
1377
- # Fetch MU::Cloud objects for each of this object's dependencies, and
1378
- # return in an easily-navigable Hash. This can include things listed in
1379
- # @config['dependencies'], implicitly-defined dependencies such as
1380
- # add_firewall_rules or vpc stanzas, and may refer to objects internal
1381
- # to this deployment or external. Will populate the instance variables
1382
- # @dependencies (general dependencies, which can only be sibling
1383
- # resources in this deployment), as well as for certain config stanzas
1384
- # which can refer to external resources (@vpc, @loadbalancers,
1385
- # @add_firewall_rules)
1386
- def dependencies(use_cache: false, debug: false)
1387
- @dependencies ||= {}
1388
- @loadbalancers ||= []
1389
- @firewall_rules ||= []
1390
-
1391
- if @config.nil?
1392
- return [@dependencies, @vpc, @loadbalancers]
1393
- end
1394
- if use_cache and @dependencies.size > 0
1395
- return [@dependencies, @vpc, @loadbalancers]
1396
- end
1397
- @config['dependencies'] = [] if @config['dependencies'].nil?
1398
-
1399
- loglevel = debug ? MU::NOTICE : MU::DEBUG
1400
-
1401
- # First, general dependencies. These should all be fellow members of
1402
- # the current deployment.
1403
- @config['dependencies'].each { |dep|
1404
- @dependencies[dep['type']] ||= {}
1405
- next if @dependencies[dep['type']].has_key?(dep['name'])
1406
- handle = @deploy.findLitterMate(type: dep['type'], name: dep['name']) if !@deploy.nil?
1407
- if !handle.nil?
1408
- MU.log "Loaded dependency for #{self}: #{dep['name']} => #{handle}", loglevel
1409
- @dependencies[dep['type']][dep['name']] = handle
1410
- else
1411
- # XXX yell under circumstances where we should expect to have
1412
- # our stuff available already?
1413
- end
1414
- }
1415
-
1416
- # Special dependencies: my containing VPC
1417
- if self.class.can_live_in_vpc and !@config['vpc'].nil?
1418
- @config['vpc']["id"] ||= @config['vpc']["vpc_id"] # old deploys
1419
- @config['vpc']["name"] ||= @config['vpc']["vpc_name"] # old deploys
1420
- # If something hash-ified a MU::Config::Ref here, fix it
1421
- if !@config['vpc']["id"].nil? and @config['vpc']["id"].is_a?(Hash)
1422
- @config['vpc']["id"] = MU::Config::Ref.new(@config['vpc']["id"])
1423
- end
1424
- if !@config['vpc']["id"].nil?
1425
- if @config['vpc']["id"].is_a?(MU::Config::Ref) and !@config['vpc']["id"].kitten.nil?
1426
- @vpc = @config['vpc']["id"].kitten
1427
- else
1428
- if @config['vpc']['habitat']
1429
- @config['vpc']['habitat'] = MU::Config::Ref.get(@config['vpc']['habitat'])
1430
- end
1431
- vpc_ref = MU::Config::Ref.get(@config['vpc'])
1432
- @vpc = vpc_ref.kitten
1433
- end
1434
- elsif !@config['vpc']["name"].nil? and @deploy
1435
- MU.log "Attempting findLitterMate on VPC for #{self}", loglevel, details: @config['vpc']
1436
-
1437
- sib_by_name = @deploy.findLitterMate(name: @config['vpc']['name'], type: "vpcs", return_all: true, habitat: @config['vpc']['project'], debug: debug)
1438
- if sib_by_name.is_a?(Array)
1439
- if sib_by_name.size == 1
1440
- @vpc = matches.first
1441
- MU.log "Single VPC match for #{self}", loglevel, details: @vpc.to_s
1442
- else
1443
- # XXX ok but this is the wrong place for this really the config parser needs to sort this out somehow
1444
- # we got multiple matches, try to pick one by preferred subnet
1445
- # behavior
1446
- MU.log "Sorting a bunch of VPC matches for #{self}", loglevel, details: sib_by_name.map { |s| s.to_s }.join(", ")
1447
- sib_by_name.each { |sibling|
1448
- all_private = sibling.subnets.map { |s| s.private? }.all?(true)
1449
- all_public = sibling.subnets.map { |s| s.private? }.all?(false)
1450
- names = sibling.subnets.map { |s| s.name }
1451
- ids = sibling.subnets.map { |s| s.cloud_id }
1452
- if all_private and ["private", "all_private"].include?(@config['vpc']['subnet_pref'])
1453
- @vpc = sibling
1454
- break
1455
- elsif all_public and ["public", "all_public"].include?(@config['vpc']['subnet_pref'])
1456
- @vpc = sibling
1457
- break
1458
- elsif @config['vpc']['subnet_name'] and
1459
- names.include?(@config['vpc']['subnet_name'])
1460
- puts "CHOOSING #{@vpc.to_s} 'cause it has #{@config['vpc']['subnet_name']}"
1461
- @vpc = sibling
1462
- break
1463
- elsif @config['vpc']['subnet_id'] and
1464
- ids.include?(@config['vpc']['subnet_id'])
1465
- @vpc = sibling
1466
- break
1467
- end
1468
- }
1469
- if !@vpc
1470
- sibling = sib_by_name.sample
1471
- 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']
1472
- @vpc = sibling
1473
- end
1474
- end
1475
- else
1476
- @vpc = sib_by_name
1477
- MU.log "Found exact VPC match for #{self}", loglevel, details: sib_by_name.to_s
1478
- end
1479
- else
1480
- MU.log "No shortcuts available to fetch VPC for #{self}", loglevel, details: @config['vpc']
1481
- end
1482
-
1483
- if !@vpc and !@config['vpc']["name"].nil? and
1484
- @dependencies.has_key?("vpc") and
1485
- @dependencies["vpc"].has_key?(@config['vpc']["name"])
1486
- MU.log "Grabbing VPC I see in @dependencies['vpc']['#{@config['vpc']["name"]}'] for #{self}", loglevel, details: @config['vpc']
1487
- @vpc = @dependencies["vpc"][@config['vpc']["name"]]
1488
- elsif !@vpc
1489
- tag_key, tag_value = @config['vpc']['tag'].split(/=/, 2) if !@config['vpc']['tag'].nil?
1490
- if !@config['vpc'].has_key?("id") and
1491
- !@config['vpc'].has_key?("deploy_id") and !@deploy.nil?
1492
- @config['vpc']["deploy_id"] = @deploy.deploy_id
1493
- end
1494
- MU.log "Doing findStray for VPC for #{self}", loglevel, details: @config['vpc']
1495
- vpcs = MU::MommaCat.findStray(
1496
- @config['cloud'],
1497
- "vpc",
1498
- deploy_id: @config['vpc']["deploy_id"],
1499
- cloud_id: @config['vpc']["id"],
1500
- name: @config['vpc']["name"],
1501
- tag_key: tag_key,
1502
- tag_value: tag_value,
1503
- habitats: [@project_id],
1504
- region: @config['vpc']["region"],
1505
- calling_deploy: @deploy,
1506
- credentials: @credentials,
1507
- dummy_ok: true,
1508
- debug: debug
1509
- )
1510
- @vpc = vpcs.first if !vpcs.nil? and vpcs.size > 0
1511
- end
1512
- if @vpc and @vpc.config and @vpc.config['bastion'] and
1513
- @vpc.config['bastion'].to_h['name'] != @config['name']
1514
- refhash = @vpc.config['bastion'].to_h
1515
- refhash['deploy_id'] ||= @vpc.deploy.deploy_id
1516
- natref = MU::Config::Ref.get(refhash)
1517
- if natref and natref.kitten(@vpc.deploy)
1518
- @nat = natref.kitten(@vpc.deploy)
1519
- end
1520
- end
1521
- if @nat.nil? and !@vpc.nil? and (
1522
- @config['vpc'].has_key?("nat_host_id") or
1523
- @config['vpc'].has_key?("nat_host_tag") or
1524
- @config['vpc'].has_key?("nat_host_ip") or
1525
- @config['vpc'].has_key?("nat_host_name")
1526
- )
1527
-
1528
- nat_tag_key, nat_tag_value = @config['vpc']['nat_host_tag'].split(/=/, 2) if !@config['vpc']['nat_host_tag'].nil?
1529
-
1530
- @nat = @vpc.findBastion(
1531
- nat_name: @config['vpc']['nat_host_name'],
1532
- nat_cloud_id: @config['vpc']['nat_host_id'],
1533
- nat_tag_key: nat_tag_key,
1534
- nat_tag_value: nat_tag_value,
1535
- nat_ip: @config['vpc']['nat_host_ip']
1536
- )
1537
-
1538
- if @nat.nil?
1539
- if !@vpc.cloud_desc.nil?
1540
- @nat = @vpc.findNat(
1541
- nat_cloud_id: @config['vpc']['nat_host_id'],
1542
- nat_filter_key: "vpc-id",
1543
- region: @config['vpc']["region"],
1544
- nat_filter_value: @vpc.cloud_id,
1545
- credentials: @config['credentials']
1546
- )
1547
- else
1548
- @nat = @vpc.findNat(
1549
- nat_cloud_id: @config['vpc']['nat_host_id'],
1550
- region: @config['vpc']["region"],
1551
- credentials: @config['credentials']
1552
- )
1553
- end
1554
- end
1555
- end
1556
- elsif self.class.cfg_name == "vpc"
1557
- @vpc = self
1558
- end
1559
-
1560
- # Google accounts usually have a useful default VPC we can use
1561
- if @vpc.nil? and @project_id and @cloud == "Google" and
1562
- self.class.can_live_in_vpc
1563
- MU.log "Seeing about default VPC for #{self.to_s}", MU::NOTICE
1564
- vpcs = MU::MommaCat.findStray(
1565
- "Google",
1566
- "vpc",
1567
- cloud_id: "default",
1568
- habitats: [@project_id],
1569
- credentials: @credentials,
1570
- dummy_ok: true,
1571
- debug: debug
1572
- )
1573
- @vpc = vpcs.first if !vpcs.nil? and vpcs.size > 0
1574
- end
1575
-
1576
- # Special dependencies: LoadBalancers I've asked to attach to an
1577
- # instance.
1578
- if @config.has_key?("loadbalancers")
1579
- @loadbalancers = [] if !@loadbalancers
1580
- @config['loadbalancers'].each { |lb|
1581
- MU.log "Loading LoadBalancer for #{self}", MU::DEBUG, details: lb
1582
- if @dependencies.has_key?("loadbalancer") and
1583
- @dependencies["loadbalancer"].has_key?(lb['concurrent_load_balancer'])
1584
- @loadbalancers << @dependencies["loadbalancer"][lb['concurrent_load_balancer']]
1585
- else
1586
- if !lb.has_key?("existing_load_balancer") and
1587
- !lb.has_key?("deploy_id") and !@deploy.nil?
1588
- lb["deploy_id"] = @deploy.deploy_id
1589
- end
1590
- lbs = MU::MommaCat.findStray(
1591
- @config['cloud'],
1592
- "loadbalancer",
1593
- deploy_id: lb["deploy_id"],
1594
- cloud_id: lb['existing_load_balancer'],
1595
- name: lb['concurrent_load_balancer'],
1596
- region: @config["region"],
1597
- calling_deploy: @deploy,
1598
- dummy_ok: true
1599
- )
1600
- @loadbalancers << lbs.first if !lbs.nil? and lbs.size > 0
1601
- end
1602
- }
1603
- end
1604
-
1605
- # Munge in external resources referenced by the existing_deploys
1606
- # keyword
1607
- if @config["existing_deploys"] && !@config["existing_deploys"].empty?
1608
- @config["existing_deploys"].each { |ext_deploy|
1609
- if ext_deploy["cloud_id"]
1610
- found = MU::MommaCat.findStray(
1611
- @config['cloud'],
1612
- ext_deploy["cloud_type"],
1613
- cloud_id: ext_deploy["cloud_id"],
1614
- region: @config['region'],
1615
- dummy_ok: false
1616
- ).first
1617
-
1618
- MU.log "Couldn't find existing resource #{ext_deploy["cloud_id"]}, #{ext_deploy["cloud_type"]}", MU::ERR if found.nil?
1619
- @deploy.notify(ext_deploy["cloud_type"], found.config["name"], found.deploydata, mu_name: found.mu_name, triggering_node: @mu_name)
1620
- elsif ext_deploy["mu_name"] && ext_deploy["deploy_id"]
1621
- MU.log "#{ext_deploy["mu_name"]} / #{ext_deploy["deploy_id"]}"
1622
- found = MU::MommaCat.findStray(
1623
- @config['cloud'],
1624
- ext_deploy["cloud_type"],
1625
- deploy_id: ext_deploy["deploy_id"],
1626
- mu_name: ext_deploy["mu_name"],
1627
- region: @config['region'],
1628
- dummy_ok: false
1629
- ).first
1630
-
1631
- MU.log "Couldn't find existing resource #{ext_deploy["mu_name"]}/#{ext_deploy["deploy_id"]}, #{ext_deploy["cloud_type"]}", MU::ERR if found.nil?
1632
- @deploy.notify(ext_deploy["cloud_type"], found.config["name"], found.deploydata, mu_name: ext_deploy["mu_name"], triggering_node: @mu_name)
1633
- else
1634
- MU.log "Trying to find existing deploy, but either the cloud_id is not valid or no mu_name and deploy_id where provided", MU::ERR
1635
- end
1636
- }
1637
- end
1638
-
1639
- if @config['dns_records'] && !@config['dns_records'].empty?
1640
- @config['dns_records'].each { |dnsrec|
1641
- if dnsrec.has_key?("name")
1642
- if dnsrec['name'].start_with?(@deploy.deploy_id.downcase) && !dnsrec['name'].start_with?(@mu_name.downcase)
1643
- MU.log "DNS records for #{@mu_name} seem to be wrong, deleting from current config", MU::WARN, details: dnsrec
1644
- dnsrec.delete('name')
1645
- dnsrec.delete('target')
1646
- end
1647
- end
1648
- }
1649
- end
1650
-
1651
- return [@dependencies, @vpc, @loadbalancers]
1652
- end
1653
-
1654
- # Using the automatically-defined +@vpc+ from {dependencies} in
1655
- # conjunction with our config, return our configured subnets.
1656
- # @return [Array<MU::Cloud::VPC::Subnet>]
1657
- def mySubnets
1658
- dependencies
1659
- if !@vpc or !@config["vpc"]
1660
- return nil
1661
- end
1662
-
1663
- if @config["vpc"]["subnet_id"] or @config["vpc"]["subnet_name"]
1664
- @config["vpc"]["subnets"] ||= []
1665
- subnet_block = {}
1666
- subnet_block["subnet_id"] = @config["vpc"]["subnet_id"] if @config["vpc"]["subnet_id"]
1667
- subnet_block["subnet_name"] = @config["vpc"]["subnet_name"] if @config["vpc"]["subnet_name"]
1668
- @config["vpc"]["subnets"] << subnet_block
1669
- @config["vpc"]["subnets"].uniq!
1670
- end
1671
-
1672
- if (!@config["vpc"]["subnets"] or @config["vpc"]["subnets"].empty?) and
1673
- !@config["vpc"]["subnet_id"]
1674
- return @vpc.subnets
1675
- end
1676
-
1677
- subnets = []
1678
- @config["vpc"]["subnets"].each { |subnet|
1679
- subnet_obj = @vpc.getSubnet(cloud_id: subnet["subnet_id"].to_s, name: subnet["subnet_name"].to_s)
1680
- raise MuError, "Couldn't find a live subnet for #{self.to_s} matching #{subnet} in #{@vpc.to_s} (#{@vpc.subnets.map { |s| s.name }.join(",")})" if subnet_obj.nil?
1681
- subnets << subnet_obj
1682
- }
1683
-
1684
- subnets
1685
- end
1686
-
1687
- # @return [Array<MU::Cloud::FirewallRule>]
1688
- def myFirewallRules
1689
- dependencies
1690
-
1691
- rules = []
1692
- if @dependencies.has_key?("firewall_rule")
1693
- rules = @dependencies['firewall_rule'].values
1694
- end
1695
- # XXX what other ways are these specified?
1696
-
1697
- rules
1698
- end
1699
-
1700
- # Defaults any resources that don't declare their release-readiness to
1701
- # ALPHA. That'll learn 'em.
1702
- def self.quality
1703
- MU::Cloud::ALPHA
1704
- end
1705
-
1706
- # Return a list of "container" artifacts, by class, that apply to this
1707
- # resource type in a cloud provider. This is so methods that call find
1708
- # know whether to call +find+ with identifiers for parent resources.
1709
- # This is similar in purpose to the +isGlobal?+ resource class method,
1710
- # which tells our search functions whether or not a resource scopes to
1711
- # a region. In almost all cases this is one-entry list consisting of
1712
- # +:Habitat+. Notable exceptions include most implementations of
1713
- # +Habitat+, which either reside inside a +:Folder+ or nothing at all;
1714
- # whereas a +:Folder+ tends to not have any containing parent. Very few
1715
- # resource implementations will need to override this.
1716
- # A +nil+ entry in this list is interpreted as "this resource can be
1717
- # global."
1718
- # @return [Array<Symbol,nil>]
1719
- def self.canLiveIn
1720
- if self.shortname == "Folder"
1721
- [nil, :Folder]
1722
- elsif self.shortname == "Habitat"
1723
- [:Folder]
1724
- else
1725
- [:Habitat]
1726
- end
1727
- end
1728
-
1729
- def self.find(*flags)
1730
- allfound = {}
1731
-
1732
- MU::Cloud.availableClouds.each { |cloud|
1733
- begin
1734
- args = flags.first
1735
- next if args[:cloud] and args[:cloud] != cloud
1736
- # skip this cloud if we have a region argument that makes no
1737
- # sense there
1738
- cloudbase = Object.const_get("MU").const_get("Cloud").const_get(cloud)
1739
- next if cloudbase.listCredentials.nil? or cloudbase.listCredentials.empty? or cloudbase.credConfig(args[:credentials]).nil?
1740
- if args[:region] and cloudbase.respond_to?(:listRegions)
1741
- if !cloudbase.listRegions(credentials: args[:credentials])
1742
- MU.log "Failed to get region list for credentials #{args[:credentials]} in cloud #{cloud}", MU::ERR, details: caller
1743
- else
1744
- next if !cloudbase.listRegions(credentials: args[:credentials]).include?(args[:region])
1745
- end
1746
- end
1747
- begin
1748
- cloudclass = MU::Cloud.loadCloudType(cloud, shortname)
1749
- rescue MU::MuError
1750
- next
1751
- end
1752
-
1753
- found = cloudclass.find(args)
1754
- if !found.nil?
1755
- if found.is_a?(Hash)
1756
- allfound.merge!(found)
1757
- else
1758
- raise MuError, "#{cloudclass}.find returned a non-Hash result"
1759
- end
1760
- end
1761
- rescue MuCloudResourceNotImplemented
1762
- end
1763
- }
1764
- allfound
1765
- end
1766
-
1767
- if shortname == "DNSZone"
1768
- def self.genericMuDNSEntry(*flags)
1769
- # XXX have this switch on a global config for where Mu puts its DNS
1770
- cloudclass = MU::Cloud.loadCloudType(MU::Config.defaultCloud, "DNSZone")
1771
- cloudclass.genericMuDNSEntry(flags.first)
1772
- end
1773
- def self.createRecordsFromConfig(*flags)
1774
- cloudclass = MU::Cloud.loadCloudType(MU::Config.defaultCloud, "DNSZone")
1775
- if !flags.nil? and flags.size == 1
1776
- cloudclass.createRecordsFromConfig(flags.first)
1777
- else
1778
- cloudclass.createRecordsFromConfig(*flags)
1779
- end
1780
- end
1781
- end
1782
-
1783
- if shortname == "Server" or shortname == "ServerPool"
1784
- def windows?
1785
- return true if %w{win2k16 win2k12r2 win2k12 win2k8 win2k8r2 win2k19 windows}.include?(@config['platform'])
1786
- begin
1787
- return true if cloud_desc.respond_to?(:platform) and cloud_desc.platform == "Windows"
1788
- # 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?
1789
- rescue MU::MuError
1790
- return false
1791
- end
1792
- false
1793
- end
1794
-
1795
- # Gracefully message and attempt to accommodate the common transient errors peculiar to Windows nodes
1796
- # @param e [Exception]: The exception that we're handling
1797
- # @param retries [Integer]: The current number of retries, which we'll increment and pass back to the caller
1798
- # @param rebootable_fails [Integer]: The current number of reboot-worthy failures, which we'll increment and pass back to the caller
1799
- # @param max_retries [Integer]: Maximum number of retries to attempt; we'll raise an exception if this is exceeded
1800
- # @param reboot_on_problems [Boolean]: Whether we should try to reboot a "stuck" machine
1801
- # @param retry_interval [Integer]: How many seconds to wait before returning for another attempt
1802
- def handleWindowsFail(e, retries, rebootable_fails, max_retries: 30, reboot_on_problems: false, retry_interval: 45)
1803
- msg = "WinRM connection to https://"+@mu_name+":5986/wsman: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})"
1804
- if e.class.name == "WinRM::WinRMAuthorizationError" or e.message.match(/execution expired/) and reboot_on_problems
1805
- if rebootable_fails > 0 and (rebootable_fails % 7) == 0
1806
- MU.log "#{@mu_name} still misbehaving, forcing Stop and Start from API", MU::WARN
1807
- reboot(true) # vicious API stop/start
1808
- sleep retry_interval*3
1809
- rebootable_fails = 0
1810
- else
1811
- if rebootable_fails == 5
1812
- MU.log "#{@mu_name} misbehaving, attempting to reboot from API", MU::WARN
1813
- reboot # graceful API restart
1814
- sleep retry_interval*2
1815
- end
1816
- rebootable_fails = rebootable_fails + 1
1817
- end
1818
- end
1819
- if retries < max_retries
1820
- if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0 and retries != 0)
1821
- MU.log msg, MU::NOTICE
1822
- elsif retries/max_retries > 0.5
1823
- MU.log msg, MU::WARN, details: e.inspect
1824
- end
1825
- sleep retry_interval
1826
- retries = retries + 1
1827
- else
1828
- raise MuError, "#{@mu_name}: #{e.inspect} trying to connect with WinRM, max_retries exceeded", e.backtrace
1829
- end
1830
- return [retries, rebootable_fails]
1831
- end
1832
-
1833
- def windowsRebootPending?(shell = nil)
1834
- if shell.nil?
1835
- shell = getWinRMSession(1, 30)
1836
- end
1837
- # if (Get-Item "HKLM:/SOFTWARE/Microsoft/Windows/CurrentVersion/WindowsUpdate/Auto Update/RebootRequired" -EA Ignore) { exit 1 }
1838
- cmd = %Q{
1839
- if (Get-ChildItem "HKLM:/Software/Microsoft/Windows/CurrentVersion/Component Based Servicing/RebootPending" -EA Ignore) {
1840
- echo "Component Based Servicing/RebootPending is true"
1841
- exit 1
1842
- }
1843
- if (Get-ItemProperty "HKLM:/SYSTEM/CurrentControlSet/Control/Session Manager" -Name PendingFileRenameOperations -EA Ignore) {
1844
- echo "Control/Session Manager/PendingFileRenameOperations is true"
1845
- exit 1
1846
- }
1847
- try {
1848
- $util = [wmiclass]"\\\\.\\root\\ccm\\clientsdk:CCM_ClientUtilities"
1849
- $status = $util.DetermineIfRebootPending()
1850
- if(($status -ne $null) -and $status.RebootPending){
1851
- echo "WMI says RebootPending is true"
1852
- exit 1
1853
- }
1854
- } catch {
1855
- exit 0
1856
- }
1857
- exit 0
1858
- }
1859
- resp = shell.run(cmd)
1860
- returnval = resp.exitcode == 0 ? false : true
1861
- shell.close
1862
- returnval
1863
- end
1864
-
1865
- # Basic setup tasks performed on a new node during its first WinRM
1866
- # connection. Most of this is terrible Windows glue.
1867
- # @param shell [WinRM::Shells::Powershell]: An active Powershell session to the new node.
1868
- def initialWinRMTasks(shell)
1869
- retries = 0
1870
- rebootable_fails = 0
1871
- begin
1872
- if !@config['use_cloud_provider_windows_password']
1873
- pw = @groomer.getSecret(
1874
- vault: @config['mu_name'],
1875
- item: "windows_credentials",
1876
- field: "password"
1877
- )
1878
- 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}
1879
- resp = shell.run(win_check_for_pw)
1880
- if resp.stdout.chomp != "True"
1881
- win_set_pw = %Q{(([adsi]('WinNT://./#{@config["windows_admin_username"]}, user')).psbase.invoke('SetPassword', '#{pw}'))}
1882
- resp = shell.run(win_set_pw)
1883
- puts resp.stdout
1884
- MU.log "Resetting Windows host password", MU::NOTICE, details: resp.stdout
1885
- end
1886
- end
1887
-
1888
- # Install Cygwin here, because for some reason it breaks inside Chef
1889
- # XXX would love to not do this here
1890
- pkgs = ["bash", "mintty", "vim", "curl", "openssl", "wget", "lynx", "openssh"]
1891
- admin_home = "c:/bin/cygwin/home/#{@config["windows_admin_username"]}"
1892
- install_cygwin = %Q{
1893
- If (!(Test-Path "c:/bin/cygwin/Cygwin.bat")){
1894
- $WebClient = New-Object System.Net.WebClient
1895
- $WebClient.DownloadFile("http://cygwin.com/setup-x86_64.exe","$env:Temp/setup-x86_64.exe")
1896
- 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(',')}"
1897
- }
1898
- if(!(Test-Path #{admin_home})){
1899
- New-Item -type directory -path #{admin_home}
1900
- }
1901
- if(!(Test-Path #{admin_home}/.ssh)){
1902
- New-Item -type directory -path #{admin_home}/.ssh
1903
- }
1904
- if(!(Test-Path #{admin_home}/.ssh/authorized_keys)){
1905
- New-Item #{admin_home}/.ssh/authorized_keys -type file -force -value "#{@deploy.ssh_public_key}"
1906
- }
1907
- }
1908
- resp = shell.run(install_cygwin)
1909
- if resp.exitcode != 0
1910
- MU.log "Failed at installing Cygwin", MU::ERR, details: resp
1911
- end
1912
-
1913
- hostname = nil
1914
- if !@config['active_directory'].nil?
1915
- if @config['active_directory']['node_type'] == "domain_controller" && @config['active_directory']['domain_controller_hostname']
1916
- hostname = @config['active_directory']['domain_controller_hostname']
1917
- @mu_windows_name = hostname
1918
- else
1919
- # Do we have an AD specific hostname?
1920
- hostname = @mu_windows_name
1921
- end
1922
- else
1923
- hostname = @mu_windows_name
1924
- end
1925
- resp = shell.run(%Q{hostname})
1926
-
1927
- if resp.stdout.chomp != hostname
1928
- resp = shell.run(%Q{Rename-Computer -NewName '#{hostname}' -Force -PassThru -Restart; Restart-Computer -Force})
1929
- MU.log "Renaming Windows host to #{hostname}; this will trigger a reboot", MU::NOTICE, details: resp.stdout
1930
- reboot(true)
1931
- sleep 30
1932
- end
1933
- rescue WinRM::WinRMError, HTTPClient::ConnectTimeoutError => e
1934
- retries, rebootable_fails = handleWindowsFail(e, retries, rebootable_fails, max_retries: 10, reboot_on_problems: true, retry_interval: 30)
1935
- retry
1936
- end
1937
- end
1938
-
1939
-
1940
- # Basic setup tasks performed on a new node during its first initial
1941
- # ssh connection. Most of this is terrible Windows glue.
1942
- # @param ssh [Net::SSH::Connection::Session]: The active SSH session to the new node.
1943
- def initialSSHTasks(ssh)
1944
- 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" )}
1945
- win_installer_check = %q{ls /proc/registry/HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Installer/}
1946
- lnx_installer_check = %q{ps auxww | awk '{print $11}' | egrep '(/usr/bin/yum|apt-get|dpkg)'}
1947
- lnx_updates_check = %q{( test -f /.mu-installer-ran-updates || ! test -d /var/lib/cloud/instance ) || echo "userdata still running"}
1948
- win_set_pw = nil
1949
-
1950
- if windows? and !@config['use_cloud_provider_windows_password']
1951
- # 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
1952
- pw = @groomer.getSecret(
1953
- vault: @config['mu_name'],
1954
- item: "windows_credentials",
1955
- field: "password"
1956
- )
1957
- 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}'}
1958
- win_set_pw = %Q{powershell -Command "& {(([adsi]('WinNT://./#{@config["windows_admin_username"]}, user')).psbase.invoke('SetPassword', '#{pw}'))}"}
1959
- end
1960
-
1961
- # There shouldn't be a use case where a domain joined computer goes through initialSSHTasks. Removing Active Directory specific computer rename.
1962
- set_hostname = true
1963
- hostname = nil
1964
- if !@config['active_directory'].nil?
1965
- if @config['active_directory']['node_type'] == "domain_controller" && @config['active_directory']['domain_controller_hostname']
1966
- hostname = @config['active_directory']['domain_controller_hostname']
1967
- @mu_windows_name = hostname
1968
- set_hostname = true
1969
- else
1970
- # Do we have an AD specific hostname?
1971
- hostname = @mu_windows_name
1972
- set_hostname = true
1973
- end
1974
- else
1975
- hostname = @mu_windows_name
1976
- end
1977
- win_check_for_hostname = %Q{powershell -Command '& {hostname}'}
1978
- win_set_hostname = %Q{powershell -Command "& {Rename-Computer -NewName '#{hostname}' -Force -PassThru -Restart; Restart-Computer -Force }"}
1979
-
1980
- begin
1981
- # Set our admin password first, if we need to
1982
- if windows? and !win_set_pw.nil? and !win_check_for_pw.nil?
1983
- output = ssh.exec!(win_check_for_pw)
1984
- raise MU::Cloud::BootstrapTempFail, "Got nil output from ssh session, waiting and retrying" if output.nil?
1985
- if !output.match(/True/)
1986
- MU.log "Setting Windows password for user #{@config['windows_admin_username']}", details: ssh.exec!(win_set_pw)
1987
- end
1988
- end
1989
- if windows?
1990
- output = ssh.exec!(win_env_fix)
1991
- output += ssh.exec!(win_installer_check)
1992
- raise MU::Cloud::BootstrapTempFail, "Got nil output from ssh session, waiting and retrying" if output.nil?
1993
- if output.match(/InProgress/)
1994
- raise MU::Cloud::BootstrapTempFail, "Windows Installer service is still doing something, need to wait"
1995
- end
1996
- if set_hostname and !@hostname_set and @mu_windows_name
1997
- output = ssh.exec!(win_check_for_hostname)
1998
- raise MU::Cloud::BootstrapTempFail, "Got nil output from ssh session, waiting and retrying" if output.nil?
1999
- if !output.match(/#{@mu_windows_name}/)
2000
- MU.log "Setting Windows hostname to #{@mu_windows_name}", details: ssh.exec!(win_set_hostname)
2001
- @hostname_set = true
2002
- # Reboot from the API too, in case Windows is flailing
2003
- if !@cloudobj.nil?
2004
- @cloudobj.reboot
2005
- else
2006
- reboot
2007
- end
2008
- raise MU::Cloud::BootstrapTempFail, "Set hostname in Windows, waiting for reboot"
2009
- end
2010
- end
2011
- else
2012
- output = ssh.exec!(lnx_installer_check)
2013
- if !output.nil? and !output.empty?
2014
- raise MU::Cloud::BootstrapTempFail, "Linux package manager is still doing something, need to wait (#{output})"
2015
- end
2016
- if !@config['skipinitialupdates'] and
2017
- !@config['scrub_mu_isms'] and
2018
- !@config['userdata_script']
2019
- output = ssh.exec!(lnx_updates_check)
2020
- if !output.nil? and output.match(/userdata still running/)
2021
- raise MU::Cloud::BootstrapTempFail, "Waiting for initial userdata system updates to complete"
2022
- end
2023
- end
2024
- end
2025
- rescue RuntimeError => e
2026
- raise MU::Cloud::BootstrapTempFail, "Got #{e.inspect} performing initial SSH connect tasks, will try again"
2027
- end
2028
-
2029
- end
2030
-
2031
- # Get a privileged Powershell session on the server in question, using SSL-encrypted WinRM with certificate authentication.
2032
- # @param max_retries [Integer]:
2033
- # @param retry_interval [Integer]:
2034
- # @param timeout [Integer]:
2035
- # @param winrm_retries [Integer]:
2036
- # @param reboot_on_problems [Boolean]:
2037
- def getWinRMSession(max_retries = 40, retry_interval = 60, timeout: 30, winrm_retries: 2, reboot_on_problems: false)
2038
- _nat_ssh_key, _nat_ssh_user, _nat_ssh_host, canonical_ip, _ssh_user, _ssh_key_name = getSSHConfig
2039
- @mu_name ||= @config['mu_name']
2040
-
2041
- shell = nil
2042
- opts = nil
2043
- # and now, a thing I really don't want to do
2044
- MU::Master.addInstanceToEtcHosts(canonical_ip, @mu_name)
2045
-
2046
- # catch exceptions that circumvent our regular call stack
2047
- Thread.abort_on_exception = false
2048
- Thread.handle_interrupt(WinRM::WinRMWSManFault => :never) {
2049
- begin
2050
- Thread.handle_interrupt(WinRM::WinRMWSManFault => :immediate) {
2051
- MU.log "(Probably harmless) Caught a WinRM::WinRMWSManFault in #{Thread.current.inspect}", MU::DEBUG, details: Thread.current.backtrace
2052
- }
2053
- ensure
2054
- # Reraise something useful
2055
- end
2056
- }
2057
-
2058
- retries = 0
2059
- rebootable_fails = 0
2060
- begin
2061
- loglevel = retries > 4 ? MU::NOTICE : MU::DEBUG
2062
- MU.log "Calling WinRM on #{@mu_name}", loglevel, details: opts
2063
- opts = {
2064
- retry_limit: winrm_retries,
2065
- 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
2066
- ca_trust_path: "#{MU.mySSLDir}/Mu_CA.pem",
2067
- transport: :ssl,
2068
- operation_timeout: timeout,
2069
- }
2070
- if retries % 2 == 0 # NTLM password over https
2071
- opts[:endpoint] = 'https://'+canonical_ip+':5986/wsman'
2072
- opts[:user] = @config['windows_admin_username']
2073
- opts[:password] = getWindowsAdminPassword
2074
- else # certificate auth over https
2075
- opts[:endpoint] = 'https://'+@mu_name+':5986/wsman'
2076
- opts[:client_cert] = "#{MU.mySSLDir}/#{@mu_name}-winrm.crt"
2077
- opts[:client_key] = "#{MU.mySSLDir}/#{@mu_name}-winrm.key"
2078
- end
2079
- conn = WinRM::Connection.new(opts)
2080
- conn.logger.level = :debug if retries > 2
2081
- MU.log "WinRM connection to #{@mu_name} created", MU::DEBUG, details: conn
2082
- shell = conn.shell(:powershell)
2083
- shell.run('ipconfig') # verify that we can do something
2084
- rescue Errno::EHOSTUNREACH, Errno::ECONNREFUSED, HTTPClient::ConnectTimeoutError, OpenSSL::SSL::SSLError, SocketError, WinRM::WinRMError, Timeout::Error => e
2085
- retries, rebootable_fails = handleWindowsFail(e, retries, rebootable_fails, max_retries: max_retries, reboot_on_problems: reboot_on_problems, retry_interval: retry_interval)
2086
- retry
2087
- ensure
2088
- MU::Master.removeInstanceFromEtcHosts(@mu_name)
2089
- end
2090
-
2091
- shell
2092
- end
2093
-
2094
- # @param max_retries [Integer]: Number of connection attempts to make before giving up
2095
- # @param retry_interval [Integer]: Number of seconds to wait between connection attempts
2096
- # @return [Net::SSH::Connection::Session]
2097
- def getSSHSession(max_retries = 12, retry_interval = 30)
2098
- ssh_keydir = Etc.getpwnam(@deploy.mu_user).dir+"/.ssh"
2099
- nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, _ssh_key_name = getSSHConfig
2100
- session = nil
2101
- retries = 0
2102
-
2103
- vpc_class = Object.const_get("MU").const_get("Cloud").const_get(@cloud).const_get("VPC")
2104
-
2105
- # XXX WHY is this a thing
2106
- Thread.handle_interrupt(Errno::ECONNREFUSED => :never) {
2107
- }
2108
-
2109
- begin
2110
- MU::Cloud.handleNetSSHExceptions
2111
- if !nat_ssh_host.nil?
2112
- proxy_cmd = "ssh -q -o StrictHostKeyChecking=no -W %h:%p #{nat_ssh_user}@#{nat_ssh_host}"
2113
- 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
2114
- proxy = Net::SSH::Proxy::Command.new(proxy_cmd)
2115
- session = Net::SSH.start(
2116
- canonical_ip,
2117
- ssh_user,
2118
- :config => false,
2119
- :keys_only => true,
2120
- :keys => [ssh_keydir+"/"+nat_ssh_key, ssh_keydir+"/"+@deploy.ssh_key_name],
2121
- :verify_host_key => false,
2122
- # :verbose => :info,
2123
- :host_key => "ssh-rsa",
2124
- :port => 22,
2125
- :auth_methods => ['publickey'],
2126
- :proxy => proxy
2127
- )
2128
- else
2129
-
2130
- MU.log "Attempting SSH to #{canonical_ip} (#{@mu_name}) as #{ssh_user} with key #{ssh_keydir}/#{@deploy.ssh_key_name}" if retries == 0
2131
- session = Net::SSH.start(
2132
- canonical_ip,
2133
- ssh_user,
2134
- :config => false,
2135
- :keys_only => true,
2136
- :keys => [ssh_keydir+"/"+@deploy.ssh_key_name],
2137
- :verify_host_key => false,
2138
- # :verbose => :info,
2139
- :host_key => "ssh-rsa",
2140
- :port => 22,
2141
- :auth_methods => ['publickey']
2142
- )
2143
- end
2144
- retries = 0
2145
- rescue Net::SSH::HostKeyMismatch => e
2146
- MU.log("Remembering new key: #{e.fingerprint}")
2147
- e.remember_host!
2148
- session.close
2149
- retry
2150
- # 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
2151
- 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
2152
- begin
2153
- session.close if !session.nil?
2154
- rescue Net::SSH::Disconnect, IOError => e
2155
- if windows?
2156
- MU.log "Windows has probably closed the ssh session before we could. Waiting before trying again", MU::NOTICE
2157
- else
2158
- MU.log "ssh session was closed unexpectedly, waiting before trying again", MU::NOTICE
2159
- end
2160
- sleep 10
2161
- end
2162
-
2163
- if retries < max_retries
2164
- retries = retries + 1
2165
- msg = "ssh #{ssh_user}@#{@mu_name}: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})"
2166
- if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0)
2167
- MU.log msg, MU::NOTICE
2168
- if !vpc_class.haveRouteToInstance?(cloud_desc, credentials: @credentials) and
2169
- canonical_ip.match(/(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1$)|(^[fF][cCdD])/) and
2170
- !nat_ssh_host
2171
- 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
2172
- end
2173
- elsif retries/max_retries > 0.5
2174
- MU.log msg, MU::WARN, details: e.inspect
2175
- end
2176
- sleep retry_interval
2177
- retry
2178
- else
2179
- raise MuError, "#{@mu_name}: #{e.inspect} trying to connect with SSH, max_retries exceeded", e.backtrace
2180
- end
2181
- end
2182
- return session
2183
- end
2184
- end
2185
-
2186
- # Wrapper for the cleanup class method of underlying cloud object implementations.
2187
- def self.cleanup(*flags)
2188
- ok = true
2189
- params = flags.first
2190
- clouds = MU::Cloud.supportedClouds
2191
- if params[:cloud]
2192
- clouds = [params[:cloud]]
2193
- params.delete(:cloud)
2194
- end
2195
-
2196
- clouds.each { |cloud|
2197
- begin
2198
- cloudclass = MU::Cloud.loadCloudType(cloud, shortname)
2199
-
2200
- if cloudclass.isGlobal?
2201
- params.delete(:region)
2202
- end
2203
-
2204
- raise MuCloudResourceNotImplemented if !cloudclass.respond_to?(:cleanup) or cloudclass.method(:cleanup).owner.to_s != "#<Class:#{cloudclass}>"
2205
- MU.log "Invoking #{cloudclass}.cleanup from #{shortname}", MU::DEBUG, details: flags
2206
- cloudclass.cleanup(params)
2207
- rescue MuCloudResourceNotImplemented
2208
- MU.log "No #{cloud} implementation of #{shortname}.cleanup, skipping", MU::DEBUG, details: flags
2209
- rescue StandardError => e
2210
- in_msg = cloud
2211
- if params and params[:region]
2212
- in_msg += " "+params[:region]
2213
- end
2214
- if params and params[:flags] and params[:flags]["project"] and !params[:flags]["project"].empty?
2215
- in_msg += " project "+params[:flags]["project"]
2216
- end
2217
- MU.log "Skipping #{shortname} cleanup method in #{in_msg} due to #{e.class.name}: #{e.message}", MU::WARN, details: e.backtrace
2218
- ok = false
2219
- end
2220
- }
2221
- MU::MommaCat.unlockAll
2222
-
2223
- ok
2224
- end
2225
-
2226
- # A hook that is always called just before each instance method is
2227
- # invoked, so that we can ensure that repetitive setup tasks (like
2228
- # resolving +:resource_group+ for Azure resources) have always been
2229
- # done.
2230
- def resourceInitHook
2231
- @cloud ||= cloud
2232
- if @cloudparentclass.respond_to?(:resourceInitHook)
2233
- @cloudparentclass.resourceInitHook(@cloudobj, @deploy)
2234
- end
2235
- end
2236
-
2237
- # Wrap the instance methods that this cloud resource type has to
2238
- # implement.
2239
- MU::Cloud.resource_types[name.to_sym][:instance].each { |method|
2240
- define_method method do |*args|
2241
- return nil if @cloudobj.nil?
2242
- MU.log "Invoking #{@cloudobj}.#{method}", MU::DEBUG
2243
-
2244
- # Go ahead and guarantee that we can't accidentally trigger these
2245
- # methods recursively.
2246
- @method_semaphore.synchronize {
2247
- # We're looking for recursion, not contention, so ignore some
2248
- # obviously harmless things.
2249
- if @method_locks.has_key?(method) and method != :findBastion and method != :cloud_id
2250
- MU.log "Double-call to cloud method #{method} for #{self}", MU::DEBUG, details: caller + ["competing call stack:"] + @method_locks[method]
2251
- end
2252
- @method_locks[method] = caller
2253
- }
2254
-
2255
- # Make sure the describe() caches are fresh
2256
- @cloudobj.describe if method != :describe
2257
-
2258
- # Don't run through dependencies on simple attr_reader lookups
2259
- if ![:dependencies, :cloud_id, :config, :mu_name].include?(method)
2260
- @cloudobj.dependencies
2261
- end
2262
-
2263
- retval = nil
2264
- if !args.nil? and args.size == 1
2265
- retval = @cloudobj.method(method).call(args.first)
2266
- elsif !args.nil? and args.size > 0
2267
- retval = @cloudobj.method(method).call(*args)
2268
- else
2269
- retval = @cloudobj.method(method).call
2270
- end
2271
- if (method == :create or method == :groom or method == :postBoot) and
2272
- (!@destroyed and !@cloudobj.destroyed)
2273
- deploydata = @cloudobj.method(:notify).call
2274
- @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?
2275
- if deploydata.nil? or !deploydata.is_a?(Hash)
2276
- 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
2277
- deploydata = MU.structToHash(@cloudobj.cloud_desc)
2278
- raise MuError, "Failed to collect metadata about #{self}" if deploydata.nil?
2279
- end
2280
- deploydata['cloud_id'] ||= @cloudobj.cloud_id if !@cloudobj.cloud_id.nil?
2281
- deploydata['mu_name'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
2282
- deploydata['nodename'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
2283
- deploydata.delete("#MUOBJECT")
2284
- @deploy.notify(self.class.cfg_plural, @config['name'], deploydata, triggering_node: @cloudobj, delayed_save: @delayed_save) if !@deploy.nil?
2285
- elsif method == :notify
2286
- retval['cloud_id'] = @cloudobj.cloud_id.to_s if !@cloudobj.cloud_id.nil?
2287
- retval['mu_name'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
2288
- @deploy.notify(self.class.cfg_plural, @config['name'], retval, triggering_node: @cloudobj, delayed_save: @delayed_save) if !@deploy.nil?
2289
- end
2290
- @method_semaphore.synchronize {
2291
- @method_locks.delete(method)
2292
- }
2293
-
2294
- @deploydata = @cloudobj.deploydata
2295
- @config = @cloudobj.config
2296
- retval
2297
- end
2298
- } # end instance method list
2299
- } # end dynamic class generation block
2300
- } # end resource type iteration
652
+ require 'mu/cloud/machine_images'
653
+ require 'mu/cloud/resource_base'
654
+ require 'mu/cloud/providers'
2301
655
 
2302
656
  end
2303
657