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