cloud-mu 3.1.6 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mu-adopt +4 -12
  3. data/bin/mu-azure-tests +57 -0
  4. data/bin/mu-cleanup +2 -4
  5. data/bin/mu-configure +37 -1
  6. data/bin/mu-deploy +3 -3
  7. data/bin/mu-findstray-tests +25 -0
  8. data/bin/mu-gen-docs +2 -4
  9. data/bin/mu-run-tests +23 -10
  10. data/cloud-mu.gemspec +2 -2
  11. data/cookbooks/mu-tools/libraries/helper.rb +1 -1
  12. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  13. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  14. data/extras/generate-stock-images +1 -0
  15. data/modules/mu.rb +82 -95
  16. data/modules/mu/adoption.rb +356 -56
  17. data/modules/mu/cleanup.rb +21 -20
  18. data/modules/mu/cloud.rb +79 -1753
  19. data/modules/mu/cloud/database.rb +49 -0
  20. data/modules/mu/cloud/dnszone.rb +46 -0
  21. data/modules/mu/cloud/machine_images.rb +212 -0
  22. data/modules/mu/cloud/providers.rb +81 -0
  23. data/modules/mu/cloud/resource_base.rb +920 -0
  24. data/modules/mu/cloud/server.rb +40 -0
  25. data/modules/mu/cloud/server_pool.rb +1 -0
  26. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  27. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  28. data/modules/mu/cloud/wrappers.rb +165 -0
  29. data/modules/mu/config.rb +122 -80
  30. data/modules/mu/config/alarm.rb +2 -6
  31. data/modules/mu/config/bucket.rb +1 -1
  32. data/modules/mu/config/cache_cluster.rb +1 -1
  33. data/modules/mu/config/collection.rb +1 -1
  34. data/modules/mu/config/container_cluster.rb +2 -2
  35. data/modules/mu/config/database.rb +83 -104
  36. data/modules/mu/config/database.yml +1 -2
  37. data/modules/mu/config/dnszone.rb +1 -1
  38. data/modules/mu/config/doc_helpers.rb +4 -5
  39. data/modules/mu/config/endpoint.rb +1 -1
  40. data/modules/mu/config/firewall_rule.rb +3 -19
  41. data/modules/mu/config/folder.rb +1 -1
  42. data/modules/mu/config/function.rb +1 -1
  43. data/modules/mu/config/group.rb +1 -1
  44. data/modules/mu/config/habitat.rb +1 -1
  45. data/modules/mu/config/loadbalancer.rb +57 -11
  46. data/modules/mu/config/log.rb +1 -1
  47. data/modules/mu/config/msg_queue.rb +1 -1
  48. data/modules/mu/config/nosqldb.rb +1 -1
  49. data/modules/mu/config/notifier.rb +1 -1
  50. data/modules/mu/config/ref.rb +30 -4
  51. data/modules/mu/config/role.rb +1 -1
  52. data/modules/mu/config/schema_helpers.rb +30 -34
  53. data/modules/mu/config/search_domain.rb +1 -1
  54. data/modules/mu/config/server.rb +4 -12
  55. data/modules/mu/config/server_pool.rb +3 -7
  56. data/modules/mu/config/storage_pool.rb +1 -1
  57. data/modules/mu/config/tail.rb +10 -0
  58. data/modules/mu/config/user.rb +1 -1
  59. data/modules/mu/config/vpc.rb +12 -17
  60. data/modules/mu/defaults/AWS.yaml +32 -32
  61. data/modules/mu/defaults/Azure.yaml +1 -0
  62. data/modules/mu/defaults/Google.yaml +1 -0
  63. data/modules/mu/deploy.rb +16 -15
  64. data/modules/mu/groomer.rb +15 -0
  65. data/modules/mu/groomers/chef.rb +3 -0
  66. data/modules/mu/logger.rb +120 -144
  67. data/modules/mu/master.rb +1 -1
  68. data/modules/mu/mommacat.rb +54 -25
  69. data/modules/mu/mommacat/daemon.rb +10 -7
  70. data/modules/mu/mommacat/naming.rb +82 -3
  71. data/modules/mu/mommacat/search.rb +47 -15
  72. data/modules/mu/mommacat/storage.rb +72 -41
  73. data/modules/mu/{clouds → providers}/README.md +1 -1
  74. data/modules/mu/{clouds → providers}/aws.rb +114 -47
  75. data/modules/mu/{clouds → providers}/aws/alarm.rb +1 -1
  76. data/modules/mu/{clouds → providers}/aws/bucket.rb +2 -2
  77. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +10 -46
  78. data/modules/mu/{clouds → providers}/aws/collection.rb +3 -3
  79. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +15 -33
  80. data/modules/mu/providers/aws/database.rb +1744 -0
  81. data/modules/mu/{clouds → providers}/aws/dnszone.rb +2 -5
  82. data/modules/mu/{clouds → providers}/aws/endpoint.rb +2 -11
  83. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +33 -29
  84. data/modules/mu/{clouds → providers}/aws/folder.rb +0 -0
  85. data/modules/mu/{clouds → providers}/aws/function.rb +2 -10
  86. data/modules/mu/{clouds → providers}/aws/group.rb +9 -13
  87. data/modules/mu/{clouds → providers}/aws/habitat.rb +1 -1
  88. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +41 -33
  89. data/modules/mu/{clouds → providers}/aws/log.rb +2 -2
  90. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +2 -8
  91. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +0 -0
  92. data/modules/mu/{clouds → providers}/aws/notifier.rb +0 -0
  93. data/modules/mu/{clouds → providers}/aws/role.rb +7 -7
  94. data/modules/mu/{clouds → providers}/aws/search_domain.rb +8 -13
  95. data/modules/mu/{clouds → providers}/aws/server.rb +55 -90
  96. data/modules/mu/{clouds → providers}/aws/server_pool.rb +10 -33
  97. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +19 -36
  98. data/modules/mu/{clouds → providers}/aws/user.rb +8 -12
  99. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  100. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +0 -0
  101. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +0 -0
  102. data/modules/mu/{clouds → providers}/aws/vpc.rb +135 -70
  103. data/modules/mu/{clouds → providers}/aws/vpc_subnet.rb +0 -0
  104. data/modules/mu/{clouds → providers}/azure.rb +4 -1
  105. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
  106. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
  107. data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
  108. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
  109. data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
  110. data/modules/mu/{clouds → providers}/azure/server.rb +30 -23
  111. data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
  112. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  113. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  114. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  115. data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
  116. data/modules/mu/{clouds → providers}/cloudformation.rb +1 -1
  117. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  118. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  119. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  120. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  121. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  122. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  123. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  124. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  125. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  126. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  127. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
  128. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  129. data/modules/mu/{clouds → providers}/google.rb +14 -6
  130. data/modules/mu/{clouds → providers}/google/bucket.rb +1 -1
  131. data/modules/mu/{clouds → providers}/google/container_cluster.rb +28 -13
  132. data/modules/mu/{clouds → providers}/google/database.rb +1 -8
  133. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +2 -2
  134. data/modules/mu/{clouds → providers}/google/folder.rb +4 -8
  135. data/modules/mu/{clouds → providers}/google/function.rb +3 -3
  136. data/modules/mu/{clouds → providers}/google/group.rb +8 -16
  137. data/modules/mu/{clouds → providers}/google/habitat.rb +3 -7
  138. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +1 -1
  139. data/modules/mu/{clouds → providers}/google/role.rb +42 -34
  140. data/modules/mu/{clouds → providers}/google/server.rb +25 -10
  141. data/modules/mu/{clouds → providers}/google/server_pool.rb +10 -10
  142. data/modules/mu/{clouds → providers}/google/user.rb +31 -21
  143. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  144. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  145. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  146. data/modules/mu/{clouds → providers}/google/vpc.rb +37 -2
  147. data/modules/tests/centos6.yaml +11 -0
  148. data/modules/tests/centos7.yaml +11 -0
  149. data/modules/tests/centos8.yaml +12 -0
  150. data/modules/tests/rds.yaml +108 -0
  151. data/modules/tests/regrooms/rds.yaml +123 -0
  152. data/spec/mu/clouds/azure_spec.rb +2 -2
  153. metadata +108 -89
  154. data/modules/mu/clouds/aws/database.rb +0 -1974
@@ -30,6 +30,8 @@ module MU
30
30
  @onlycloud = false
31
31
  @skipcloud = false
32
32
 
33
+ # Resource types, in the order in which we generally have to clean them up
34
+ # to disentangle them from one another.
33
35
  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"]
34
36
 
35
37
  # Purge all resources associated with a deployment.
@@ -120,7 +122,7 @@ module MU
120
122
  }
121
123
 
122
124
  creds.each_pair { |provider, credsets_inner|
123
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(provider)
125
+ cloudclass = MU::Cloud.cloudClass(provider)
124
126
  credsets_inner.keys.each { |c|
125
127
  cloudclass.cleanDeploy(MU.deploy_id, credentials: c, noop: @noop)
126
128
  }
@@ -158,7 +160,7 @@ module MU
158
160
  def self.listUsedCredentials(credsets)
159
161
  creds = {}
160
162
  MU::Cloud.availableClouds.each { |cloud|
161
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
163
+ cloudclass = MU::Cloud.cloudClass(cloud)
162
164
  if $MU_CFG[cloud.downcase] and $MU_CFG[cloud.downcase].size > 0
163
165
  creds[cloud] ||= {}
164
166
  cloudclass.listCredentials.each { |credset|
@@ -179,7 +181,7 @@ module MU
179
181
  private_class_method :listUsedCredentials
180
182
 
181
183
  def self.cleanCloud(cloud, habitats, regions, credsets)
182
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
184
+ cloudclass = MU::Cloud.cloudClass(cloud)
183
185
  credsets.each_pair { |credset, acct_regions|
184
186
  next if @credsused and !@credsused.include?(credset)
185
187
  global_vs_region_semaphore = Mutex.new
@@ -212,8 +214,8 @@ module MU
212
214
 
213
215
  def self.cleanRegion(cloud, credset, region, global_vs_region_semaphore, global_done, habitats)
214
216
  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")
217
+ cloudclass = MU::Cloud.cloudClass(cloud)
218
+ habitatclass = MU::Cloud.resourceClass(cloud, "Habitat")
217
219
 
218
220
  projects = []
219
221
  if habitats
@@ -226,7 +228,7 @@ module MU
226
228
  projects << $MU_CFG[cloud.downcase][credset]["project"]
227
229
  end
228
230
  begin
229
- projects.concat(cloudclass.listHabitats(credset))
231
+ projects.concat(cloudclass.listHabitats(credset, use_cache: false))
230
232
  rescue NoMethodError
231
233
  end
232
234
  end
@@ -280,8 +282,7 @@ module MU
280
282
  begin
281
283
  skipme = false
282
284
  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?
285
+ if MU::Cloud.resourceClass(cloud, t).isGlobal?
285
286
  global_done[habitat] ||= []
286
287
  if !global_done[habitat].include?(t)
287
288
  global_done[habitat] << t
@@ -295,7 +296,7 @@ module MU
295
296
  rescue MU::Cloud::MuDefunctHabitat, MU::Cloud::MuCloudResourceNotImplemented
296
297
  next
297
298
  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
299
+ MU.log "While checking mu/providers/#{cloud.downcase}/#{cloudclass.cfg_name} for global-ness in cleanup: "+e.message, MU::WARN
299
300
  next
300
301
  rescue ::Aws::EC2::Errors::AuthFailure, ::Google::Apis::ClientError => e
301
302
  MU.log e.message+" in "+region, MU::ERR
@@ -323,21 +324,21 @@ module MU
323
324
  def self.call_cleanup(type, credset, provider, flags, region)
324
325
  if @mommacat.nil? or @mommacat.numKittens(types: [type]) > 0
325
326
  if @mommacat
327
+
326
328
  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
329
+
330
+ if found
331
+ flags['known'] = if found.is_a?(Array)
332
+ found.map { |k| k.cloud_id }
333
+ elsif found.is_a?(Hash)
334
+ found.each_value.map { |k| k.cloud_id }
335
+ else
336
+ [found.cloud_id]
337
+ end
336
338
  end
337
339
  end
338
- resclass = Object.const_get("MU").const_get("Cloud").const_get(type)
339
340
 
340
- resclass.cleanup(
341
+ MU::Cloud.loadBaseType(type).cleanup(
341
342
  noop: @noop,
342
343
  ignoremaster: @ignoremaster,
343
344
  region: region,
@@ -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
  #
@@ -178,8 +173,8 @@ module MU
178
173
  :interface => self.const_get("Folder"),
179
174
  :deps_wait_on_my_creation => true,
180
175
  :waits_on_parent_completion => true,
181
- :class => generic_class_methods,
182
- :instance => generic_instance_methods
176
+ :class => @@generic_class_methods,
177
+ :instance => @@generic_instance_methods
183
178
  },
184
179
  :Habitat => {
185
180
  :has_multiples => false,
@@ -189,8 +184,8 @@ module MU
189
184
  :interface => self.const_get("Habitat"),
190
185
  :deps_wait_on_my_creation => true,
191
186
  :waits_on_parent_completion => true,
192
- :class => generic_class_methods + [:isLive?],
193
- :instance => generic_instance_methods + [:groom]
187
+ :class => @@generic_class_methods + [:isLive?],
188
+ :instance => @@generic_instance_methods + [:groom]
194
189
  },
195
190
  :Collection => {
196
191
  :has_multiples => false,
@@ -200,8 +195,8 @@ module MU
200
195
  :interface => self.const_get("Collection"),
201
196
  :deps_wait_on_my_creation => true,
202
197
  :waits_on_parent_completion => false,
203
- :class => generic_class_methods,
204
- :instance => generic_instance_methods
198
+ :class => @@generic_class_methods,
199
+ :instance => @@generic_instance_methods
205
200
  },
206
201
  :Database => {
207
202
  :has_multiples => true,
@@ -211,8 +206,8 @@ module MU
211
206
  :interface => self.const_get("Database"),
212
207
  :deps_wait_on_my_creation => true,
213
208
  :waits_on_parent_completion => false,
214
- :class => generic_class_methods,
215
- :instance => generic_instance_methods + [:groom, :allowHost]
209
+ :class => @@generic_class_methods,
210
+ :instance => @@generic_instance_methods + [:groom, :allowHost]
216
211
  },
217
212
  :DNSZone => {
218
213
  :has_multiples => false,
@@ -222,8 +217,8 @@ module MU
222
217
  :interface => self.const_get("DNSZone"),
223
218
  :deps_wait_on_my_creation => true,
224
219
  :waits_on_parent_completion => true,
225
- :class => generic_class_methods + [:genericMuDNSEntry, :createRecordsFromConfig],
226
- :instance => generic_instance_methods
220
+ :class => @@generic_class_methods + [:genericMuDNSEntry, :createRecordsFromConfig],
221
+ :instance => @@generic_instance_methods
227
222
  },
228
223
  :FirewallRule => {
229
224
  :has_multiples => false,
@@ -233,8 +228,8 @@ module MU
233
228
  :interface => self.const_get("FirewallRule"),
234
229
  :deps_wait_on_my_creation => true,
235
230
  :waits_on_parent_completion => false,
236
- :class => generic_class_methods,
237
- :instance => generic_instance_methods + [:groom, :addRule]
231
+ :class => @@generic_class_methods,
232
+ :instance => @@generic_instance_methods + [:groom, :addRule]
238
233
  },
239
234
  :LoadBalancer => {
240
235
  :has_multiples => false,
@@ -244,8 +239,8 @@ module MU
244
239
  :interface => self.const_get("LoadBalancer"),
245
240
  :deps_wait_on_my_creation => true,
246
241
  :waits_on_parent_completion => false,
247
- :class => generic_class_methods,
248
- :instance => generic_instance_methods + [:groom, :registerNode]
242
+ :class => @@generic_class_methods,
243
+ :instance => @@generic_instance_methods + [:groom, :registerNode]
249
244
  },
250
245
  :Server => {
251
246
  :has_multiples => true,
@@ -255,8 +250,8 @@ module MU
255
250
  :interface => self.const_get("Server"),
256
251
  :deps_wait_on_my_creation => false,
257
252
  :waits_on_parent_completion => false,
258
- :class => generic_class_methods + [:validateInstanceType, :imageTimeStamp],
259
- :instance => generic_instance_methods + [:groom, :postBoot, :getSSHConfig, :canonicalIP, :getWindowsAdminPassword, :active?, :groomer, :mu_windows_name, :mu_windows_name=, :reboot, :addVolume, :genericNAT]
253
+ :class => @@generic_class_methods + [:validateInstanceType, :imageTimeStamp],
254
+ :instance => @@generic_instance_methods + [:groom, :postBoot, :getSSHConfig, :canonicalIP, :getWindowsAdminPassword, :active?, :groomer, :mu_windows_name, :mu_windows_name=, :reboot, :addVolume, :genericNAT, :listIPs]
260
255
  },
261
256
  :ServerPool => {
262
257
  :has_multiples => false,
@@ -266,8 +261,8 @@ module MU
266
261
  :interface => self.const_get("ServerPool"),
267
262
  :deps_wait_on_my_creation => false,
268
263
  :waits_on_parent_completion => true,
269
- :class => generic_class_methods,
270
- :instance => generic_instance_methods + [:groom, :listNodes]
264
+ :class => @@generic_class_methods,
265
+ :instance => @@generic_instance_methods + [:groom, :listNodes]
271
266
  },
272
267
  :VPC => {
273
268
  :has_multiples => false,
@@ -277,8 +272,8 @@ module MU
277
272
  :interface => self.const_get("VPC"),
278
273
  :deps_wait_on_my_creation => true,
279
274
  :waits_on_parent_completion => false,
280
- :class => generic_class_methods,
281
- :instance => generic_instance_methods + [:groom, :subnets, :getSubnet, :listSubnets, :findBastion, :findNat]
275
+ :class => @@generic_class_methods,
276
+ :instance => @@generic_instance_methods + [:groom, :subnets, :getSubnet, :findBastion, :findNat]
282
277
  },
283
278
  :CacheCluster => {
284
279
  :has_multiples => true,
@@ -288,8 +283,8 @@ module MU
288
283
  :interface => self.const_get("CacheCluster"),
289
284
  :deps_wait_on_my_creation => true,
290
285
  :waits_on_parent_completion => false,
291
- :class => generic_class_methods,
292
- :instance => generic_instance_methods + [:groom]
286
+ :class => @@generic_class_methods,
287
+ :instance => @@generic_instance_methods + [:groom]
293
288
  },
294
289
  :Alarm => {
295
290
  :has_multiples => false,
@@ -299,8 +294,8 @@ module MU
299
294
  :interface => self.const_get("Alarm"),
300
295
  :deps_wait_on_my_creation => false,
301
296
  :waits_on_parent_completion => true,
302
- :class => generic_class_methods,
303
- :instance => generic_instance_methods + [:groom]
297
+ :class => @@generic_class_methods,
298
+ :instance => @@generic_instance_methods + [:groom]
304
299
  },
305
300
  :Notifier => {
306
301
  :has_multiples => false,
@@ -310,8 +305,8 @@ module MU
310
305
  :interface => self.const_get("Notifier"),
311
306
  :deps_wait_on_my_creation => false,
312
307
  :waits_on_parent_completion => false,
313
- :class => generic_class_methods,
314
- :instance => generic_instance_methods + [:groom]
308
+ :class => @@generic_class_methods,
309
+ :instance => @@generic_instance_methods + [:groom]
315
310
  },
316
311
  :Log => {
317
312
  :has_multiples => false,
@@ -321,8 +316,8 @@ module MU
321
316
  :interface => self.const_get("Log"),
322
317
  :deps_wait_on_my_creation => true,
323
318
  :waits_on_parent_completion => true,
324
- :class => generic_class_methods,
325
- :instance => generic_instance_methods + [:groom]
319
+ :class => @@generic_class_methods,
320
+ :instance => @@generic_instance_methods + [:groom]
326
321
  },
327
322
  :StoragePool => {
328
323
  :has_multiples => false,
@@ -332,8 +327,8 @@ module MU
332
327
  :interface => self.const_get("StoragePool"),
333
328
  :deps_wait_on_my_creation => true,
334
329
  :waits_on_parent_completion => false,
335
- :class => generic_class_methods,
336
- :instance => generic_instance_methods + [:groom]
330
+ :class => @@generic_class_methods,
331
+ :instance => @@generic_instance_methods + [:groom]
337
332
  },
338
333
  :Function => {
339
334
  :has_multiples => false,
@@ -343,8 +338,8 @@ module MU
343
338
  :interface => self.const_get("Function"),
344
339
  :deps_wait_on_my_creation => true,
345
340
  :waits_on_parent_completion => false,
346
- :class => generic_class_methods,
347
- :instance => generic_instance_methods + [:groom]
341
+ :class => @@generic_class_methods,
342
+ :instance => @@generic_instance_methods + [:groom]
348
343
  },
349
344
  :Endpoint => {
350
345
  :has_multiples => false,
@@ -354,8 +349,8 @@ module MU
354
349
  :interface => self.const_get("Endpoint"),
355
350
  :deps_wait_on_my_creation => true,
356
351
  :waits_on_parent_completion => false,
357
- :class => generic_class_methods,
358
- :instance => generic_instance_methods + [:groom]
352
+ :class => @@generic_class_methods,
353
+ :instance => @@generic_instance_methods + [:groom]
359
354
  },
360
355
  :ContainerCluster => {
361
356
  :has_multiples => false,
@@ -365,8 +360,8 @@ module MU
365
360
  :interface => self.const_get("ContainerCluster"),
366
361
  :deps_wait_on_my_creation => true,
367
362
  :waits_on_parent_completion => false,
368
- :class => generic_class_methods,
369
- :instance => generic_instance_methods + [:groom]
363
+ :class => @@generic_class_methods,
364
+ :instance => @@generic_instance_methods + [:groom]
370
365
  },
371
366
  :SearchDomain => {
372
367
  :has_multiples => false,
@@ -376,8 +371,8 @@ module MU
376
371
  :interface => self.const_get("SearchDomain"),
377
372
  :deps_wait_on_my_creation => true,
378
373
  :waits_on_parent_completion => false,
379
- :class => generic_class_methods,
380
- :instance => generic_instance_methods + [:groom]
374
+ :class => @@generic_class_methods,
375
+ :instance => @@generic_instance_methods + [:groom]
381
376
  },
382
377
  :MsgQueue => {
383
378
  :has_multiples => false,
@@ -387,8 +382,8 @@ module MU
387
382
  :interface => self.const_get("MsgQueue"),
388
383
  :deps_wait_on_my_creation => true,
389
384
  :waits_on_parent_completion => true,
390
- :class => generic_class_methods,
391
- :instance => generic_instance_methods + [:groom]
385
+ :class => @@generic_class_methods,
386
+ :instance => @@generic_instance_methods + [:groom]
392
387
  },
393
388
  :User => {
394
389
  :has_multiples => false,
@@ -398,8 +393,8 @@ module MU
398
393
  :interface => self.const_get("User"),
399
394
  :deps_wait_on_my_creation => true,
400
395
  :waits_on_parent_completion => true,
401
- :class => generic_class_methods,
402
- :instance => generic_instance_methods + [:groom]
396
+ :class => @@generic_class_methods,
397
+ :instance => @@generic_instance_methods + [:groom]
403
398
  },
404
399
  :Group => {
405
400
  :has_multiples => false,
@@ -409,8 +404,8 @@ module MU
409
404
  :interface => self.const_get("Group"),
410
405
  :deps_wait_on_my_creation => true,
411
406
  :waits_on_parent_completion => true,
412
- :class => generic_class_methods,
413
- :instance => generic_instance_methods + [:groom]
407
+ :class => @@generic_class_methods,
408
+ :instance => @@generic_instance_methods + [:groom]
414
409
  },
415
410
  :Role => {
416
411
  :has_multiples => false,
@@ -420,8 +415,8 @@ module MU
420
415
  :interface => self.const_get("Role"),
421
416
  :deps_wait_on_my_creation => true,
422
417
  :waits_on_parent_completion => true,
423
- :class => generic_class_methods,
424
- :instance => generic_instance_methods + [:groom]
418
+ :class => @@generic_class_methods,
419
+ :instance => @@generic_instance_methods + [:groom]
425
420
  },
426
421
  :Bucket => {
427
422
  :has_multiples => false,
@@ -431,8 +426,8 @@ module MU
431
426
  :interface => self.const_get("Bucket"),
432
427
  :deps_wait_on_my_creation => true,
433
428
  :waits_on_parent_completion => true,
434
- :class => generic_class_methods + [:upload],
435
- :instance => generic_instance_methods + [:groom, :upload]
429
+ :class => @@generic_class_methods + [:upload],
430
+ :instance => @@generic_instance_methods + [:groom, :upload]
436
431
  },
437
432
  :NoSQLDB => {
438
433
  :has_multiples => false,
@@ -442,201 +437,11 @@ module MU
442
437
  :interface => self.const_get("NoSQLDB"),
443
438
  :deps_wait_on_my_creation => true,
444
439
  :waits_on_parent_completion => true,
445
- :class => generic_class_methods,
446
- :instance => generic_instance_methods + [:groom]
440
+ :class => @@generic_class_methods,
441
+ :instance => @@generic_instance_methods + [:groom]
447
442
  }
448
443
  }.freeze
449
444
 
450
- # The public AWS S3 bucket where we expect to find YAML files listing our
451
- # standard base images for various platforms.
452
- BASE_IMAGE_BUCKET = "cloudamatic"
453
- # The path in the AWS S3 bucket where we expect to find YAML files listing
454
- # our standard base images for various platforms.
455
- BASE_IMAGE_PATH = "/images"
456
-
457
- # Aliases for platform names, in case we don't have actual images built for
458
- # them.
459
- PLATFORM_ALIASES = {
460
- "linux" => "centos7",
461
- "windows" => "win2k12r2",
462
- "win2k12" => "win2k12r2",
463
- "ubuntu" => "ubuntu16",
464
- "centos" => "centos7",
465
- "rhel7" => "rhel71",
466
- "rhel" => "rhel71",
467
- "amazon" => "amazon2016"
468
- }
469
-
470
- @@image_fetch_cache = {}
471
- @@platform_cache = []
472
- @@image_fetch_semaphore = Mutex.new
473
-
474
- # Rifle our image lists from {MU::Cloud.getStockImage} and return a list
475
- # of valid +platform+ names.
476
- # @return [Array<String>]
477
- def self.listPlatforms
478
- return @@platform_cache if @@platform_cache and !@@platform_cache.empty?
479
- @@platform_cache = MU::Cloud.supportedClouds.map { |cloud|
480
- begin
481
- loadCloudType(cloud, :Server)
482
- rescue MU::Cloud::MuCloudResourceNotImplemented, MU::MuError
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
445
  # A list of supported cloud resource types as Mu classes
641
446
  def self.resource_types;
642
447
  @@resource_types
@@ -658,9 +463,9 @@ module MU
658
463
  if name == type.to_sym or
659
464
  cloudclass[:cfg_name] == type or
660
465
  cloudclass[:cfg_plural] == type or
661
- Object.const_get("MU").const_get("Cloud").const_get(name) == type
466
+ MU::Cloud.const_get(name) == type
662
467
  type = name
663
- return [type.to_sym, cloudclass[:cfg_name], cloudclass[:cfg_plural], Object.const_get("MU").const_get("Cloud").const_get(name), cloudclass]
468
+ return [type.to_sym, cloudclass[:cfg_name], cloudclass[:cfg_plural], MU::Cloud.const_get(name), cloudclass]
664
469
  end
665
470
  }
666
471
  if assert
@@ -670,80 +475,6 @@ module MU
670
475
  [nil, nil, nil, nil, {}]
671
476
  end
672
477
 
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
478
  # @return [Mutex]
748
479
  def self.userdata_mutex
749
480
  @userdata_mutex ||= Mutex.new
@@ -767,7 +498,7 @@ module MU
767
498
  end
768
499
  template_variables["credentials"] ||= credentials
769
500
  $mu = OpenStruct.new(template_variables)
770
- userdata_dir = File.expand_path(MU.myRoot+"/modules/mu/clouds/#{cloud.downcase}/userdata")
501
+ userdata_dir = File.expand_path(MU.myRoot+"/modules/mu/providers/#{cloud.downcase}/userdata")
771
502
 
772
503
  platform = if %w{win2k12r2 win2k12 win2k8 win2k8r2 win2k16 windows win2k19}.include?(platform)
773
504
  "windows"
@@ -821,13 +552,25 @@ module MU
821
552
  }
822
553
  end
823
554
 
555
+ # Given a resource type, validate that it's legit and return its base class from the {MU::Cloud} module
556
+ # @param type [String]
557
+ # @return [MU::Cloud]
558
+ def self.loadBaseType(type)
559
+ raise MuError, "Argument to MU::Cloud.loadBaseType cannot be nil" if type.nil?
560
+ shortclass, cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(type)
561
+ if !shortclass
562
+ raise MuCloudResourceNotImplemented, "#{type} does not appear to be a valid resource type"
563
+ end
564
+ Object.const_get("MU").const_get("Cloud").const_get(shortclass)
565
+ end
566
+
824
567
  @cloud_class_cache = {}
825
568
  # Given a cloud layer and resource type, return the class which implements it.
826
569
  # @param cloud [String]: The Cloud layer
827
570
  # @param type [String]: The resource type. Can be the full class name, symbolic name, or Basket of Kittens configuration shorthand for the resource type.
828
571
  # @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?
572
+ def self.resourceClass(cloud, type)
573
+ raise MuError, "cloud argument to MU::Cloud.resourceClass cannot be nil" if cloud.nil?
831
574
  shortclass, cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(type)
832
575
  if @cloud_class_cache.has_key?(cloud) and @cloud_class_cache[cloud].has_key?(type)
833
576
  if @cloud_class_cache[cloud][type].nil?
@@ -839,18 +582,20 @@ module MU
839
582
  if cfg_name.nil?
840
583
  raise MuError, "Can't find a cloud resource type named '#{type}'"
841
584
  end
842
- if !File.size?(MU.myRoot+"/modules/mu/clouds/#{cloud.downcase}.rb")
585
+ if !File.size?(MU.myRoot+"/modules/mu/providers/#{cloud.downcase}.rb")
843
586
  raise MuError, "Requested to use unsupported provisioning layer #{cloud}"
844
587
  end
845
588
  begin
846
- require "mu/clouds/#{cloud.downcase}/#{cfg_name}"
589
+ require "mu/providers/#{cloud.downcase}/#{cfg_name}"
847
590
  rescue LoadError => e
848
591
  raise MuCloudResourceNotImplemented, "MU::Cloud::#{cloud} does not currently implement #{shortclass}, or implementation does not load correctly (#{e.message})"
849
592
  end
593
+
850
594
  @cloud_class_cache[cloud] = {} if !@cloud_class_cache.has_key?(cloud)
851
595
  begin
852
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
596
+ cloudclass = const_get("MU").const_get("Cloud").const_get(cloud)
853
597
  myclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(shortclass)
598
+
854
599
  @@resource_types[shortclass.to_sym][:class].each { |class_method|
855
600
  if !myclass.respond_to?(class_method) or myclass.method(class_method).owner.to_s != "#<Class:#{myclass}>"
856
601
  raise MuError, "MU::Cloud::#{cloud}::#{shortclass} has not implemented required class method #{class_method}"
@@ -868,6 +613,7 @@ module MU
868
613
  }
869
614
 
870
615
  @cloud_class_cache[cloud][type] = myclass
616
+
871
617
  return myclass
872
618
  rescue NameError => e
873
619
  @cloud_class_cache[cloud][type] = nil
@@ -875,1429 +621,9 @@ module MU
875
621
  end
876
622
  end
877
623
 
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
624
+ require 'mu/cloud/machine_images'
625
+ require 'mu/cloud/resource_base'
626
+ require 'mu/cloud/providers'
2301
627
 
2302
628
  end
2303
629