cloud-mu 3.1.2 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Dockerfile +15 -3
- data/ansible/roles/mu-windows/README.md +33 -0
- data/ansible/roles/mu-windows/defaults/main.yml +2 -0
- 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/handlers/main.yml +2 -0
- data/ansible/roles/mu-windows/meta/main.yml +53 -0
- data/ansible/roles/mu-windows/tasks/main.yml +36 -0
- data/ansible/roles/mu-windows/tests/inventory +2 -0
- data/ansible/roles/mu-windows/tests/test.yml +5 -0
- data/ansible/roles/mu-windows/vars/main.yml +2 -0
- data/bin/mu-adopt +10 -13
- 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 -3
- data/bin/mu-node-manage +15 -16
- data/bin/mu-run-tests +135 -37
- data/cloud-mu.gemspec +22 -20
- 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 +3 -2
- data/cookbooks/mu-tools/libraries/monkey.rb +35 -0
- 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/google_api.rb +2 -2
- data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
- data/cookbooks/mu-tools/recipes/windows-client.rb +163 -164
- data/cookbooks/mu-tools/resources/disk.rb +1 -1
- data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
- data/extras/clean-stock-amis +25 -19
- data/extras/generate-stock-images +1 -0
- data/extras/image-generators/AWS/win2k12.yaml +18 -13
- data/extras/image-generators/AWS/win2k16.yaml +18 -13
- data/extras/image-generators/AWS/win2k19.yaml +21 -0
- data/extras/image-generators/Google/centos6.yaml +1 -0
- data/extras/image-generators/Google/centos7.yaml +1 -1
- data/modules/mommacat.ru +6 -16
- data/modules/mu.rb +165 -111
- data/modules/mu/adoption.rb +401 -68
- data/modules/mu/cleanup.rb +199 -306
- data/modules/mu/cloud.rb +100 -1632
- data/modules/mu/cloud/database.rb +49 -0
- data/modules/mu/cloud/dnszone.rb +46 -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 +920 -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 +165 -0
- data/modules/mu/config.rb +171 -1767
- data/modules/mu/config/alarm.rb +2 -6
- data/modules/mu/config/bucket.rb +4 -4
- data/modules/mu/config/cache_cluster.rb +1 -1
- data/modules/mu/config/collection.rb +4 -4
- data/modules/mu/config/container_cluster.rb +9 -4
- data/modules/mu/config/database.rb +83 -104
- data/modules/mu/config/database.yml +1 -2
- data/modules/mu/config/dnszone.rb +6 -6
- data/modules/mu/config/doc_helpers.rb +516 -0
- data/modules/mu/config/endpoint.rb +4 -4
- data/modules/mu/config/firewall_rule.rb +103 -4
- data/modules/mu/config/folder.rb +4 -4
- data/modules/mu/config/function.rb +3 -3
- data/modules/mu/config/group.rb +4 -4
- data/modules/mu/config/habitat.rb +4 -4
- data/modules/mu/config/loadbalancer.rb +60 -14
- data/modules/mu/config/log.rb +4 -4
- data/modules/mu/config/msg_queue.rb +4 -4
- data/modules/mu/config/nosqldb.rb +4 -4
- data/modules/mu/config/notifier.rb +3 -3
- data/modules/mu/config/ref.rb +365 -0
- data/modules/mu/config/role.rb +4 -4
- data/modules/mu/config/schema_helpers.rb +509 -0
- data/modules/mu/config/search_domain.rb +4 -4
- data/modules/mu/config/server.rb +97 -70
- data/modules/mu/config/server.yml +1 -0
- data/modules/mu/config/server_pool.rb +5 -9
- data/modules/mu/config/storage_pool.rb +1 -1
- data/modules/mu/config/tail.rb +200 -0
- data/modules/mu/config/user.rb +4 -4
- data/modules/mu/config/vpc.rb +70 -27
- data/modules/mu/config/vpc.yml +0 -1
- data/modules/mu/defaults/AWS.yaml +83 -60
- data/modules/mu/defaults/Azure.yaml +1 -0
- data/modules/mu/defaults/Google.yaml +3 -2
- data/modules/mu/deploy.rb +30 -26
- data/modules/mu/groomer.rb +17 -2
- data/modules/mu/groomers/ansible.rb +188 -41
- data/modules/mu/groomers/chef.rb +116 -55
- data/modules/mu/logger.rb +127 -148
- data/modules/mu/master.rb +389 -2
- data/modules/mu/master/chef.rb +3 -4
- data/modules/mu/master/ldap.rb +3 -3
- data/modules/mu/master/ssl.rb +12 -3
- data/modules/mu/mommacat.rb +217 -2612
- data/modules/mu/mommacat/daemon.rb +397 -0
- data/modules/mu/mommacat/naming.rb +473 -0
- data/modules/mu/mommacat/search.rb +495 -0
- data/modules/mu/mommacat/storage.rb +722 -0
- data/modules/mu/{clouds → providers}/README.md +1 -1
- data/modules/mu/{clouds → providers}/aws.rb +271 -112
- data/modules/mu/{clouds → providers}/aws/alarm.rb +5 -3
- data/modules/mu/{clouds → providers}/aws/bucket.rb +26 -22
- data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +33 -67
- data/modules/mu/{clouds → providers}/aws/collection.rb +24 -23
- data/modules/mu/{clouds → providers}/aws/container_cluster.rb +681 -721
- data/modules/mu/providers/aws/database.rb +1744 -0
- data/modules/mu/{clouds → providers}/aws/dnszone.rb +64 -63
- data/modules/mu/{clouds → providers}/aws/endpoint.rb +22 -27
- data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +214 -244
- data/modules/mu/{clouds → providers}/aws/folder.rb +7 -7
- data/modules/mu/{clouds → providers}/aws/function.rb +17 -22
- data/modules/mu/{clouds → providers}/aws/group.rb +23 -23
- data/modules/mu/{clouds → providers}/aws/habitat.rb +17 -14
- data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +57 -48
- data/modules/mu/{clouds → providers}/aws/log.rb +15 -12
- data/modules/mu/{clouds → providers}/aws/msg_queue.rb +17 -16
- data/modules/mu/{clouds → providers}/aws/nosqldb.rb +18 -11
- data/modules/mu/{clouds → providers}/aws/notifier.rb +11 -6
- data/modules/mu/{clouds → providers}/aws/role.rb +112 -86
- data/modules/mu/{clouds → providers}/aws/search_domain.rb +39 -33
- data/modules/mu/{clouds → providers}/aws/server.rb +835 -1133
- data/modules/mu/{clouds → providers}/aws/server_pool.rb +56 -60
- data/modules/mu/{clouds → providers}/aws/storage_pool.rb +24 -42
- data/modules/mu/{clouds → providers}/aws/user.rb +21 -22
- data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
- data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +0 -0
- data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
- data/modules/mu/{clouds → providers}/aws/vpc.rb +523 -929
- data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
- data/modules/mu/{clouds → providers}/azure.rb +29 -9
- data/modules/mu/{clouds → providers}/azure/container_cluster.rb +3 -8
- data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +18 -11
- data/modules/mu/{clouds → providers}/azure/habitat.rb +8 -6
- data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +5 -5
- data/modules/mu/{clouds → providers}/azure/role.rb +8 -10
- data/modules/mu/{clouds → providers}/azure/server.rb +95 -48
- data/modules/mu/{clouds → providers}/azure/user.rb +6 -8
- 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 +16 -21
- data/modules/mu/{clouds → providers}/cloudformation.rb +18 -7
- 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 +5 -7
- data/modules/mu/{clouds → providers}/docker.rb +0 -0
- data/modules/mu/{clouds → providers}/google.rb +67 -30
- data/modules/mu/{clouds → providers}/google/bucket.rb +13 -15
- data/modules/mu/{clouds → providers}/google/container_cluster.rb +84 -77
- data/modules/mu/{clouds → providers}/google/database.rb +10 -20
- data/modules/mu/{clouds → providers}/google/firewall_rule.rb +15 -14
- data/modules/mu/{clouds → providers}/google/folder.rb +20 -17
- data/modules/mu/{clouds → providers}/google/function.rb +139 -167
- data/modules/mu/{clouds → providers}/google/group.rb +29 -34
- data/modules/mu/{clouds → providers}/google/habitat.rb +21 -22
- data/modules/mu/{clouds → providers}/google/loadbalancer.rb +18 -20
- data/modules/mu/{clouds → providers}/google/role.rb +92 -58
- data/modules/mu/{clouds → providers}/google/server.rb +242 -155
- data/modules/mu/{clouds → providers}/google/server_pool.rb +25 -44
- data/modules/mu/{clouds → providers}/google/user.rb +95 -31
- 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 +103 -79
- data/modules/tests/bucket.yml +4 -0
- data/modules/tests/centos6.yaml +11 -0
- data/modules/tests/centos7.yaml +11 -0
- data/modules/tests/centos8.yaml +12 -0
- data/modules/tests/ecs.yaml +23 -0
- data/modules/tests/includes-and-params.yaml +2 -1
- data/modules/tests/rds.yaml +108 -0
- data/modules/tests/regrooms/aws-iam.yaml +201 -0
- data/modules/tests/regrooms/bucket.yml +19 -0
- data/modules/tests/regrooms/rds.yaml +123 -0
- data/modules/tests/server-with-scrub-muisms.yaml +1 -0
- data/modules/tests/super_simple_bok.yml +1 -3
- data/modules/tests/win2k12.yaml +17 -5
- data/modules/tests/win2k16.yaml +25 -0
- data/modules/tests/win2k19.yaml +25 -0
- data/requirements.txt +1 -0
- data/spec/mu/clouds/azure_spec.rb +2 -2
- metadata +232 -154
- data/extras/image-generators/AWS/windows.yaml +0 -18
- data/modules/mu/clouds/aws/database.rb +0 -1985
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
|
#
|
|
@@ -178,8 +173,8 @@ module MU
|
|
|
178
173
|
:interface => self.const_get("Folder"),
|
|
179
174
|
:deps_wait_on_my_creation => true,
|
|
180
175
|
:waits_on_parent_completion => true,
|
|
181
|
-
:class => generic_class_methods,
|
|
182
|
-
:instance => generic_instance_methods
|
|
176
|
+
:class => @@generic_class_methods,
|
|
177
|
+
:instance => @@generic_instance_methods
|
|
183
178
|
},
|
|
184
179
|
:Habitat => {
|
|
185
180
|
:has_multiples => false,
|
|
@@ -189,8 +184,8 @@ module MU
|
|
|
189
184
|
:interface => self.const_get("Habitat"),
|
|
190
185
|
:deps_wait_on_my_creation => true,
|
|
191
186
|
:waits_on_parent_completion => true,
|
|
192
|
-
:class => generic_class_methods + [:isLive?],
|
|
193
|
-
:instance => generic_instance_methods + [:groom]
|
|
187
|
+
:class => @@generic_class_methods + [:isLive?],
|
|
188
|
+
:instance => @@generic_instance_methods + [:groom]
|
|
194
189
|
},
|
|
195
190
|
:Collection => {
|
|
196
191
|
:has_multiples => false,
|
|
@@ -200,8 +195,8 @@ module MU
|
|
|
200
195
|
:interface => self.const_get("Collection"),
|
|
201
196
|
:deps_wait_on_my_creation => true,
|
|
202
197
|
:waits_on_parent_completion => false,
|
|
203
|
-
:class => generic_class_methods,
|
|
204
|
-
:instance => generic_instance_methods
|
|
198
|
+
:class => @@generic_class_methods,
|
|
199
|
+
:instance => @@generic_instance_methods
|
|
205
200
|
},
|
|
206
201
|
:Database => {
|
|
207
202
|
:has_multiples => true,
|
|
@@ -211,8 +206,8 @@ module MU
|
|
|
211
206
|
:interface => self.const_get("Database"),
|
|
212
207
|
:deps_wait_on_my_creation => true,
|
|
213
208
|
:waits_on_parent_completion => false,
|
|
214
|
-
:class => generic_class_methods,
|
|
215
|
-
:instance => generic_instance_methods + [:groom, :allowHost]
|
|
209
|
+
:class => @@generic_class_methods,
|
|
210
|
+
:instance => @@generic_instance_methods + [:groom, :allowHost]
|
|
216
211
|
},
|
|
217
212
|
:DNSZone => {
|
|
218
213
|
:has_multiples => false,
|
|
@@ -222,8 +217,8 @@ module MU
|
|
|
222
217
|
:interface => self.const_get("DNSZone"),
|
|
223
218
|
:deps_wait_on_my_creation => true,
|
|
224
219
|
:waits_on_parent_completion => true,
|
|
225
|
-
:class => generic_class_methods + [:genericMuDNSEntry, :createRecordsFromConfig],
|
|
226
|
-
:instance => generic_instance_methods
|
|
220
|
+
:class => @@generic_class_methods + [:genericMuDNSEntry, :createRecordsFromConfig],
|
|
221
|
+
:instance => @@generic_instance_methods
|
|
227
222
|
},
|
|
228
223
|
:FirewallRule => {
|
|
229
224
|
:has_multiples => false,
|
|
@@ -233,8 +228,8 @@ module MU
|
|
|
233
228
|
:interface => self.const_get("FirewallRule"),
|
|
234
229
|
:deps_wait_on_my_creation => true,
|
|
235
230
|
:waits_on_parent_completion => false,
|
|
236
|
-
:class => generic_class_methods,
|
|
237
|
-
:instance => generic_instance_methods + [:groom, :addRule]
|
|
231
|
+
:class => @@generic_class_methods,
|
|
232
|
+
:instance => @@generic_instance_methods + [:groom, :addRule]
|
|
238
233
|
},
|
|
239
234
|
:LoadBalancer => {
|
|
240
235
|
:has_multiples => false,
|
|
@@ -244,8 +239,8 @@ module MU
|
|
|
244
239
|
:interface => self.const_get("LoadBalancer"),
|
|
245
240
|
:deps_wait_on_my_creation => true,
|
|
246
241
|
:waits_on_parent_completion => false,
|
|
247
|
-
:class => generic_class_methods,
|
|
248
|
-
:instance => generic_instance_methods + [:groom, :registerNode]
|
|
242
|
+
:class => @@generic_class_methods,
|
|
243
|
+
:instance => @@generic_instance_methods + [:groom, :registerNode]
|
|
249
244
|
},
|
|
250
245
|
:Server => {
|
|
251
246
|
:has_multiples => true,
|
|
@@ -255,8 +250,8 @@ module MU
|
|
|
255
250
|
:interface => self.const_get("Server"),
|
|
256
251
|
:deps_wait_on_my_creation => false,
|
|
257
252
|
:waits_on_parent_completion => false,
|
|
258
|
-
:class => generic_class_methods + [:validateInstanceType, :imageTimeStamp],
|
|
259
|
-
:instance => generic_instance_methods + [:groom, :postBoot, :getSSHConfig, :canonicalIP, :getWindowsAdminPassword, :active?, :groomer, :mu_windows_name, :mu_windows_name=, :reboot, :addVolume, :genericNAT]
|
|
253
|
+
:class => @@generic_class_methods + [:validateInstanceType, :imageTimeStamp],
|
|
254
|
+
:instance => @@generic_instance_methods + [:groom, :postBoot, :getSSHConfig, :canonicalIP, :getWindowsAdminPassword, :active?, :groomer, :mu_windows_name, :mu_windows_name=, :reboot, :addVolume, :genericNAT, :listIPs]
|
|
260
255
|
},
|
|
261
256
|
:ServerPool => {
|
|
262
257
|
:has_multiples => false,
|
|
@@ -266,8 +261,8 @@ module MU
|
|
|
266
261
|
:interface => self.const_get("ServerPool"),
|
|
267
262
|
:deps_wait_on_my_creation => false,
|
|
268
263
|
:waits_on_parent_completion => true,
|
|
269
|
-
:class => generic_class_methods,
|
|
270
|
-
:instance => generic_instance_methods + [:groom, :listNodes]
|
|
264
|
+
:class => @@generic_class_methods,
|
|
265
|
+
:instance => @@generic_instance_methods + [:groom, :listNodes]
|
|
271
266
|
},
|
|
272
267
|
:VPC => {
|
|
273
268
|
:has_multiples => false,
|
|
@@ -277,8 +272,8 @@ module MU
|
|
|
277
272
|
:interface => self.const_get("VPC"),
|
|
278
273
|
:deps_wait_on_my_creation => true,
|
|
279
274
|
:waits_on_parent_completion => false,
|
|
280
|
-
:class => generic_class_methods,
|
|
281
|
-
:instance => generic_instance_methods + [:groom, :subnets, :getSubnet, :
|
|
275
|
+
:class => @@generic_class_methods,
|
|
276
|
+
:instance => @@generic_instance_methods + [:groom, :subnets, :getSubnet, :findBastion, :findNat]
|
|
282
277
|
},
|
|
283
278
|
:CacheCluster => {
|
|
284
279
|
:has_multiples => true,
|
|
@@ -288,8 +283,8 @@ module MU
|
|
|
288
283
|
:interface => self.const_get("CacheCluster"),
|
|
289
284
|
:deps_wait_on_my_creation => true,
|
|
290
285
|
:waits_on_parent_completion => false,
|
|
291
|
-
:class => generic_class_methods,
|
|
292
|
-
:instance => generic_instance_methods + [:groom]
|
|
286
|
+
:class => @@generic_class_methods,
|
|
287
|
+
:instance => @@generic_instance_methods + [:groom]
|
|
293
288
|
},
|
|
294
289
|
:Alarm => {
|
|
295
290
|
:has_multiples => false,
|
|
@@ -299,8 +294,8 @@ module MU
|
|
|
299
294
|
:interface => self.const_get("Alarm"),
|
|
300
295
|
:deps_wait_on_my_creation => false,
|
|
301
296
|
:waits_on_parent_completion => true,
|
|
302
|
-
:class => generic_class_methods,
|
|
303
|
-
:instance => generic_instance_methods + [:groom]
|
|
297
|
+
:class => @@generic_class_methods,
|
|
298
|
+
:instance => @@generic_instance_methods + [:groom]
|
|
304
299
|
},
|
|
305
300
|
:Notifier => {
|
|
306
301
|
:has_multiples => false,
|
|
@@ -310,8 +305,8 @@ module MU
|
|
|
310
305
|
:interface => self.const_get("Notifier"),
|
|
311
306
|
:deps_wait_on_my_creation => false,
|
|
312
307
|
:waits_on_parent_completion => false,
|
|
313
|
-
:class => generic_class_methods,
|
|
314
|
-
:instance => generic_instance_methods + [:groom]
|
|
308
|
+
:class => @@generic_class_methods,
|
|
309
|
+
:instance => @@generic_instance_methods + [:groom]
|
|
315
310
|
},
|
|
316
311
|
:Log => {
|
|
317
312
|
:has_multiples => false,
|
|
@@ -321,8 +316,8 @@ module MU
|
|
|
321
316
|
:interface => self.const_get("Log"),
|
|
322
317
|
:deps_wait_on_my_creation => true,
|
|
323
318
|
:waits_on_parent_completion => true,
|
|
324
|
-
:class => generic_class_methods,
|
|
325
|
-
:instance => generic_instance_methods + [:groom]
|
|
319
|
+
:class => @@generic_class_methods,
|
|
320
|
+
:instance => @@generic_instance_methods + [:groom]
|
|
326
321
|
},
|
|
327
322
|
:StoragePool => {
|
|
328
323
|
:has_multiples => false,
|
|
@@ -332,8 +327,8 @@ module MU
|
|
|
332
327
|
:interface => self.const_get("StoragePool"),
|
|
333
328
|
:deps_wait_on_my_creation => true,
|
|
334
329
|
:waits_on_parent_completion => false,
|
|
335
|
-
:class => generic_class_methods,
|
|
336
|
-
:instance => generic_instance_methods + [:groom]
|
|
330
|
+
:class => @@generic_class_methods,
|
|
331
|
+
:instance => @@generic_instance_methods + [:groom]
|
|
337
332
|
},
|
|
338
333
|
:Function => {
|
|
339
334
|
:has_multiples => false,
|
|
@@ -343,8 +338,8 @@ module MU
|
|
|
343
338
|
:interface => self.const_get("Function"),
|
|
344
339
|
:deps_wait_on_my_creation => true,
|
|
345
340
|
:waits_on_parent_completion => false,
|
|
346
|
-
:class => generic_class_methods,
|
|
347
|
-
:instance => generic_instance_methods + [:groom]
|
|
341
|
+
:class => @@generic_class_methods,
|
|
342
|
+
:instance => @@generic_instance_methods + [:groom]
|
|
348
343
|
},
|
|
349
344
|
:Endpoint => {
|
|
350
345
|
:has_multiples => false,
|
|
@@ -354,8 +349,8 @@ module MU
|
|
|
354
349
|
:interface => self.const_get("Endpoint"),
|
|
355
350
|
:deps_wait_on_my_creation => true,
|
|
356
351
|
:waits_on_parent_completion => false,
|
|
357
|
-
:class => generic_class_methods,
|
|
358
|
-
:instance => generic_instance_methods + [:groom]
|
|
352
|
+
:class => @@generic_class_methods,
|
|
353
|
+
:instance => @@generic_instance_methods + [:groom]
|
|
359
354
|
},
|
|
360
355
|
:ContainerCluster => {
|
|
361
356
|
:has_multiples => false,
|
|
@@ -365,8 +360,8 @@ module MU
|
|
|
365
360
|
:interface => self.const_get("ContainerCluster"),
|
|
366
361
|
:deps_wait_on_my_creation => true,
|
|
367
362
|
:waits_on_parent_completion => false,
|
|
368
|
-
:class => generic_class_methods,
|
|
369
|
-
:instance => generic_instance_methods + [:groom]
|
|
363
|
+
:class => @@generic_class_methods,
|
|
364
|
+
:instance => @@generic_instance_methods + [:groom]
|
|
370
365
|
},
|
|
371
366
|
:SearchDomain => {
|
|
372
367
|
:has_multiples => false,
|
|
@@ -376,8 +371,8 @@ module MU
|
|
|
376
371
|
:interface => self.const_get("SearchDomain"),
|
|
377
372
|
:deps_wait_on_my_creation => true,
|
|
378
373
|
:waits_on_parent_completion => false,
|
|
379
|
-
:class => generic_class_methods,
|
|
380
|
-
:instance => generic_instance_methods + [:groom]
|
|
374
|
+
:class => @@generic_class_methods,
|
|
375
|
+
:instance => @@generic_instance_methods + [:groom]
|
|
381
376
|
},
|
|
382
377
|
:MsgQueue => {
|
|
383
378
|
:has_multiples => false,
|
|
@@ -387,8 +382,8 @@ module MU
|
|
|
387
382
|
:interface => self.const_get("MsgQueue"),
|
|
388
383
|
:deps_wait_on_my_creation => true,
|
|
389
384
|
:waits_on_parent_completion => true,
|
|
390
|
-
:class => generic_class_methods,
|
|
391
|
-
:instance => generic_instance_methods + [:groom]
|
|
385
|
+
:class => @@generic_class_methods,
|
|
386
|
+
:instance => @@generic_instance_methods + [:groom]
|
|
392
387
|
},
|
|
393
388
|
:User => {
|
|
394
389
|
:has_multiples => false,
|
|
@@ -398,8 +393,8 @@ module MU
|
|
|
398
393
|
:interface => self.const_get("User"),
|
|
399
394
|
:deps_wait_on_my_creation => true,
|
|
400
395
|
:waits_on_parent_completion => true,
|
|
401
|
-
:class => generic_class_methods,
|
|
402
|
-
:instance => generic_instance_methods + [:groom]
|
|
396
|
+
:class => @@generic_class_methods,
|
|
397
|
+
:instance => @@generic_instance_methods + [:groom]
|
|
403
398
|
},
|
|
404
399
|
:Group => {
|
|
405
400
|
:has_multiples => false,
|
|
@@ -409,8 +404,8 @@ module MU
|
|
|
409
404
|
:interface => self.const_get("Group"),
|
|
410
405
|
:deps_wait_on_my_creation => true,
|
|
411
406
|
:waits_on_parent_completion => true,
|
|
412
|
-
:class => generic_class_methods,
|
|
413
|
-
:instance => generic_instance_methods + [:groom]
|
|
407
|
+
:class => @@generic_class_methods,
|
|
408
|
+
:instance => @@generic_instance_methods + [:groom]
|
|
414
409
|
},
|
|
415
410
|
:Role => {
|
|
416
411
|
:has_multiples => false,
|
|
@@ -420,8 +415,8 @@ module MU
|
|
|
420
415
|
:interface => self.const_get("Role"),
|
|
421
416
|
:deps_wait_on_my_creation => true,
|
|
422
417
|
:waits_on_parent_completion => true,
|
|
423
|
-
:class => generic_class_methods,
|
|
424
|
-
:instance => generic_instance_methods + [:groom]
|
|
418
|
+
:class => @@generic_class_methods,
|
|
419
|
+
:instance => @@generic_instance_methods + [:groom]
|
|
425
420
|
},
|
|
426
421
|
:Bucket => {
|
|
427
422
|
:has_multiples => false,
|
|
@@ -431,8 +426,8 @@ module MU
|
|
|
431
426
|
:interface => self.const_get("Bucket"),
|
|
432
427
|
:deps_wait_on_my_creation => true,
|
|
433
428
|
:waits_on_parent_completion => true,
|
|
434
|
-
:class => generic_class_methods + [:upload],
|
|
435
|
-
:instance => generic_instance_methods + [:groom, :upload]
|
|
429
|
+
:class => @@generic_class_methods + [:upload],
|
|
430
|
+
:instance => @@generic_instance_methods + [:groom, :upload]
|
|
436
431
|
},
|
|
437
432
|
:NoSQLDB => {
|
|
438
433
|
:has_multiples => false,
|
|
@@ -442,201 +437,11 @@ module MU
|
|
|
442
437
|
:interface => self.const_get("NoSQLDB"),
|
|
443
438
|
:deps_wait_on_my_creation => true,
|
|
444
439
|
:waits_on_parent_completion => true,
|
|
445
|
-
:class => generic_class_methods,
|
|
446
|
-
:instance => generic_instance_methods + [:groom]
|
|
440
|
+
:class => @@generic_class_methods,
|
|
441
|
+
:instance => @@generic_instance_methods + [:groom]
|
|
447
442
|
}
|
|
448
443
|
}.freeze
|
|
449
444
|
|
|
450
|
-
# The public AWS S3 bucket where we expect to find YAML files listing our
|
|
451
|
-
# standard base images for various platforms.
|
|
452
|
-
BASE_IMAGE_BUCKET = "cloudamatic"
|
|
453
|
-
# The path in the AWS S3 bucket where we expect to find YAML files listing
|
|
454
|
-
# our standard base images for various platforms.
|
|
455
|
-
BASE_IMAGE_PATH = "/images"
|
|
456
|
-
|
|
457
|
-
# Aliases for platform names, in case we don't have actual images built for
|
|
458
|
-
# them.
|
|
459
|
-
PLATFORM_ALIASES = {
|
|
460
|
-
"linux" => "centos7",
|
|
461
|
-
"windows" => "win2k12r2",
|
|
462
|
-
"win2k12" => "win2k12r2",
|
|
463
|
-
"ubuntu" => "ubuntu16",
|
|
464
|
-
"centos" => "centos7",
|
|
465
|
-
"rhel7" => "rhel71",
|
|
466
|
-
"rhel" => "rhel71",
|
|
467
|
-
"amazon" => "amazon2016"
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
@@image_fetch_cache = {}
|
|
471
|
-
@@platform_cache = []
|
|
472
|
-
@@image_fetch_semaphore = Mutex.new
|
|
473
|
-
|
|
474
|
-
# Rifle our image lists from {MU::Cloud.getStockImage} and return a list
|
|
475
|
-
# of valid +platform+ names.
|
|
476
|
-
# @return [Array<String>]
|
|
477
|
-
def self.listPlatforms
|
|
478
|
-
return @@platform_cache if @@platform_cache and !@@platform_cache.empty?
|
|
479
|
-
@@platform_cache = MU::Cloud.supportedClouds.map { |cloud|
|
|
480
|
-
begin
|
|
481
|
-
loadCloudType(cloud, :Server)
|
|
482
|
-
rescue MU::Cloud::MuCloudResourceNotImplemented, MU::MuError => e
|
|
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 Exception => e
|
|
533
|
-
if fail_hard
|
|
534
|
-
raise MuError, "Failed to fetch stock images from #{base_url}/#{cloud}.yaml (#{e.message})"
|
|
535
|
-
else
|
|
536
|
-
MU.log "Failed to fetch stock images from #{base_url}/#{cloud}.yaml (#{e.message})", MU::WARN if !quiet
|
|
537
|
-
end
|
|
538
|
-
end
|
|
539
|
-
end
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
@@image_fetch_semaphore.synchronize {
|
|
544
|
-
@@image_fetch_cache[cloud] = {
|
|
545
|
-
'contents' => images.dup,
|
|
546
|
-
'time' => Time.now
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
backwards_compat = {
|
|
551
|
-
"AWS" => "amazon_images",
|
|
552
|
-
"Google" => "google_images",
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
# Load from inside our repository, if we didn't get images elsewise
|
|
556
|
-
if images.nil?
|
|
557
|
-
[backwards_compat[cloud], cloud].each { |file|
|
|
558
|
-
next if file.nil?
|
|
559
|
-
if File.exist?("#{MU.myRoot}/modules/mu/defaults/#{file}.yaml")
|
|
560
|
-
images = YAML.load(File.read("#{MU.myRoot}/modules/mu/defaults/#{file}.yaml"))
|
|
561
|
-
break
|
|
562
|
-
end
|
|
563
|
-
}
|
|
564
|
-
end
|
|
565
|
-
|
|
566
|
-
# Now overlay local overrides, both of the systemwide (/opt/mu/etc) and
|
|
567
|
-
# per-user (~/.mu/etc) variety.
|
|
568
|
-
[backwards_compat[cloud], cloud].each { |file|
|
|
569
|
-
next if file.nil?
|
|
570
|
-
if File.exist?("#{MU.etcDir}/#{file}.yaml")
|
|
571
|
-
images ||= {}
|
|
572
|
-
images.deep_merge!(YAML.load(File.read("#{MU.etcDir}/#{file}.yaml")))
|
|
573
|
-
end
|
|
574
|
-
if Process.uid != 0
|
|
575
|
-
basepath = Etc.getpwuid(Process.uid).dir+"/.mu/etc"
|
|
576
|
-
if File.exist?("#{basepath}/#{file}.yaml")
|
|
577
|
-
images ||= {}
|
|
578
|
-
images.deep_merge!(YAML.load(File.read("#{basepath}/#{file}.yaml")))
|
|
579
|
-
end
|
|
580
|
-
end
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
if images.nil?
|
|
584
|
-
if fail_hard
|
|
585
|
-
raise MuError, "Failed to find any base images for #{cloud}"
|
|
586
|
-
else
|
|
587
|
-
MU.log "Failed to find any base images for #{cloud}", MU::WARN if !quiet
|
|
588
|
-
return nil
|
|
589
|
-
end
|
|
590
|
-
end
|
|
591
|
-
|
|
592
|
-
PLATFORM_ALIASES.each_pair { |a, t|
|
|
593
|
-
if images[t] and !images[a]
|
|
594
|
-
images[a] = images[t]
|
|
595
|
-
end
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
if platform
|
|
599
|
-
if !images[platform]
|
|
600
|
-
if fail_hard
|
|
601
|
-
raise MuError, "No base image for platform #{platform} in cloud #{cloud}"
|
|
602
|
-
else
|
|
603
|
-
MU.log "No base image for platform #{platform} in cloud #{cloud}", MU::WARN if !quiet
|
|
604
|
-
return nil
|
|
605
|
-
end
|
|
606
|
-
end
|
|
607
|
-
images = images[platform]
|
|
608
|
-
|
|
609
|
-
if region
|
|
610
|
-
# We won't fuss about the region argument if this isn't a cloud that
|
|
611
|
-
# has regions, just quietly don't bother.
|
|
612
|
-
if images.is_a?(Hash)
|
|
613
|
-
if images[region]
|
|
614
|
-
images = images[region]
|
|
615
|
-
else
|
|
616
|
-
if fail_hard
|
|
617
|
-
raise MuError, "No base image for platform #{platform} in cloud #{cloud} region #{region} found"
|
|
618
|
-
else
|
|
619
|
-
MU.log "No base image for platform #{platform} in cloud #{cloud} region #{region} found", MU::WARN if !quiet
|
|
620
|
-
return nil
|
|
621
|
-
end
|
|
622
|
-
end
|
|
623
|
-
end
|
|
624
|
-
end
|
|
625
|
-
else
|
|
626
|
-
if region
|
|
627
|
-
images.each_pair { |p, regions|
|
|
628
|
-
# Filter to match our requested region, but for all the platforms,
|
|
629
|
-
# since we didn't specify one.
|
|
630
|
-
if regions.is_a?(Hash)
|
|
631
|
-
regions.delete_if { |r| r != region }
|
|
632
|
-
end
|
|
633
|
-
}
|
|
634
|
-
end
|
|
635
|
-
end
|
|
636
|
-
|
|
637
|
-
images
|
|
638
|
-
end
|
|
639
|
-
|
|
640
445
|
# A list of supported cloud resource types as Mu classes
|
|
641
446
|
def self.resource_types;
|
|
642
447
|
@@resource_types
|
|
@@ -644,80 +449,32 @@ module MU
|
|
|
644
449
|
|
|
645
450
|
# 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
451
|
# @param type [String]: A string that looks like our short or full class name or singular or plural configuration names.
|
|
452
|
+
# @param assert [Boolean]: Raise an exception if the type isn't valid
|
|
647
453
|
# @return [Array]: Class name (Symbol), singular config name (String), plural config name (String), full class name (Object)
|
|
648
|
-
def self.getResourceNames(type)
|
|
649
|
-
|
|
454
|
+
def self.getResourceNames(type, assert = true)
|
|
455
|
+
if !type
|
|
456
|
+
if assert
|
|
457
|
+
raise MuError, "nil resource type requested in getResourceNames"
|
|
458
|
+
else
|
|
459
|
+
return [nil, nil, nil, nil, {}]
|
|
460
|
+
end
|
|
461
|
+
end
|
|
650
462
|
@@resource_types.each_pair { |name, cloudclass|
|
|
651
463
|
if name == type.to_sym or
|
|
652
464
|
cloudclass[:cfg_name] == type or
|
|
653
465
|
cloudclass[:cfg_plural] == type or
|
|
654
|
-
|
|
655
|
-
cfg_name = cloudclass[:cfg_name]
|
|
466
|
+
MU::Cloud.const_get(name) == type
|
|
656
467
|
type = name
|
|
657
|
-
return [type.to_sym, cloudclass[:cfg_name], cloudclass[:cfg_plural],
|
|
658
|
-
end
|
|
659
|
-
}
|
|
660
|
-
[nil, nil, nil, nil, {}]
|
|
661
|
-
end
|
|
662
|
-
|
|
663
|
-
# Net::SSH exceptions seem to have their own behavior vis a vis threads,
|
|
664
|
-
# and our regular call stack gets circumvented when they're thrown. Cheat
|
|
665
|
-
# here to catch them gracefully.
|
|
666
|
-
def self.handleNetSSHExceptions
|
|
667
|
-
Thread.handle_interrupt(Net::SSH::Exception => :never) {
|
|
668
|
-
begin
|
|
669
|
-
Thread.handle_interrupt(Net::SSH::Exception => :immediate) {
|
|
670
|
-
MU.log "(Probably harmless) Caught a Net::SSH Exception in #{Thread.current.inspect}", MU::DEBUG, details: Thread.current.backtrace
|
|
671
|
-
}
|
|
672
|
-
ensure
|
|
673
|
-
# raise NetSSHFail, "Net::SSH had a nutty"
|
|
674
|
-
end
|
|
675
|
-
}
|
|
676
|
-
end
|
|
677
|
-
|
|
678
|
-
# List of known/supported Cloud providers. This may be modified at runtime
|
|
679
|
-
# if an implemention is defective or missing required methods.
|
|
680
|
-
@@supportedCloudList = ['AWS', 'CloudFormation', 'Google', 'Azure']
|
|
681
|
-
|
|
682
|
-
# List of known/supported Cloud providers
|
|
683
|
-
# @return [Array<String>]
|
|
684
|
-
def self.supportedClouds
|
|
685
|
-
@@supportedCloudList
|
|
686
|
-
end
|
|
687
|
-
|
|
688
|
-
# List of known/supported Cloud providers for which we have at least one
|
|
689
|
-
# set of credentials configured.
|
|
690
|
-
# @return [Array<String>]
|
|
691
|
-
def self.availableClouds
|
|
692
|
-
available = []
|
|
693
|
-
MU::Cloud.supportedClouds.each { |cloud|
|
|
694
|
-
begin
|
|
695
|
-
cloudbase = Object.const_get("MU").const_get("Cloud").const_get(cloud)
|
|
696
|
-
next if cloudbase.listCredentials.nil? or cloudbase.listCredentials.empty?
|
|
697
|
-
available << cloud
|
|
698
|
-
rescue NameError
|
|
468
|
+
return [type.to_sym, cloudclass[:cfg_name], cloudclass[:cfg_plural], MU::Cloud.const_get(name), cloudclass]
|
|
699
469
|
end
|
|
700
470
|
}
|
|
471
|
+
if assert
|
|
472
|
+
raise MuError, "Invalid resource type #{type} requested in getResourceNames"
|
|
473
|
+
end
|
|
701
474
|
|
|
702
|
-
|
|
475
|
+
[nil, nil, nil, nil, {}]
|
|
703
476
|
end
|
|
704
477
|
|
|
705
|
-
# Load the container class for each cloud we know about, and inject autoload
|
|
706
|
-
# code for each of its supported resource type classes.
|
|
707
|
-
failed = []
|
|
708
|
-
MU::Cloud.supportedClouds.each { |cloud|
|
|
709
|
-
require "mu/clouds/#{cloud.downcase}"
|
|
710
|
-
cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
|
|
711
|
-
generic_class_methods_toplevel.each { |method|
|
|
712
|
-
if !cloudclass.respond_to?(method)
|
|
713
|
-
MU.log "MU::Cloud::#{cloud} has not implemented required class method #{method}, disabling", MU::ERR
|
|
714
|
-
failed << cloud
|
|
715
|
-
end
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
failed.uniq!
|
|
719
|
-
@@supportedCloudList = @@supportedCloudList - failed
|
|
720
|
-
|
|
721
478
|
# @return [Mutex]
|
|
722
479
|
def self.userdata_mutex
|
|
723
480
|
@userdata_mutex ||= Mutex.new
|
|
@@ -741,7 +498,7 @@ module MU
|
|
|
741
498
|
end
|
|
742
499
|
template_variables["credentials"] ||= credentials
|
|
743
500
|
$mu = OpenStruct.new(template_variables)
|
|
744
|
-
userdata_dir = File.expand_path(MU.myRoot+"/modules/mu/
|
|
501
|
+
userdata_dir = File.expand_path(MU.myRoot+"/modules/mu/providers/#{cloud.downcase}/userdata")
|
|
745
502
|
|
|
746
503
|
platform = if %w{win2k12r2 win2k12 win2k8 win2k8r2 win2k16 windows win2k19}.include?(platform)
|
|
747
504
|
"windows"
|
|
@@ -795,14 +552,26 @@ module MU
|
|
|
795
552
|
}
|
|
796
553
|
end
|
|
797
554
|
|
|
555
|
+
# Given a resource type, validate that it's legit and return its base class from the {MU::Cloud} module
|
|
556
|
+
# @param type [String]
|
|
557
|
+
# @return [MU::Cloud]
|
|
558
|
+
def self.loadBaseType(type)
|
|
559
|
+
raise MuError, "Argument to MU::Cloud.loadBaseType cannot be nil" if type.nil?
|
|
560
|
+
shortclass, cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(type)
|
|
561
|
+
if !shortclass
|
|
562
|
+
raise MuCloudResourceNotImplemented, "#{type} does not appear to be a valid resource type"
|
|
563
|
+
end
|
|
564
|
+
Object.const_get("MU").const_get("Cloud").const_get(shortclass)
|
|
565
|
+
end
|
|
566
|
+
|
|
798
567
|
@cloud_class_cache = {}
|
|
799
568
|
# Given a cloud layer and resource type, return the class which implements it.
|
|
800
569
|
# @param cloud [String]: The Cloud layer
|
|
801
570
|
# @param type [String]: The resource type. Can be the full class name, symbolic name, or Basket of Kittens configuration shorthand for the resource type.
|
|
802
571
|
# @return [Class]: The cloud-specific class implementing this resource
|
|
803
|
-
def self.
|
|
804
|
-
raise MuError, "cloud argument to MU::Cloud.
|
|
805
|
-
shortclass, cfg_name,
|
|
572
|
+
def self.resourceClass(cloud, type)
|
|
573
|
+
raise MuError, "cloud argument to MU::Cloud.resourceClass cannot be nil" if cloud.nil?
|
|
574
|
+
shortclass, cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(type)
|
|
806
575
|
if @cloud_class_cache.has_key?(cloud) and @cloud_class_cache[cloud].has_key?(type)
|
|
807
576
|
if @cloud_class_cache[cloud][type].nil?
|
|
808
577
|
raise MuError, "The '#{type}' resource is not supported in cloud #{cloud} (tried MU::#{cloud}::#{type})", caller
|
|
@@ -813,1349 +582,48 @@ module MU
|
|
|
813
582
|
if cfg_name.nil?
|
|
814
583
|
raise MuError, "Can't find a cloud resource type named '#{type}'"
|
|
815
584
|
end
|
|
816
|
-
if !File.size?(MU.myRoot+"/modules/mu/
|
|
585
|
+
if !File.size?(MU.myRoot+"/modules/mu/providers/#{cloud.downcase}.rb")
|
|
817
586
|
raise MuError, "Requested to use unsupported provisioning layer #{cloud}"
|
|
818
587
|
end
|
|
819
588
|
begin
|
|
820
|
-
require "mu/
|
|
589
|
+
require "mu/providers/#{cloud.downcase}/#{cfg_name}"
|
|
821
590
|
rescue LoadError => e
|
|
822
591
|
raise MuCloudResourceNotImplemented, "MU::Cloud::#{cloud} does not currently implement #{shortclass}, or implementation does not load correctly (#{e.message})"
|
|
823
592
|
end
|
|
593
|
+
|
|
824
594
|
@cloud_class_cache[cloud] = {} if !@cloud_class_cache.has_key?(cloud)
|
|
825
595
|
begin
|
|
826
|
-
cloudclass =
|
|
827
|
-
myclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(
|
|
828
|
-
|
|
596
|
+
cloudclass = const_get("MU").const_get("Cloud").const_get(cloud)
|
|
597
|
+
myclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(shortclass)
|
|
598
|
+
|
|
599
|
+
@@resource_types[shortclass.to_sym][:class].each { |class_method|
|
|
829
600
|
if !myclass.respond_to?(class_method) or myclass.method(class_method).owner.to_s != "#<Class:#{myclass}>"
|
|
830
|
-
raise MuError, "MU::Cloud::#{cloud}::#{
|
|
601
|
+
raise MuError, "MU::Cloud::#{cloud}::#{shortclass} has not implemented required class method #{class_method}"
|
|
831
602
|
end
|
|
832
603
|
}
|
|
833
|
-
@@resource_types[
|
|
604
|
+
@@resource_types[shortclass.to_sym][:instance].each { |instance_method|
|
|
834
605
|
if !myclass.public_instance_methods.include?(instance_method)
|
|
835
|
-
raise MuCloudResourceNotImplemented, "MU::Cloud::#{cloud}::#{
|
|
606
|
+
raise MuCloudResourceNotImplemented, "MU::Cloud::#{cloud}::#{shortclass} has not implemented required instance method #{instance_method}"
|
|
836
607
|
end
|
|
837
608
|
}
|
|
838
609
|
cloudclass.required_instance_methods.each { |instance_method|
|
|
839
610
|
if !myclass.public_instance_methods.include?(instance_method)
|
|
840
|
-
MU.log "MU::Cloud::#{cloud}::#{
|
|
611
|
+
MU.log "MU::Cloud::#{cloud}::#{shortclass} has not implemented required instance method #{instance_method}, will declare as attr_accessor", MU::DEBUG
|
|
841
612
|
end
|
|
842
613
|
}
|
|
843
614
|
|
|
844
615
|
@cloud_class_cache[cloud][type] = myclass
|
|
616
|
+
|
|
845
617
|
return myclass
|
|
846
618
|
rescue NameError => e
|
|
847
619
|
@cloud_class_cache[cloud][type] = nil
|
|
848
|
-
raise MuCloudResourceNotImplemented, "The '#{type}' resource is not supported in cloud #{cloud} (tried MU::#{cloud}::#{
|
|
620
|
+
raise MuCloudResourceNotImplemented, "The '#{type}' resource is not supported in cloud #{cloud} (tried MU::Cloud::#{cloud}::#{shortclass})", e.backtrace
|
|
849
621
|
end
|
|
850
622
|
end
|
|
851
623
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
# Automatically load supported cloud resource classes when they're
|
|
856
|
-
# referenced.
|
|
857
|
-
def self.const_missing(symbol)
|
|
858
|
-
if MU::Cloud.resource_types.has_key?(symbol.to_sym)
|
|
859
|
-
return MU::Cloud.loadCloudType(name.sub(/.*?::([^:]+)$/, '\1'), symbol)
|
|
860
|
-
else
|
|
861
|
-
raise MuCloudResourceNotImplemented, "No such cloud resource #{name}:#{symbol}"
|
|
862
|
-
end
|
|
863
|
-
end
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
@@resource_types.each_pair { |name, attrs|
|
|
868
|
-
Object.const_get("MU").const_get("Cloud").const_get(name).class_eval {
|
|
869
|
-
attr_reader :cloudclass
|
|
870
|
-
attr_reader :cloudobj
|
|
871
|
-
attr_reader :destroyed
|
|
872
|
-
attr_reader :delayed_save
|
|
873
|
-
|
|
874
|
-
def self.shortname
|
|
875
|
-
name.sub(/.*?::([^:]+)$/, '\1')
|
|
876
|
-
end
|
|
877
|
-
|
|
878
|
-
def self.cfg_plural
|
|
879
|
-
MU::Cloud.resource_types[shortname.to_sym][:cfg_plural]
|
|
880
|
-
end
|
|
881
|
-
|
|
882
|
-
def self.has_multiples
|
|
883
|
-
MU::Cloud.resource_types[shortname.to_sym][:has_multiples]
|
|
884
|
-
end
|
|
885
|
-
|
|
886
|
-
def self.cfg_name
|
|
887
|
-
MU::Cloud.resource_types[shortname.to_sym][:cfg_name]
|
|
888
|
-
end
|
|
889
|
-
|
|
890
|
-
def self.can_live_in_vpc
|
|
891
|
-
MU::Cloud.resource_types[shortname.to_sym][:can_live_in_vpc]
|
|
892
|
-
end
|
|
893
|
-
|
|
894
|
-
def self.waits_on_parent_completion
|
|
895
|
-
MU::Cloud.resource_types[shortname.to_sym][:waits_on_parent_completion]
|
|
896
|
-
end
|
|
897
|
-
|
|
898
|
-
def self.deps_wait_on_my_creation
|
|
899
|
-
MU::Cloud.resource_types[shortname.to_sym][:deps_wait_on_my_creation]
|
|
900
|
-
end
|
|
901
|
-
|
|
902
|
-
# Print something palatable when we're called in a string context.
|
|
903
|
-
def to_s
|
|
904
|
-
fullname = "#{self.class.shortname}"
|
|
905
|
-
if !@cloudobj.nil? and !@cloudobj.mu_name.nil?
|
|
906
|
-
@mu_name ||= @cloudobj.mu_name
|
|
907
|
-
end
|
|
908
|
-
if !@mu_name.nil? and !@mu_name.empty?
|
|
909
|
-
fullname = fullname + " '#{@mu_name}'"
|
|
910
|
-
end
|
|
911
|
-
if !@cloud_id.nil?
|
|
912
|
-
fullname = fullname + " (#{@cloud_id})"
|
|
913
|
-
end
|
|
914
|
-
return fullname
|
|
915
|
-
end
|
|
916
|
-
|
|
917
|
-
# Set our +deploy+ and +deploy_id+ attributes, optionally doing so even
|
|
918
|
-
# if they have already been set.
|
|
919
|
-
#
|
|
920
|
-
# @param mommacat [MU::MommaCat]: The deploy to which we're being told we belong
|
|
921
|
-
# @param force [Boolean]: Set even if we already have a deploy object
|
|
922
|
-
# @return [String]: Our new +deploy_id+
|
|
923
|
-
def intoDeploy(mommacat, force: false)
|
|
924
|
-
if force or (!@deploy)
|
|
925
|
-
MU.log "Inserting #{self} (#{self.object_id}) into #{mommacat.deploy_id}", MU::DEBUG
|
|
926
|
-
@deploy = mommacat
|
|
927
|
-
@deploy_id = @deploy.deploy_id
|
|
928
|
-
@cloudobj.intoDeploy(mommacat, force: force) if @cloudobj
|
|
929
|
-
end
|
|
930
|
-
@deploy_id
|
|
931
|
-
end
|
|
932
|
-
|
|
933
|
-
# @param mommacat [MU::MommaCat]: The deployment containing this cloud resource
|
|
934
|
-
# @param mu_name [String]: Optional- specify the full Mu resource name of an existing resource to load, instead of creating a new one
|
|
935
|
-
# @param cloud_id [String]: Optional- specify the cloud provider's identifier for an existing resource to load, instead of creating a new one
|
|
936
|
-
# @param kitten_cfg [Hash]: The parse configuration for this object from {MU::Config}
|
|
937
|
-
def initialize(**args)
|
|
938
|
-
raise MuError, "Cannot invoke Cloud objects without a configuration" if args[:kitten_cfg].nil?
|
|
939
|
-
|
|
940
|
-
# We are a parent wrapper object. Initialize our child object and
|
|
941
|
-
# housekeeping bits accordingly.
|
|
942
|
-
if self.class.name.match(/^MU::Cloud::([^:]+)$/)
|
|
943
|
-
@live = true
|
|
944
|
-
@delayed_save = args[:delayed_save]
|
|
945
|
-
@method_semaphore = Mutex.new
|
|
946
|
-
@method_locks = {}
|
|
947
|
-
if args[:mommacat]
|
|
948
|
-
MU.log "Initializing an instance of #{self.class.name} in #{args[:mommacat].deploy_id} #{mu_name}", MU::DEBUG, details: args[:kitten_cfg]
|
|
949
|
-
elsif args[:mu_name].nil?
|
|
950
|
-
raise MuError, "Can't instantiate a MU::Cloud object with a live deploy or giving us a mu_name"
|
|
951
|
-
else
|
|
952
|
-
MU.log "Initializing a detached #{self.class.name} named #{args[:mu_name]}", MU::DEBUG, details: args[:kitten_cfg]
|
|
953
|
-
end
|
|
954
|
-
|
|
955
|
-
my_cloud = args[:kitten_cfg]['cloud'].to_s || MU::Config.defaultCloud
|
|
956
|
-
if my_cloud.nil? or !MU::Cloud.supportedClouds.include?(my_cloud)
|
|
957
|
-
raise MuError, "Can't instantiate a MU::Cloud object without a valid cloud (saw '#{my_cloud}')"
|
|
958
|
-
end
|
|
959
|
-
|
|
960
|
-
@cloudclass = MU::Cloud.loadCloudType(my_cloud, self.class.shortname)
|
|
961
|
-
@cloudparentclass = Object.const_get("MU").const_get("Cloud").const_get(my_cloud)
|
|
962
|
-
@cloudobj = @cloudclass.new(
|
|
963
|
-
mommacat: args[:mommacat],
|
|
964
|
-
kitten_cfg: args[:kitten_cfg],
|
|
965
|
-
cloud_id: args[:cloud_id],
|
|
966
|
-
mu_name: args[:mu_name]
|
|
967
|
-
)
|
|
968
|
-
raise MuError, "Unknown error instantiating #{self}" if @cloudobj.nil?
|
|
969
|
-
# These should actually call the method live instead of caching a static value
|
|
970
|
-
PUBLIC_ATTRS.each { |a|
|
|
971
|
-
instance_variable_set(("@"+a.to_s).to_sym, @cloudobj.send(a))
|
|
972
|
-
}
|
|
973
|
-
@deploy ||= args[:mommacat]
|
|
974
|
-
@deploy_id ||= @deploy.deploy_id if @deploy
|
|
975
|
-
|
|
976
|
-
# Register with the containing deployment
|
|
977
|
-
if !@deploy.nil? and !@cloudobj.mu_name.nil? and
|
|
978
|
-
!@cloudobj.mu_name.empty? and !args[:delay_descriptor_load]
|
|
979
|
-
describe # XXX is this actually safe here?
|
|
980
|
-
@deploy.addKitten(self.class.cfg_name, @config['name'], self)
|
|
981
|
-
elsif !@deploy.nil? and @cloudobj.mu_name.nil?
|
|
982
|
-
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]
|
|
983
|
-
end
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
# We are actually a child object invoking this via super() from its
|
|
987
|
-
# own initialize(), so initialize all the attributes and instance
|
|
988
|
-
# variables we know to be universal.
|
|
989
|
-
else
|
|
990
|
-
|
|
991
|
-
# Declare the attributes that everyone should have
|
|
992
|
-
class << self
|
|
993
|
-
PUBLIC_ATTRS.each { |a|
|
|
994
|
-
attr_reader a
|
|
995
|
-
}
|
|
996
|
-
end
|
|
997
|
-
|
|
998
|
-
# 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
|
|
999
|
-
@config = MU::Config.manxify(args[:kitten_cfg]) || MU::Config.manxify(args[:config])
|
|
1000
|
-
|
|
1001
|
-
if !@config
|
|
1002
|
-
MU.log "Missing config arguments in setInstanceVariables, can't initialize a cloud object without it", MU::ERR, details: args.keys
|
|
1003
|
-
raise MuError, "Missing config arguments in setInstanceVariables"
|
|
1004
|
-
end
|
|
1005
|
-
|
|
1006
|
-
@deploy = args[:mommacat] || args[:deploy]
|
|
1007
|
-
|
|
1008
|
-
@credentials = args[:credentials]
|
|
1009
|
-
@credentials ||= @config['credentials']
|
|
1010
|
-
|
|
1011
|
-
@cloud = @config['cloud']
|
|
1012
|
-
if !@cloud
|
|
1013
|
-
if self.class.name.match(/^MU::Cloud::([^:]+)(?:::.+|$)/)
|
|
1014
|
-
cloudclass_name = Regexp.last_match[1]
|
|
1015
|
-
if MU::Cloud.supportedClouds.include?(cloudclass_name)
|
|
1016
|
-
@cloud = cloudclass_name
|
|
1017
|
-
end
|
|
1018
|
-
end
|
|
1019
|
-
end
|
|
1020
|
-
if !@cloud
|
|
1021
|
-
raise MuError, "Failed to determine what cloud #{self} should be in!"
|
|
1022
|
-
end
|
|
1023
|
-
|
|
1024
|
-
@environment = @config['environment']
|
|
1025
|
-
if @deploy
|
|
1026
|
-
@deploy_id = @deploy.deploy_id
|
|
1027
|
-
@appname = @deploy.appname
|
|
1028
|
-
end
|
|
1029
|
-
|
|
1030
|
-
@cloudclass = MU::Cloud.loadCloudType(@cloud, self.class.shortname)
|
|
1031
|
-
@cloudparentclass = Object.const_get("MU").const_get("Cloud").const_get(@cloud)
|
|
1032
|
-
|
|
1033
|
-
# A pre-existing object, you say?
|
|
1034
|
-
if args[:cloud_id]
|
|
1035
|
-
|
|
1036
|
-
# TODO implement ::Id for every cloud... and they should know how to get from
|
|
1037
|
-
# cloud_desc to a fully-resolved ::Id object, not just the short string
|
|
1038
|
-
|
|
1039
|
-
@cloud_id = args[:cloud_id]
|
|
1040
|
-
describe(cloud_id: @cloud_id)
|
|
1041
|
-
@habitat_id = habitat_id # effectively, cache this
|
|
1042
|
-
|
|
1043
|
-
# If we can build us an ::Id object for @cloud_id instead of a
|
|
1044
|
-
# string, do so.
|
|
1045
|
-
begin
|
|
1046
|
-
idclass = Object.const_get("MU").const_get("Cloud").const_get(@cloud).const_get("Id")
|
|
1047
|
-
long_id = if @deploydata and @deploydata[idclass.idattr.to_s]
|
|
1048
|
-
@deploydata[idclass.idattr.to_s]
|
|
1049
|
-
elsif self.respond_to?(idclass.idattr)
|
|
1050
|
-
self.send(idclass.idattr)
|
|
1051
|
-
end
|
|
1052
|
-
|
|
1053
|
-
@cloud_id = idclass.new(long_id) if !long_id.nil? and !long_id.empty?
|
|
1054
|
-
# 1 see if we have the value on the object directly or in deploy data
|
|
1055
|
-
# 2 set an attr_reader with the value
|
|
1056
|
-
# 3 rewrite our @cloud_id attribute with a ::Id object
|
|
1057
|
-
rescue NameError, MU::Cloud::MuCloudResourceNotImplemented
|
|
1058
|
-
end
|
|
1059
|
-
|
|
1060
|
-
end
|
|
1061
|
-
|
|
1062
|
-
# Use pre-existing mu_name (we're probably loading an extant deploy)
|
|
1063
|
-
# if available
|
|
1064
|
-
if args[:mu_name]
|
|
1065
|
-
@mu_name = args[:mu_name].dup
|
|
1066
|
-
# If scrub_mu_isms is set, our mu_name is always just the bare name
|
|
1067
|
-
# field of the resource.
|
|
1068
|
-
elsif @config['scrub_mu_isms']
|
|
1069
|
-
@mu_name = @config['name'].dup
|
|
1070
|
-
# XXX feck it insert an inheritable method right here? Set a default? How should resource implementations determine whether they're instantiating a new object?
|
|
1071
|
-
end
|
|
1072
|
-
|
|
1073
|
-
@tags = {}
|
|
1074
|
-
if !@config['scrub_mu_isms']
|
|
1075
|
-
@tags = @deploy ? @deploy.listStandardTags : MU::MommaCat.listStandardTags
|
|
1076
|
-
end
|
|
1077
|
-
if @config['tags']
|
|
1078
|
-
@config['tags'].each { |tag|
|
|
1079
|
-
@tags[tag['key']] = tag['value']
|
|
1080
|
-
}
|
|
1081
|
-
end
|
|
1082
|
-
|
|
1083
|
-
if @cloudparentclass.respond_to?(:resourceInitHook)
|
|
1084
|
-
@cloudparentclass.resourceInitHook(self, @deploy)
|
|
1085
|
-
end
|
|
1086
|
-
|
|
1087
|
-
# Add cloud-specific instance methods for our resource objects to
|
|
1088
|
-
# inherit.
|
|
1089
|
-
if @cloudparentclass.const_defined?(:AdditionalResourceMethods)
|
|
1090
|
-
self.extend @cloudparentclass.const_get(:AdditionalResourceMethods)
|
|
1091
|
-
end
|
|
1092
|
-
|
|
1093
|
-
if ["Server", "ServerPool"].include?(self.class.shortname) and @deploy
|
|
1094
|
-
@mu_name ||= @deploy.getResourceName(@config['name'], need_unique_string: @config.has_key?("basis"))
|
|
1095
|
-
if self.class.shortname == "Server"
|
|
1096
|
-
@groomer = MU::Groomer.new(self)
|
|
1097
|
-
end
|
|
1098
|
-
|
|
1099
|
-
@groomclass = MU::Groomer.loadGroomer(@config["groomer"])
|
|
1100
|
-
|
|
1101
|
-
if windows? or @config['active_directory'] and !@mu_windows_name
|
|
1102
|
-
if !@deploydata.nil? and !@deploydata['mu_windows_name'].nil?
|
|
1103
|
-
@mu_windows_name = @deploydata['mu_windows_name']
|
|
1104
|
-
else
|
|
1105
|
-
# Use the same random differentiator as the "real" name if we're
|
|
1106
|
-
# from a ServerPool. Helpful for admin sanity.
|
|
1107
|
-
unq = @mu_name.sub(/^.*?-(...)$/, '\1')
|
|
1108
|
-
if @config['basis'] and !unq.nil? and !unq.empty?
|
|
1109
|
-
@mu_windows_name = @deploy.getResourceName(@config['name'], max_length: 15, need_unique_string: true, use_unique_string: unq, reuse_unique_string: true)
|
|
1110
|
-
else
|
|
1111
|
-
@mu_windows_name = @deploy.getResourceName(@config['name'], max_length: 15, need_unique_string: true)
|
|
1112
|
-
end
|
|
1113
|
-
end
|
|
1114
|
-
end
|
|
1115
|
-
class << self
|
|
1116
|
-
attr_reader :groomer
|
|
1117
|
-
attr_reader :groomerclass
|
|
1118
|
-
attr_accessor :mu_windows_name # XXX might be ok as reader now
|
|
1119
|
-
end
|
|
1120
|
-
end
|
|
1121
|
-
end
|
|
1122
|
-
|
|
1123
|
-
end
|
|
1124
|
-
|
|
1125
|
-
def cloud
|
|
1126
|
-
if @cloud
|
|
1127
|
-
@cloud
|
|
1128
|
-
elsif @config and @config['cloud']
|
|
1129
|
-
@config['cloud']
|
|
1130
|
-
elsif self.class.name.match(/^MU::Cloud::([^:]+)::.+/)
|
|
1131
|
-
cloudclass_name = Regexp.last_match[1]
|
|
1132
|
-
if MU::Cloud.supportedClouds.include?(cloudclass_name)
|
|
1133
|
-
cloudclass_name
|
|
1134
|
-
else
|
|
1135
|
-
nil
|
|
1136
|
-
end
|
|
1137
|
-
else
|
|
1138
|
-
nil
|
|
1139
|
-
end
|
|
1140
|
-
end
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
# Remove all metadata and cloud resources associated with this object
|
|
1144
|
-
def destroy
|
|
1145
|
-
if self.class.cfg_name == "server"
|
|
1146
|
-
begin
|
|
1147
|
-
ip = canonicalIP
|
|
1148
|
-
MU::MommaCat.removeIPFromSSHKnownHosts(ip) if ip
|
|
1149
|
-
if @deploy and @deploy.deployment and
|
|
1150
|
-
@deploy.deployment['servers'] and @config['name'] and
|
|
1151
|
-
me = @deploy.deployment['servers'][@config['name']][@mu_name]
|
|
1152
|
-
if me
|
|
1153
|
-
["private_ip_address", "public_ip_address"].each { |field|
|
|
1154
|
-
if me[field]
|
|
1155
|
-
MU::MommaCat.removeIPFromSSHKnownHosts(me[field])
|
|
1156
|
-
end
|
|
1157
|
-
}
|
|
1158
|
-
if me["private_ip_list"]
|
|
1159
|
-
me["private_ip_list"].each { |ip|
|
|
1160
|
-
MU::MommaCat.removeIPFromSSHKnownHosts(ip)
|
|
1161
|
-
}
|
|
1162
|
-
end
|
|
1163
|
-
end
|
|
1164
|
-
end
|
|
1165
|
-
rescue MU::MuError => e
|
|
1166
|
-
MU.log e.message, MU::WARN
|
|
1167
|
-
end
|
|
1168
|
-
end
|
|
1169
|
-
if !@cloudobj.nil? and !@cloudobj.groomer.nil?
|
|
1170
|
-
@cloudobj.groomer.cleanup
|
|
1171
|
-
elsif !@groomer.nil?
|
|
1172
|
-
@groomer.cleanup
|
|
1173
|
-
end
|
|
1174
|
-
if !@deploy.nil?
|
|
1175
|
-
if !@cloudobj.nil? and !@config.nil? and !@cloudobj.mu_name.nil?
|
|
1176
|
-
@deploy.notify(self.class.cfg_plural, @config['name'], nil, mu_name: @cloudobj.mu_name, remove: true, triggering_node: @cloudobj, delayed_save: @delayed_save)
|
|
1177
|
-
elsif !@mu_name.nil?
|
|
1178
|
-
@deploy.notify(self.class.cfg_plural, @config['name'], nil, mu_name: @mu_name, remove: true, triggering_node: self, delayed_save: @delayed_save)
|
|
1179
|
-
end
|
|
1180
|
-
@deploy.removeKitten(self)
|
|
1181
|
-
end
|
|
1182
|
-
# Make sure that if notify gets called again it won't go returning a
|
|
1183
|
-
# bunch of now-bogus metadata.
|
|
1184
|
-
@destroyed = true
|
|
1185
|
-
if !@cloudobj.nil?
|
|
1186
|
-
def @cloudobj.notify
|
|
1187
|
-
{}
|
|
1188
|
-
end
|
|
1189
|
-
else
|
|
1190
|
-
def notify
|
|
1191
|
-
{}
|
|
1192
|
-
end
|
|
1193
|
-
end
|
|
1194
|
-
end
|
|
1195
|
-
|
|
1196
|
-
# Return the cloud object's idea of where it lives (project, account,
|
|
1197
|
-
# etc) in the form of an identifier. If not applicable for this object,
|
|
1198
|
-
# we expect to return +nil+.
|
|
1199
|
-
# @return [String,nil]
|
|
1200
|
-
def habitat(nolookup: true)
|
|
1201
|
-
return nil if ["folder", "habitat"].include?(self.class.cfg_name)
|
|
1202
|
-
if @cloudobj
|
|
1203
|
-
@cloudparentclass.habitat(@cloudobj, nolookup: nolookup, deploy: @deploy)
|
|
1204
|
-
else
|
|
1205
|
-
@cloudparentclass.habitat(self, nolookup: nolookup, deploy: @deploy)
|
|
1206
|
-
end
|
|
1207
|
-
end
|
|
1208
|
-
|
|
1209
|
-
def habitat_id(nolookup: false)
|
|
1210
|
-
@habitat_id ||= habitat(nolookup: nolookup)
|
|
1211
|
-
@habitat_id
|
|
1212
|
-
end
|
|
1213
|
-
|
|
1214
|
-
# We're fundamentally a wrapper class, so go ahead and reroute requests
|
|
1215
|
-
# that are meant for our wrapped object.
|
|
1216
|
-
def method_missing(method_sym, *arguments)
|
|
1217
|
-
if @cloudobj
|
|
1218
|
-
MU.log "INVOKING #{method_sym.to_s} FROM PARENT CLOUD OBJECT #{self}", MU::DEBUG, details: arguments
|
|
1219
|
-
@cloudobj.method(method_sym).call(*arguments)
|
|
1220
|
-
else
|
|
1221
|
-
raise NoMethodError, "No such instance method #{method_sym.to_s} available on #{self.class.name}"
|
|
1222
|
-
end
|
|
1223
|
-
end
|
|
1224
|
-
|
|
1225
|
-
# Merge the passed hash into the existing configuration hash of this
|
|
1226
|
-
# cloud object. Currently this is only used by the {MU::Adoption}
|
|
1227
|
-
# module. I don't love exposing this to the whole internal API, but I'm
|
|
1228
|
-
# probably overthinking that.
|
|
1229
|
-
# @param newcfg [Hash]
|
|
1230
|
-
def config!(newcfg)
|
|
1231
|
-
@config.merge!(newcfg)
|
|
1232
|
-
end
|
|
1233
|
-
|
|
1234
|
-
def cloud_desc(use_cache: true)
|
|
1235
|
-
describe
|
|
1236
|
-
|
|
1237
|
-
if !@cloudobj.nil?
|
|
1238
|
-
if @cloudobj.class.instance_methods(false).include?(:cloud_desc)
|
|
1239
|
-
@cloud_desc_cache ||= @cloudobj.cloud_desc
|
|
1240
|
-
end
|
|
1241
|
-
end
|
|
1242
|
-
if !@config.nil? and !@cloud_id.nil? and (!use_cache or @cloud_desc_cache.nil?)
|
|
1243
|
-
# The find() method should be returning a Hash with the cloud_id
|
|
1244
|
-
# as a key and a cloud platform descriptor as the value.
|
|
1245
|
-
begin
|
|
1246
|
-
args = {
|
|
1247
|
-
:region => @config['region'],
|
|
1248
|
-
:cloud => @config['cloud'],
|
|
1249
|
-
:cloud_id => @cloud_id,
|
|
1250
|
-
:credentials => @credentials,
|
|
1251
|
-
:project => habitat_id, # XXX this belongs in our required_instance_methods hack
|
|
1252
|
-
:flags => @config
|
|
1253
|
-
}
|
|
1254
|
-
@cloudparentclass.required_instance_methods.each { |m|
|
|
1255
|
-
# if respond_to?(m)
|
|
1256
|
-
# args[m] = method(m).call
|
|
1257
|
-
# else
|
|
1258
|
-
args[m] = instance_variable_get(("@"+m.to_s).to_sym)
|
|
1259
|
-
# end
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
matches = self.class.find(args)
|
|
1263
|
-
if !matches.nil? and matches.is_a?(Hash)
|
|
1264
|
-
# XXX or if the hash is keyed with an ::Id element, oh boy
|
|
1265
|
-
# puts matches[@cloud_id][:self_link]
|
|
1266
|
-
# puts matches[@cloud_id][:url]
|
|
1267
|
-
# if matches[@cloud_id][:self_link]
|
|
1268
|
-
# @url ||= matches[@cloud_id][:self_link]
|
|
1269
|
-
# elsif matches[@cloud_id][:url]
|
|
1270
|
-
# @url ||= matches[@cloud_id][:url]
|
|
1271
|
-
# elsif matches[@cloud_id][:arn]
|
|
1272
|
-
# @arn ||= matches[@cloud_id][:arn]
|
|
1273
|
-
# end
|
|
1274
|
-
if matches[@cloud_id]
|
|
1275
|
-
@cloud_desc_cache = matches[@cloud_id]
|
|
1276
|
-
else
|
|
1277
|
-
matches.each_pair { |k, v| # flatten out ::Id objects just in case
|
|
1278
|
-
if @cloud_id.to_s == k.to_s
|
|
1279
|
-
@cloud_desc_cache = v
|
|
1280
|
-
break
|
|
1281
|
-
end
|
|
1282
|
-
}
|
|
1283
|
-
end
|
|
1284
|
-
end
|
|
1285
|
-
|
|
1286
|
-
if !@cloud_desc_cache
|
|
1287
|
-
MU.log "cloud_desc via #{self.class.name}.find() failed to locate a live object.\nWas called by #{caller[0]}", MU::WARN, details: args
|
|
1288
|
-
end
|
|
1289
|
-
rescue Exception => e
|
|
1290
|
-
MU.log "Got #{e.inspect} trying to find cloud handle for #{self.class.shortname} #{@mu_name} (#{@cloud_id})", MU::WARN
|
|
1291
|
-
raise e
|
|
1292
|
-
end
|
|
1293
|
-
end
|
|
1294
|
-
|
|
1295
|
-
return @cloud_desc_cache
|
|
1296
|
-
end
|
|
1297
|
-
|
|
1298
|
-
# Retrieve all of the known metadata for this resource.
|
|
1299
|
-
# @param cloud_id [String]: The cloud platform's identifier for the resource we're describing. Makes lookups more efficient.
|
|
1300
|
-
# @param update_cache [Boolean]: Ignore cached data if we have any, instead reconsituting from original sources.
|
|
1301
|
-
# @return [Array<Hash>]: mu_name, config, deploydata
|
|
1302
|
-
def describe(cloud_id: nil, update_cache: false)
|
|
1303
|
-
if cloud_id.nil? and !@cloudobj.nil?
|
|
1304
|
-
@cloud_id ||= @cloudobj.cloud_id
|
|
1305
|
-
end
|
|
1306
|
-
res_type = self.class.cfg_plural
|
|
1307
|
-
res_name = @config['name'] if !@config.nil?
|
|
1308
|
-
@credentials ||= @config['credentials'] if !@config.nil?
|
|
1309
|
-
deploydata = nil
|
|
1310
|
-
if !@deploy.nil? and @deploy.is_a?(MU::MommaCat) and
|
|
1311
|
-
!@deploy.deployment.nil? and
|
|
1312
|
-
!@deploy.deployment[res_type].nil? and
|
|
1313
|
-
!@deploy.deployment[res_type][res_name].nil?
|
|
1314
|
-
deploydata = @deploy.deployment[res_type][res_name]
|
|
1315
|
-
else
|
|
1316
|
-
# XXX This should only happen on a brand new resource, but we should
|
|
1317
|
-
# probably complain under other circumstances, if we can
|
|
1318
|
-
# differentiate them.
|
|
1319
|
-
end
|
|
1320
|
-
|
|
1321
|
-
if self.class.has_multiples and !@mu_name.nil? and deploydata.is_a?(Hash) and deploydata.has_key?(@mu_name)
|
|
1322
|
-
@deploydata = deploydata[@mu_name]
|
|
1323
|
-
elsif deploydata.is_a?(Hash)
|
|
1324
|
-
@deploydata = deploydata
|
|
1325
|
-
end
|
|
1326
|
-
|
|
1327
|
-
if @cloud_id.nil? and @deploydata.is_a?(Hash)
|
|
1328
|
-
if @mu_name.nil? and @deploydata.has_key?('#MU_NAME')
|
|
1329
|
-
@mu_name = @deploydata['#MU_NAME']
|
|
1330
|
-
end
|
|
1331
|
-
if @deploydata.has_key?('cloud_id')
|
|
1332
|
-
@cloud_id ||= @deploydata['cloud_id']
|
|
1333
|
-
end
|
|
1334
|
-
end
|
|
1335
|
-
|
|
1336
|
-
return [@mu_name, @config, @deploydata]
|
|
1337
|
-
end
|
|
1338
|
-
|
|
1339
|
-
# Fetch MU::Cloud objects for each of this object's dependencies, and
|
|
1340
|
-
# return in an easily-navigable Hash. This can include things listed in
|
|
1341
|
-
# @config['dependencies'], implicitly-defined dependencies such as
|
|
1342
|
-
# add_firewall_rules or vpc stanzas, and may refer to objects internal
|
|
1343
|
-
# to this deployment or external. Will populate the instance variables
|
|
1344
|
-
# @dependencies (general dependencies, which can only be sibling
|
|
1345
|
-
# resources in this deployment), as well as for certain config stanzas
|
|
1346
|
-
# which can refer to external resources (@vpc, @loadbalancers,
|
|
1347
|
-
# @add_firewall_rules)
|
|
1348
|
-
def dependencies(use_cache: false, debug: false)
|
|
1349
|
-
@dependencies = {} if @dependencies.nil?
|
|
1350
|
-
@loadbalancers = [] if @loadbalancers.nil?
|
|
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
|
-
return [@dependencies, @vpc, @loadbalancers]
|
|
1566
|
-
end
|
|
1567
|
-
|
|
1568
|
-
# Defaults any resources that don't declare their release-readiness to
|
|
1569
|
-
# ALPHA. That'll learn 'em.
|
|
1570
|
-
def self.quality
|
|
1571
|
-
MU::Cloud::ALPHA
|
|
1572
|
-
end
|
|
1573
|
-
|
|
1574
|
-
# Return a list of "container" artifacts, by class, that apply to this
|
|
1575
|
-
# resource type in a cloud provider. This is so methods that call find
|
|
1576
|
-
# know whether to call +find+ with identifiers for parent resources.
|
|
1577
|
-
# This is similar in purpose to the +isGlobal?+ resource class method,
|
|
1578
|
-
# which tells our search functions whether or not a resource scopes to
|
|
1579
|
-
# a region. In almost all cases this is one-entry list consisting of
|
|
1580
|
-
# +:Habitat+. Notable exceptions include most implementations of
|
|
1581
|
-
# +Habitat+, which either reside inside a +:Folder+ or nothing at all;
|
|
1582
|
-
# whereas a +:Folder+ tends to not have any containing parent. Very few
|
|
1583
|
-
# resource implementations will need to override this.
|
|
1584
|
-
# A +nil+ entry in this list is interpreted as "this resource can be
|
|
1585
|
-
# global."
|
|
1586
|
-
# @return [Array<Symbol,nil>]
|
|
1587
|
-
def self.canLiveIn
|
|
1588
|
-
if self.shortname == "Folder"
|
|
1589
|
-
[nil, :Folder]
|
|
1590
|
-
elsif self.shortname == "Habitat"
|
|
1591
|
-
[:Folder]
|
|
1592
|
-
else
|
|
1593
|
-
[:Habitat]
|
|
1594
|
-
end
|
|
1595
|
-
end
|
|
1596
|
-
|
|
1597
|
-
def self.find(*flags)
|
|
1598
|
-
allfound = {}
|
|
1599
|
-
|
|
1600
|
-
MU::Cloud.availableClouds.each { |cloud|
|
|
1601
|
-
begin
|
|
1602
|
-
args = flags.first
|
|
1603
|
-
next if args[:cloud] and args[:cloud] != cloud
|
|
1604
|
-
# skip this cloud if we have a region argument that makes no
|
|
1605
|
-
# sense there
|
|
1606
|
-
cloudbase = Object.const_get("MU").const_get("Cloud").const_get(cloud)
|
|
1607
|
-
next if cloudbase.listCredentials.nil? or cloudbase.listCredentials.empty? or cloudbase.credConfig(args[:credentials]).nil?
|
|
1608
|
-
if args[:region] and cloudbase.respond_to?(:listRegions)
|
|
1609
|
-
if !cloudbase.listRegions(credentials: args[:credentials])
|
|
1610
|
-
MU.log "Failed to get region list for credentials #{args[:credentials]} in cloud #{cloud}", MU::ERR, details: caller
|
|
1611
|
-
else
|
|
1612
|
-
next if !cloudbase.listRegions(credentials: args[:credentials]).include?(args[:region])
|
|
1613
|
-
end
|
|
1614
|
-
end
|
|
1615
|
-
begin
|
|
1616
|
-
cloudclass = MU::Cloud.loadCloudType(cloud, shortname)
|
|
1617
|
-
rescue MU::MuError => e
|
|
1618
|
-
next
|
|
1619
|
-
end
|
|
1620
|
-
|
|
1621
|
-
found = cloudclass.find(args)
|
|
1622
|
-
if !found.nil?
|
|
1623
|
-
if found.is_a?(Hash)
|
|
1624
|
-
allfound.merge!(found)
|
|
1625
|
-
else
|
|
1626
|
-
raise MuError, "#{cloudclass}.find returned a non-Hash result"
|
|
1627
|
-
end
|
|
1628
|
-
end
|
|
1629
|
-
rescue MuCloudResourceNotImplemented
|
|
1630
|
-
end
|
|
1631
|
-
}
|
|
1632
|
-
allfound
|
|
1633
|
-
end
|
|
1634
|
-
|
|
1635
|
-
if shortname == "DNSZone"
|
|
1636
|
-
def self.genericMuDNSEntry(*flags)
|
|
1637
|
-
# XXX have this switch on a global config for where Mu puts its DNS
|
|
1638
|
-
cloudclass = MU::Cloud.loadCloudType(MU::Config.defaultCloud, "DNSZone")
|
|
1639
|
-
cloudclass.genericMuDNSEntry(flags.first)
|
|
1640
|
-
end
|
|
1641
|
-
def self.createRecordsFromConfig(*flags)
|
|
1642
|
-
cloudclass = MU::Cloud.loadCloudType(MU::Config.defaultCloud, "DNSZone")
|
|
1643
|
-
if !flags.nil? and flags.size == 1
|
|
1644
|
-
cloudclass.createRecordsFromConfig(flags.first)
|
|
1645
|
-
else
|
|
1646
|
-
cloudclass.createRecordsFromConfig(*flags)
|
|
1647
|
-
end
|
|
1648
|
-
end
|
|
1649
|
-
end
|
|
1650
|
-
|
|
1651
|
-
if shortname == "Server" or shortname == "ServerPool"
|
|
1652
|
-
def windows?
|
|
1653
|
-
return true if %w{win2k16 win2k12r2 win2k12 win2k8 win2k8r2 win2k19 windows}.include?(@config['platform'])
|
|
1654
|
-
begin
|
|
1655
|
-
return true if cloud_desc.respond_to?(:platform) and cloud_desc.platform == "Windows"
|
|
1656
|
-
# 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?
|
|
1657
|
-
rescue MU::MuError
|
|
1658
|
-
return false
|
|
1659
|
-
end
|
|
1660
|
-
false
|
|
1661
|
-
end
|
|
1662
|
-
|
|
1663
|
-
# Gracefully message and attempt to accommodate the common transient errors peculiar to Windows nodes
|
|
1664
|
-
# @param e [Exception]: The exception that we're handling
|
|
1665
|
-
# @param retries [Integer]: The current number of retries, which we'll increment and pass back to the caller
|
|
1666
|
-
# @param rebootable_fails [Integer]: The current number of reboot-worthy failures, which we'll increment and pass back to the caller
|
|
1667
|
-
# @param max_retries [Integer]: Maximum number of retries to attempt; we'll raise an exception if this is exceeded
|
|
1668
|
-
# @param reboot_on_problems [Boolean]: Whether we should try to reboot a "stuck" machine
|
|
1669
|
-
# @param retry_interval [Integer]: How many seconds to wait before returning for another attempt
|
|
1670
|
-
def handleWindowsFail(e, retries, rebootable_fails, max_retries: 30, reboot_on_problems: false, retry_interval: 45)
|
|
1671
|
-
msg = "WinRM connection to https://"+@mu_name+":5986/wsman: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})"
|
|
1672
|
-
if e.class.name == "WinRM::WinRMAuthorizationError" or e.message.match(/execution expired/) and reboot_on_problems
|
|
1673
|
-
if rebootable_fails > 0 and (rebootable_fails % 5) == 0
|
|
1674
|
-
MU.log "#{@mu_name} still misbehaving, forcing Stop and Start from API", MU::WARN
|
|
1675
|
-
reboot(true) # vicious API stop/start
|
|
1676
|
-
sleep retry_interval*3
|
|
1677
|
-
rebootable_fails = 0
|
|
1678
|
-
else
|
|
1679
|
-
if rebootable_fails == 3
|
|
1680
|
-
MU.log "#{@mu_name} misbehaving, attempting to reboot from API", MU::WARN
|
|
1681
|
-
reboot # graceful API restart
|
|
1682
|
-
sleep retry_interval*2
|
|
1683
|
-
end
|
|
1684
|
-
rebootable_fails = rebootable_fails + 1
|
|
1685
|
-
end
|
|
1686
|
-
end
|
|
1687
|
-
if retries < max_retries
|
|
1688
|
-
if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0 and retries != 0)
|
|
1689
|
-
MU.log msg, MU::NOTICE
|
|
1690
|
-
elsif retries/max_retries > 0.5
|
|
1691
|
-
MU.log msg, MU::WARN, details: e.inspect
|
|
1692
|
-
end
|
|
1693
|
-
sleep retry_interval
|
|
1694
|
-
retries = retries + 1
|
|
1695
|
-
else
|
|
1696
|
-
raise MuError, "#{@mu_name}: #{e.inspect} trying to connect with WinRM, max_retries exceeded", e.backtrace
|
|
1697
|
-
end
|
|
1698
|
-
return [retries, rebootable_fails]
|
|
1699
|
-
end
|
|
1700
|
-
|
|
1701
|
-
def windowsRebootPending?(shell = nil)
|
|
1702
|
-
if shell.nil?
|
|
1703
|
-
shell = getWinRMSession(1, 30)
|
|
1704
|
-
end
|
|
1705
|
-
# if (Get-Item "HKLM:/SOFTWARE/Microsoft/Windows/CurrentVersion/WindowsUpdate/Auto Update/RebootRequired" -EA Ignore) { exit 1 }
|
|
1706
|
-
cmd = %Q{
|
|
1707
|
-
if (Get-ChildItem "HKLM:/Software/Microsoft/Windows/CurrentVersion/Component Based Servicing/RebootPending" -EA Ignore) {
|
|
1708
|
-
echo "Component Based Servicing/RebootPending is true"
|
|
1709
|
-
exit 1
|
|
1710
|
-
}
|
|
1711
|
-
if (Get-ItemProperty "HKLM:/SYSTEM/CurrentControlSet/Control/Session Manager" -Name PendingFileRenameOperations -EA Ignore) {
|
|
1712
|
-
echo "Control/Session Manager/PendingFileRenameOperations is true"
|
|
1713
|
-
exit 1
|
|
1714
|
-
}
|
|
1715
|
-
try {
|
|
1716
|
-
$util = [wmiclass]"\\\\.\\root\\ccm\\clientsdk:CCM_ClientUtilities"
|
|
1717
|
-
$status = $util.DetermineIfRebootPending()
|
|
1718
|
-
if(($status -ne $null) -and $status.RebootPending){
|
|
1719
|
-
echo "WMI says RebootPending is true"
|
|
1720
|
-
exit 1
|
|
1721
|
-
}
|
|
1722
|
-
} catch {
|
|
1723
|
-
exit 0
|
|
1724
|
-
}
|
|
1725
|
-
exit 0
|
|
1726
|
-
}
|
|
1727
|
-
resp = shell.run(cmd)
|
|
1728
|
-
returnval = resp.exitcode == 0 ? false : true
|
|
1729
|
-
shell.close
|
|
1730
|
-
returnval
|
|
1731
|
-
end
|
|
1732
|
-
|
|
1733
|
-
# Basic setup tasks performed on a new node during its first WinRM
|
|
1734
|
-
# connection. Most of this is terrible Windows glue.
|
|
1735
|
-
# @param shell [WinRM::Shells::Powershell]: An active Powershell session to the new node.
|
|
1736
|
-
def initialWinRMTasks(shell)
|
|
1737
|
-
retries = 0
|
|
1738
|
-
rebootable_fails = 0
|
|
1739
|
-
begin
|
|
1740
|
-
if !@config['use_cloud_provider_windows_password']
|
|
1741
|
-
pw = @groomer.getSecret(
|
|
1742
|
-
vault: @config['mu_name'],
|
|
1743
|
-
item: "windows_credentials",
|
|
1744
|
-
field: "password"
|
|
1745
|
-
)
|
|
1746
|
-
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}
|
|
1747
|
-
resp = shell.run(win_check_for_pw)
|
|
1748
|
-
if resp.stdout.chomp != "True"
|
|
1749
|
-
win_set_pw = %Q{(([adsi]('WinNT://./#{@config["windows_admin_username"]}, user')).psbase.invoke('SetPassword', '#{pw}'))}
|
|
1750
|
-
resp = shell.run(win_set_pw)
|
|
1751
|
-
puts resp.stdout
|
|
1752
|
-
MU.log "Resetting Windows host password", MU::NOTICE, details: resp.stdout
|
|
1753
|
-
end
|
|
1754
|
-
end
|
|
1755
|
-
|
|
1756
|
-
# Install Cygwin here, because for some reason it breaks inside Chef
|
|
1757
|
-
# XXX would love to not do this here
|
|
1758
|
-
pkgs = ["bash", "mintty", "vim", "curl", "openssl", "wget", "lynx", "openssh"]
|
|
1759
|
-
admin_home = "c:/bin/cygwin/home/#{@config["windows_admin_username"]}"
|
|
1760
|
-
install_cygwin = %Q{
|
|
1761
|
-
If (!(Test-Path "c:/bin/cygwin/Cygwin.bat")){
|
|
1762
|
-
$WebClient = New-Object System.Net.WebClient
|
|
1763
|
-
$WebClient.DownloadFile("http://cygwin.com/setup-x86_64.exe","$env:Temp/setup-x86_64.exe")
|
|
1764
|
-
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(',')}"
|
|
1765
|
-
}
|
|
1766
|
-
if(!(Test-Path #{admin_home})){
|
|
1767
|
-
New-Item -type directory -path #{admin_home}
|
|
1768
|
-
}
|
|
1769
|
-
if(!(Test-Path #{admin_home}/.ssh)){
|
|
1770
|
-
New-Item -type directory -path #{admin_home}/.ssh
|
|
1771
|
-
}
|
|
1772
|
-
if(!(Test-Path #{admin_home}/.ssh/authorized_keys)){
|
|
1773
|
-
New-Item #{admin_home}/.ssh/authorized_keys -type file -force -value "#{@deploy.ssh_public_key}"
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
|
-
resp = shell.run(install_cygwin)
|
|
1777
|
-
if resp.exitcode != 0
|
|
1778
|
-
MU.log "Failed at installing Cygwin", MU::ERR, details: resp
|
|
1779
|
-
end
|
|
1780
|
-
|
|
1781
|
-
set_hostname = true
|
|
1782
|
-
hostname = nil
|
|
1783
|
-
if !@config['active_directory'].nil?
|
|
1784
|
-
if @config['active_directory']['node_type'] == "domain_controller" && @config['active_directory']['domain_controller_hostname']
|
|
1785
|
-
hostname = @config['active_directory']['domain_controller_hostname']
|
|
1786
|
-
@mu_windows_name = hostname
|
|
1787
|
-
set_hostname = true
|
|
1788
|
-
else
|
|
1789
|
-
# Do we have an AD specific hostname?
|
|
1790
|
-
hostname = @mu_windows_name
|
|
1791
|
-
set_hostname = true
|
|
1792
|
-
end
|
|
1793
|
-
else
|
|
1794
|
-
hostname = @mu_windows_name
|
|
1795
|
-
end
|
|
1796
|
-
resp = shell.run(%Q{hostname})
|
|
1797
|
-
|
|
1798
|
-
if resp.stdout.chomp != hostname
|
|
1799
|
-
resp = shell.run(%Q{Rename-Computer -NewName '#{hostname}' -Force -PassThru -Restart; Restart-Computer -Force})
|
|
1800
|
-
MU.log "Renaming Windows host to #{hostname}; this will trigger a reboot", MU::NOTICE, details: resp.stdout
|
|
1801
|
-
reboot(true)
|
|
1802
|
-
sleep 30
|
|
1803
|
-
end
|
|
1804
|
-
rescue WinRM::WinRMError, HTTPClient::ConnectTimeoutError => e
|
|
1805
|
-
retries, rebootable_fails = handleWindowsFail(e, retries, rebootable_fails, max_retries: 10, reboot_on_problems: true, retry_interval: 30)
|
|
1806
|
-
retry
|
|
1807
|
-
end
|
|
1808
|
-
end
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
# Basic setup tasks performed on a new node during its first initial
|
|
1812
|
-
# ssh connection. Most of this is terrible Windows glue.
|
|
1813
|
-
# @param ssh [Net::SSH::Connection::Session]: The active SSH session to the new node.
|
|
1814
|
-
def initialSSHTasks(ssh)
|
|
1815
|
-
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" )}
|
|
1816
|
-
win_installer_check = %q{ls /proc/registry/HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Installer/}
|
|
1817
|
-
lnx_installer_check = %q{ps auxww | awk '{print $11}' | egrep '(/usr/bin/yum|apt-get|dpkg)'}
|
|
1818
|
-
lnx_updates_check = %q{( test -f /.mu-installer-ran-updates || ! test -d /var/lib/cloud/instance ) || echo "userdata still running"}
|
|
1819
|
-
win_set_pw = nil
|
|
1820
|
-
|
|
1821
|
-
if windows? and !@config['use_cloud_provider_windows_password']
|
|
1822
|
-
# 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
|
|
1823
|
-
pw = @groomer.getSecret(
|
|
1824
|
-
vault: @config['mu_name'],
|
|
1825
|
-
item: "windows_credentials",
|
|
1826
|
-
field: "password"
|
|
1827
|
-
)
|
|
1828
|
-
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}'}
|
|
1829
|
-
win_set_pw = %Q{powershell -Command "& {(([adsi]('WinNT://./#{@config["windows_admin_username"]}, user')).psbase.invoke('SetPassword', '#{pw}'))}"}
|
|
1830
|
-
end
|
|
1831
|
-
|
|
1832
|
-
# There shouldn't be a use case where a domain joined computer goes through initialSSHTasks. Removing Active Directory specific computer rename.
|
|
1833
|
-
set_hostname = true
|
|
1834
|
-
hostname = nil
|
|
1835
|
-
if !@config['active_directory'].nil?
|
|
1836
|
-
if @config['active_directory']['node_type'] == "domain_controller" && @config['active_directory']['domain_controller_hostname']
|
|
1837
|
-
hostname = @config['active_directory']['domain_controller_hostname']
|
|
1838
|
-
@mu_windows_name = hostname
|
|
1839
|
-
set_hostname = true
|
|
1840
|
-
else
|
|
1841
|
-
# Do we have an AD specific hostname?
|
|
1842
|
-
hostname = @mu_windows_name
|
|
1843
|
-
set_hostname = true
|
|
1844
|
-
end
|
|
1845
|
-
else
|
|
1846
|
-
hostname = @mu_windows_name
|
|
1847
|
-
end
|
|
1848
|
-
win_check_for_hostname = %Q{powershell -Command '& {hostname}'}
|
|
1849
|
-
win_set_hostname = %Q{powershell -Command "& {Rename-Computer -NewName '#{hostname}' -Force -PassThru -Restart; Restart-Computer -Force }"}
|
|
1850
|
-
|
|
1851
|
-
begin
|
|
1852
|
-
# Set our admin password first, if we need to
|
|
1853
|
-
if windows? and !win_set_pw.nil? and !win_check_for_pw.nil?
|
|
1854
|
-
output = ssh.exec!(win_check_for_pw)
|
|
1855
|
-
raise MU::Cloud::BootstrapTempFail, "Got nil output from ssh session, waiting and retrying" if output.nil?
|
|
1856
|
-
if !output.match(/True/)
|
|
1857
|
-
MU.log "Setting Windows password for user #{@config['windows_admin_username']}", details: ssh.exec!(win_set_pw)
|
|
1858
|
-
end
|
|
1859
|
-
end
|
|
1860
|
-
if windows?
|
|
1861
|
-
output = ssh.exec!(win_env_fix)
|
|
1862
|
-
output = ssh.exec!(win_installer_check)
|
|
1863
|
-
raise MU::Cloud::BootstrapTempFail, "Got nil output from ssh session, waiting and retrying" if output.nil?
|
|
1864
|
-
if output.match(/InProgress/)
|
|
1865
|
-
raise MU::Cloud::BootstrapTempFail, "Windows Installer service is still doing something, need to wait"
|
|
1866
|
-
end
|
|
1867
|
-
if set_hostname and !@hostname_set and @mu_windows_name
|
|
1868
|
-
output = ssh.exec!(win_check_for_hostname)
|
|
1869
|
-
raise MU::Cloud::BootstrapTempFail, "Got nil output from ssh session, waiting and retrying" if output.nil?
|
|
1870
|
-
if !output.match(/#{@mu_windows_name}/)
|
|
1871
|
-
MU.log "Setting Windows hostname to #{@mu_windows_name}", details: ssh.exec!(win_set_hostname)
|
|
1872
|
-
@hostname_set = true
|
|
1873
|
-
# Reboot from the API too, in case Windows is flailing
|
|
1874
|
-
if !@cloudobj.nil?
|
|
1875
|
-
@cloudobj.reboot
|
|
1876
|
-
else
|
|
1877
|
-
reboot
|
|
1878
|
-
end
|
|
1879
|
-
raise MU::Cloud::BootstrapTempFail, "Set hostname in Windows, waiting for reboot"
|
|
1880
|
-
end
|
|
1881
|
-
end
|
|
1882
|
-
else
|
|
1883
|
-
output = ssh.exec!(lnx_installer_check)
|
|
1884
|
-
if !output.nil? and !output.empty?
|
|
1885
|
-
raise MU::Cloud::BootstrapTempFail, "Linux package manager is still doing something, need to wait (#{output})"
|
|
1886
|
-
end
|
|
1887
|
-
if !@config['skipinitialupdates'] and
|
|
1888
|
-
!@config['scrub_mu_isms'] and
|
|
1889
|
-
!@config['userdata_script']
|
|
1890
|
-
output = ssh.exec!(lnx_updates_check)
|
|
1891
|
-
if !output.nil? and output.match(/userdata still running/)
|
|
1892
|
-
raise MU::Cloud::BootstrapTempFail, "Waiting for initial userdata system updates to complete"
|
|
1893
|
-
end
|
|
1894
|
-
end
|
|
1895
|
-
end
|
|
1896
|
-
rescue RuntimeError => e
|
|
1897
|
-
raise MU::Cloud::BootstrapTempFail, "Got #{e.inspect} performing initial SSH connect tasks, will try again"
|
|
1898
|
-
end
|
|
1899
|
-
|
|
1900
|
-
end
|
|
1901
|
-
|
|
1902
|
-
# Get a privileged Powershell session on the server in question, using SSL-encrypted WinRM with certificate authentication.
|
|
1903
|
-
# @param max_retries [Integer]:
|
|
1904
|
-
# @param retry_interval [Integer]:
|
|
1905
|
-
# @param timeout [Integer]:
|
|
1906
|
-
# @param winrm_retries [Integer]:
|
|
1907
|
-
# @param reboot_on_problems [Boolean]:
|
|
1908
|
-
def getWinRMSession(max_retries = 40, retry_interval = 60, timeout: 30, winrm_retries: 5, reboot_on_problems: false)
|
|
1909
|
-
nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = getSSHConfig
|
|
1910
|
-
@mu_name ||= @config['mu_name']
|
|
1911
|
-
|
|
1912
|
-
conn = nil
|
|
1913
|
-
shell = nil
|
|
1914
|
-
opts = nil
|
|
1915
|
-
# and now, a thing I really don't want to do
|
|
1916
|
-
MU::MommaCat.addInstanceToEtcHosts(canonical_ip, @mu_name)
|
|
1917
|
-
|
|
1918
|
-
# catch exceptions that circumvent our regular call stack
|
|
1919
|
-
Thread.abort_on_exception = false
|
|
1920
|
-
Thread.handle_interrupt(WinRM::WinRMWSManFault => :never) {
|
|
1921
|
-
begin
|
|
1922
|
-
Thread.handle_interrupt(WinRM::WinRMWSManFault => :immediate) {
|
|
1923
|
-
MU.log "(Probably harmless) Caught a WinRM::WinRMWSManFault in #{Thread.current.inspect}", MU::DEBUG, details: Thread.current.backtrace
|
|
1924
|
-
}
|
|
1925
|
-
ensure
|
|
1926
|
-
# Reraise something useful
|
|
1927
|
-
end
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
retries = 0
|
|
1931
|
-
rebootable_fails = 0
|
|
1932
|
-
begin
|
|
1933
|
-
MU.log "Calling WinRM on #{@mu_name}", MU::DEBUG, details: opts
|
|
1934
|
-
opts = {
|
|
1935
|
-
endpoint: 'https://'+@mu_name+':5986/wsman',
|
|
1936
|
-
retry_limit: winrm_retries,
|
|
1937
|
-
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
|
|
1938
|
-
ca_trust_path: "#{MU.mySSLDir}/Mu_CA.pem",
|
|
1939
|
-
transport: :ssl,
|
|
1940
|
-
operation_timeout: timeout,
|
|
1941
|
-
client_cert: "#{MU.mySSLDir}/#{@mu_name}-winrm.crt",
|
|
1942
|
-
client_key: "#{MU.mySSLDir}/#{@mu_name}-winrm.key"
|
|
1943
|
-
}
|
|
1944
|
-
conn = WinRM::Connection.new(opts)
|
|
1945
|
-
MU.log "WinRM connection to #{@mu_name} created", MU::DEBUG, details: conn
|
|
1946
|
-
shell = conn.shell(:powershell)
|
|
1947
|
-
shell.run('ipconfig') # verify that we can do something
|
|
1948
|
-
rescue Errno::EHOSTUNREACH, Errno::ECONNREFUSED, HTTPClient::ConnectTimeoutError, OpenSSL::SSL::SSLError, SocketError, WinRM::WinRMError, Timeout::Error => e
|
|
1949
|
-
retries, rebootable_fails = handleWindowsFail(e, retries, rebootable_fails, max_retries: max_retries, reboot_on_problems: reboot_on_problems, retry_interval: retry_interval)
|
|
1950
|
-
retry
|
|
1951
|
-
ensure
|
|
1952
|
-
MU::MommaCat.removeInstanceFromEtcHosts(@mu_name)
|
|
1953
|
-
end
|
|
1954
|
-
|
|
1955
|
-
shell
|
|
1956
|
-
end
|
|
1957
|
-
|
|
1958
|
-
# @param max_retries [Integer]: Number of connection attempts to make before giving up
|
|
1959
|
-
# @param retry_interval [Integer]: Number of seconds to wait between connection attempts
|
|
1960
|
-
# @return [Net::SSH::Connection::Session]
|
|
1961
|
-
def getSSHSession(max_retries = 12, retry_interval = 30)
|
|
1962
|
-
ssh_keydir = Etc.getpwnam(@deploy.mu_user).dir+"/.ssh"
|
|
1963
|
-
nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = getSSHConfig
|
|
1964
|
-
session = nil
|
|
1965
|
-
retries = 0
|
|
1966
|
-
|
|
1967
|
-
vpc_class = Object.const_get("MU").const_get("Cloud").const_get(@cloud).const_get("VPC")
|
|
1968
|
-
|
|
1969
|
-
# XXX WHY is this a thing
|
|
1970
|
-
Thread.handle_interrupt(Errno::ECONNREFUSED => :never) {
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
begin
|
|
1974
|
-
MU::Cloud.handleNetSSHExceptions
|
|
1975
|
-
if !nat_ssh_host.nil?
|
|
1976
|
-
proxy_cmd = "ssh -q -o StrictHostKeyChecking=no -W %h:%p #{nat_ssh_user}@#{nat_ssh_host}"
|
|
1977
|
-
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
|
|
1978
|
-
proxy = Net::SSH::Proxy::Command.new(proxy_cmd)
|
|
1979
|
-
session = Net::SSH.start(
|
|
1980
|
-
canonical_ip,
|
|
1981
|
-
ssh_user,
|
|
1982
|
-
:config => false,
|
|
1983
|
-
:keys_only => true,
|
|
1984
|
-
:keys => [ssh_keydir+"/"+nat_ssh_key, ssh_keydir+"/"+@deploy.ssh_key_name],
|
|
1985
|
-
:verify_host_key => false,
|
|
1986
|
-
# :verbose => :info,
|
|
1987
|
-
:host_key => "ssh-rsa",
|
|
1988
|
-
:port => 22,
|
|
1989
|
-
:auth_methods => ['publickey'],
|
|
1990
|
-
:proxy => proxy
|
|
1991
|
-
)
|
|
1992
|
-
else
|
|
1993
|
-
|
|
1994
|
-
MU.log "Attempting SSH to #{canonical_ip} (#{@mu_name}) as #{ssh_user} with key #{ssh_keydir}/#{@deploy.ssh_key_name}" if retries == 0
|
|
1995
|
-
session = Net::SSH.start(
|
|
1996
|
-
canonical_ip,
|
|
1997
|
-
ssh_user,
|
|
1998
|
-
:config => false,
|
|
1999
|
-
:keys_only => true,
|
|
2000
|
-
:keys => [ssh_keydir+"/"+@deploy.ssh_key_name],
|
|
2001
|
-
:verify_host_key => false,
|
|
2002
|
-
# :verbose => :info,
|
|
2003
|
-
:host_key => "ssh-rsa",
|
|
2004
|
-
:port => 22,
|
|
2005
|
-
:auth_methods => ['publickey']
|
|
2006
|
-
)
|
|
2007
|
-
end
|
|
2008
|
-
retries = 0
|
|
2009
|
-
rescue Net::SSH::HostKeyMismatch => e
|
|
2010
|
-
MU.log("Remembering new key: #{e.fingerprint}")
|
|
2011
|
-
e.remember_host!
|
|
2012
|
-
session.close
|
|
2013
|
-
retry
|
|
2014
|
-
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
|
|
2015
|
-
begin
|
|
2016
|
-
session.close if !session.nil?
|
|
2017
|
-
rescue Net::SSH::Disconnect, IOError => e
|
|
2018
|
-
if windows?
|
|
2019
|
-
MU.log "Windows has probably closed the ssh session before we could. Waiting before trying again", MU::NOTICE
|
|
2020
|
-
else
|
|
2021
|
-
MU.log "ssh session was closed unexpectedly, waiting before trying again", MU::NOTICE
|
|
2022
|
-
end
|
|
2023
|
-
sleep 10
|
|
2024
|
-
end
|
|
2025
|
-
|
|
2026
|
-
if retries < max_retries
|
|
2027
|
-
retries = retries + 1
|
|
2028
|
-
msg = "ssh #{ssh_user}@#{@mu_name}: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})"
|
|
2029
|
-
if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0)
|
|
2030
|
-
MU.log msg, MU::NOTICE
|
|
2031
|
-
if !vpc_class.haveRouteToInstance?(cloud_desc, credentials: @credentials) and
|
|
2032
|
-
canonical_ip.match(/(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1$)|(^[fF][cCdD])/) and
|
|
2033
|
-
!nat_ssh_host
|
|
2034
|
-
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
|
|
2035
|
-
end
|
|
2036
|
-
elsif retries/max_retries > 0.5
|
|
2037
|
-
MU.log msg, MU::WARN, details: e.inspect
|
|
2038
|
-
end
|
|
2039
|
-
sleep retry_interval
|
|
2040
|
-
retry
|
|
2041
|
-
else
|
|
2042
|
-
raise MuError, "#{@mu_name}: #{e.inspect} trying to connect with SSH, max_retries exceeded", e.backtrace
|
|
2043
|
-
end
|
|
2044
|
-
end
|
|
2045
|
-
return session
|
|
2046
|
-
end
|
|
2047
|
-
end
|
|
2048
|
-
|
|
2049
|
-
# Wrapper for the cleanup class method of underlying cloud object implementations.
|
|
2050
|
-
def self.cleanup(*flags)
|
|
2051
|
-
ok = true
|
|
2052
|
-
params = flags.first
|
|
2053
|
-
clouds = MU::Cloud.supportedClouds
|
|
2054
|
-
if params[:cloud]
|
|
2055
|
-
clouds = [params[:cloud]]
|
|
2056
|
-
params.delete(:cloud)
|
|
2057
|
-
end
|
|
2058
|
-
|
|
2059
|
-
clouds.each { |cloud|
|
|
2060
|
-
begin
|
|
2061
|
-
cloudclass = MU::Cloud.loadCloudType(cloud, shortname)
|
|
2062
|
-
raise MuCloudResourceNotImplemented if !cloudclass.respond_to?(:cleanup) or cloudclass.method(:cleanup).owner.to_s != "#<Class:#{cloudclass}>"
|
|
2063
|
-
MU.log "Invoking #{cloudclass}.cleanup from #{shortname}", MU::DEBUG, details: flags
|
|
2064
|
-
cloudclass.cleanup(params)
|
|
2065
|
-
rescue MuCloudResourceNotImplemented
|
|
2066
|
-
MU.log "No #{cloud} implementation of #{shortname}.cleanup, skipping", MU::DEBUG, details: flags
|
|
2067
|
-
rescue Exception => e
|
|
2068
|
-
in_msg = cloud
|
|
2069
|
-
if params and params[:region]
|
|
2070
|
-
in_msg += " "+params[:region]
|
|
2071
|
-
end
|
|
2072
|
-
if params and params[:flags] and params[:flags]["project"]
|
|
2073
|
-
in_msg += " project "+params[:flags]["project"]
|
|
2074
|
-
end
|
|
2075
|
-
MU.log "Skipping #{shortname} cleanup method in #{in_msg} due to exception: #{e.message}", MU::WARN, details: e.backtrace
|
|
2076
|
-
ok = false
|
|
2077
|
-
end
|
|
2078
|
-
}
|
|
2079
|
-
MU::MommaCat.unlockAll
|
|
2080
|
-
|
|
2081
|
-
ok
|
|
2082
|
-
end
|
|
2083
|
-
|
|
2084
|
-
# A hook that is always called just before each instance method is
|
|
2085
|
-
# invoked, so that we can ensure that repetitive setup tasks (like
|
|
2086
|
-
# resolving +:resource_group+ for Azure resources) have always been
|
|
2087
|
-
# done.
|
|
2088
|
-
def resourceInitHook
|
|
2089
|
-
@cloud ||= cloud
|
|
2090
|
-
if @cloudparentclass.respond_to?(:resourceInitHook)
|
|
2091
|
-
@cloudparentclass.resourceInitHook(@cloudobj, @deploy)
|
|
2092
|
-
end
|
|
2093
|
-
end
|
|
2094
|
-
|
|
2095
|
-
# Wrap the instance methods that this cloud resource type has to
|
|
2096
|
-
# implement.
|
|
2097
|
-
MU::Cloud.resource_types[name.to_sym][:instance].each { |method|
|
|
2098
|
-
define_method method do |*args|
|
|
2099
|
-
return nil if @cloudobj.nil?
|
|
2100
|
-
MU.log "Invoking #{@cloudobj}.#{method}", MU::DEBUG
|
|
2101
|
-
|
|
2102
|
-
# Go ahead and guarantee that we can't accidentally trigger these
|
|
2103
|
-
# methods recursively.
|
|
2104
|
-
@method_semaphore.synchronize {
|
|
2105
|
-
# We're looking for recursion, not contention, so ignore some
|
|
2106
|
-
# obviously harmless things.
|
|
2107
|
-
if @method_locks.has_key?(method) and method != :findBastion and method != :cloud_id
|
|
2108
|
-
MU.log "Double-call to cloud method #{method} for #{self}", MU::DEBUG, details: caller + ["competing call stack:"] + @method_locks[method]
|
|
2109
|
-
end
|
|
2110
|
-
@method_locks[method] = caller
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
# Make sure the describe() caches are fresh
|
|
2114
|
-
@cloudobj.describe if method != :describe
|
|
2115
|
-
|
|
2116
|
-
# Don't run through dependencies on simple attr_reader lookups
|
|
2117
|
-
if ![:dependencies, :cloud_id, :config, :mu_name].include?(method)
|
|
2118
|
-
@cloudobj.dependencies
|
|
2119
|
-
end
|
|
2120
|
-
|
|
2121
|
-
retval = nil
|
|
2122
|
-
if !args.nil? and args.size == 1
|
|
2123
|
-
retval = @cloudobj.method(method).call(args.first)
|
|
2124
|
-
elsif !args.nil? and args.size > 0
|
|
2125
|
-
retval = @cloudobj.method(method).call(*args)
|
|
2126
|
-
else
|
|
2127
|
-
retval = @cloudobj.method(method).call
|
|
2128
|
-
end
|
|
2129
|
-
if (method == :create or method == :groom or method == :postBoot) and
|
|
2130
|
-
(!@destroyed and !@cloudobj.destroyed)
|
|
2131
|
-
deploydata = @cloudobj.method(:notify).call
|
|
2132
|
-
@deploydata ||= deploydata # XXX I don't remember why we're not just doing this from the get-go; maybe because we prefer some mangling occurring in @deploy.notify?
|
|
2133
|
-
if deploydata.nil? or !deploydata.is_a?(Hash)
|
|
2134
|
-
MU.log "#{self} notify method did not return a Hash of deployment data, attempting to fill in with cloud descriptor #{@cloudobj.cloud_id}", MU::WARN
|
|
2135
|
-
deploydata = MU.structToHash(@cloudobj.cloud_desc)
|
|
2136
|
-
raise MuError, "Failed to collect metadata about #{self}" if deploydata.nil?
|
|
2137
|
-
end
|
|
2138
|
-
deploydata['cloud_id'] ||= @cloudobj.cloud_id if !@cloudobj.cloud_id.nil?
|
|
2139
|
-
deploydata['mu_name'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
|
|
2140
|
-
deploydata['nodename'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
|
|
2141
|
-
deploydata.delete("#MUOBJECT")
|
|
2142
|
-
@deploy.notify(self.class.cfg_plural, @config['name'], deploydata, triggering_node: @cloudobj, delayed_save: @delayed_save) if !@deploy.nil?
|
|
2143
|
-
elsif method == :notify
|
|
2144
|
-
retval['cloud_id'] = @cloudobj.cloud_id.to_s if !@cloudobj.cloud_id.nil?
|
|
2145
|
-
retval['mu_name'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
|
|
2146
|
-
@deploy.notify(self.class.cfg_plural, @config['name'], retval, triggering_node: @cloudobj, delayed_save: @delayed_save) if !@deploy.nil?
|
|
2147
|
-
end
|
|
2148
|
-
@method_semaphore.synchronize {
|
|
2149
|
-
@method_locks.delete(method)
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
@deploydata = @cloudobj.deploydata
|
|
2153
|
-
@config = @cloudobj.config
|
|
2154
|
-
retval
|
|
2155
|
-
end
|
|
2156
|
-
} # end instance method list
|
|
2157
|
-
} # end dynamic class generation block
|
|
2158
|
-
} # end resource type iteration
|
|
624
|
+
require 'mu/cloud/machine_images'
|
|
625
|
+
require 'mu/cloud/resource_base'
|
|
626
|
+
require 'mu/cloud/providers'
|
|
2159
627
|
|
|
2160
628
|
end
|
|
2161
629
|
|