cloud-mu 2.1.0beta → 3.0.0beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (291) hide show
  1. checksums.yaml +5 -5
  2. data/Berksfile +4 -5
  3. data/Berksfile.lock +179 -0
  4. data/README.md +1 -6
  5. data/ansible/roles/geerlingguy.firewall/templates/firewall.bash.j2 +0 -0
  6. data/ansible/roles/mu-installer/README.md +33 -0
  7. data/ansible/roles/mu-installer/defaults/main.yml +2 -0
  8. data/ansible/roles/mu-installer/handlers/main.yml +2 -0
  9. data/ansible/roles/mu-installer/meta/main.yml +60 -0
  10. data/ansible/roles/mu-installer/tasks/main.yml +13 -0
  11. data/ansible/roles/mu-installer/tests/inventory +2 -0
  12. data/ansible/roles/mu-installer/tests/test.yml +5 -0
  13. data/ansible/roles/mu-installer/vars/main.yml +2 -0
  14. data/bin/mu-adopt +125 -0
  15. data/bin/mu-aws-setup +4 -4
  16. data/bin/mu-azure-setup +265 -0
  17. data/bin/mu-azure-tests +43 -0
  18. data/bin/mu-cleanup +20 -8
  19. data/bin/mu-configure +224 -98
  20. data/bin/mu-deploy +8 -3
  21. data/bin/mu-gcp-setup +16 -8
  22. data/bin/mu-gen-docs +92 -8
  23. data/bin/mu-load-config.rb +52 -12
  24. data/bin/mu-momma-cat +36 -0
  25. data/bin/mu-node-manage +34 -27
  26. data/bin/mu-self-update +2 -2
  27. data/bin/mu-ssh +12 -8
  28. data/bin/mu-upload-chef-artifacts +11 -4
  29. data/bin/mu-user-manage +3 -0
  30. data/cloud-mu.gemspec +8 -11
  31. data/cookbooks/firewall/libraries/helpers_iptables.rb +2 -2
  32. data/cookbooks/firewall/metadata.json +1 -1
  33. data/cookbooks/firewall/recipes/default.rb +5 -9
  34. data/cookbooks/mu-firewall/attributes/default.rb +2 -0
  35. data/cookbooks/mu-firewall/metadata.rb +1 -1
  36. data/cookbooks/mu-glusterfs/templates/default/mu-gluster-client.erb +0 -0
  37. data/cookbooks/mu-master/Berksfile +2 -2
  38. data/cookbooks/mu-master/files/default/check_mem.pl +0 -0
  39. data/cookbooks/mu-master/files/default/cloudamatic.png +0 -0
  40. data/cookbooks/mu-master/metadata.rb +5 -4
  41. data/cookbooks/mu-master/recipes/389ds.rb +1 -1
  42. data/cookbooks/mu-master/recipes/basepackages.rb +30 -10
  43. data/cookbooks/mu-master/recipes/default.rb +59 -7
  44. data/cookbooks/mu-master/recipes/firewall-holes.rb +1 -1
  45. data/cookbooks/mu-master/recipes/init.rb +65 -47
  46. data/cookbooks/mu-master/recipes/{eks-kubectl.rb → kubectl.rb} +4 -10
  47. data/cookbooks/mu-master/recipes/sssd.rb +2 -1
  48. data/cookbooks/mu-master/recipes/update_nagios_only.rb +6 -6
  49. data/cookbooks/mu-master/templates/default/web_app.conf.erb +2 -2
  50. data/cookbooks/mu-master/templates/mods/ldap.conf.erb +4 -0
  51. data/cookbooks/mu-php54/Berksfile +1 -2
  52. data/cookbooks/mu-php54/metadata.rb +4 -5
  53. data/cookbooks/mu-php54/recipes/default.rb +1 -1
  54. data/cookbooks/mu-splunk/templates/default/splunk-init.erb +0 -0
  55. data/cookbooks/mu-tools/Berksfile +3 -2
  56. data/cookbooks/mu-tools/files/default/Mu_CA.pem +33 -0
  57. data/cookbooks/mu-tools/libraries/helper.rb +20 -8
  58. data/cookbooks/mu-tools/metadata.rb +5 -2
  59. data/cookbooks/mu-tools/recipes/apply_security.rb +2 -3
  60. data/cookbooks/mu-tools/recipes/eks.rb +1 -1
  61. data/cookbooks/mu-tools/recipes/gcloud.rb +5 -30
  62. data/cookbooks/mu-tools/recipes/nagios.rb +1 -1
  63. data/cookbooks/mu-tools/recipes/rsyslog.rb +1 -0
  64. data/cookbooks/mu-tools/recipes/selinux.rb +19 -0
  65. data/cookbooks/mu-tools/recipes/split_var_partitions.rb +0 -1
  66. data/cookbooks/mu-tools/recipes/windows-client.rb +256 -122
  67. data/cookbooks/mu-tools/resources/disk.rb +3 -1
  68. data/cookbooks/mu-tools/templates/amazon/sshd_config.erb +1 -1
  69. data/cookbooks/mu-tools/templates/default/etc_hosts.erb +1 -1
  70. data/cookbooks/mu-tools/templates/default/{kubeconfig.erb → kubeconfig-eks.erb} +0 -0
  71. data/cookbooks/mu-tools/templates/default/kubeconfig-gke.erb +27 -0
  72. data/cookbooks/mu-tools/templates/windows-10/sshd_config.erb +137 -0
  73. data/cookbooks/mu-utility/recipes/nat.rb +4 -0
  74. data/extras/alpha.png +0 -0
  75. data/extras/beta.png +0 -0
  76. data/extras/clean-stock-amis +2 -2
  77. data/extras/generate-stock-images +131 -0
  78. data/extras/git-fix-permissions-hook +0 -0
  79. data/extras/image-generators/AWS/centos6.yaml +17 -0
  80. data/extras/image-generators/{aws → AWS}/centos7-govcloud.yaml +0 -0
  81. data/extras/image-generators/{aws → AWS}/centos7.yaml +0 -0
  82. data/extras/image-generators/{aws → AWS}/rhel7.yaml +0 -0
  83. data/extras/image-generators/{aws → AWS}/win2k12.yaml +0 -0
  84. data/extras/image-generators/{aws → AWS}/win2k16.yaml +0 -0
  85. data/extras/image-generators/{aws → AWS}/windows.yaml +0 -0
  86. data/extras/image-generators/{gcp → Google}/centos6.yaml +1 -0
  87. data/extras/image-generators/Google/centos7.yaml +18 -0
  88. data/extras/python_rpm/build.sh +0 -0
  89. data/extras/release.png +0 -0
  90. data/extras/ruby_rpm/build.sh +0 -0
  91. data/extras/ruby_rpm/muby.spec +1 -1
  92. data/install/README.md +43 -5
  93. data/install/deprecated-bash-library.sh +0 -0
  94. data/install/installer +1 -1
  95. data/install/jenkinskeys.rb +0 -0
  96. data/install/mu-master.yaml +55 -0
  97. data/modules/mommacat.ru +41 -7
  98. data/modules/mu.rb +444 -149
  99. data/modules/mu/adoption.rb +500 -0
  100. data/modules/mu/cleanup.rb +235 -158
  101. data/modules/mu/cloud.rb +675 -138
  102. data/modules/mu/clouds/aws.rb +156 -24
  103. data/modules/mu/clouds/aws/alarm.rb +4 -14
  104. data/modules/mu/clouds/aws/bucket.rb +60 -18
  105. data/modules/mu/clouds/aws/cache_cluster.rb +8 -20
  106. data/modules/mu/clouds/aws/collection.rb +12 -22
  107. data/modules/mu/clouds/aws/container_cluster.rb +209 -118
  108. data/modules/mu/clouds/aws/database.rb +120 -45
  109. data/modules/mu/clouds/aws/dnszone.rb +7 -18
  110. data/modules/mu/clouds/aws/endpoint.rb +5 -15
  111. data/modules/mu/clouds/aws/firewall_rule.rb +144 -72
  112. data/modules/mu/clouds/aws/folder.rb +4 -11
  113. data/modules/mu/clouds/aws/function.rb +6 -16
  114. data/modules/mu/clouds/aws/group.rb +4 -12
  115. data/modules/mu/clouds/aws/habitat.rb +11 -13
  116. data/modules/mu/clouds/aws/loadbalancer.rb +40 -28
  117. data/modules/mu/clouds/aws/log.rb +5 -13
  118. data/modules/mu/clouds/aws/msg_queue.rb +9 -24
  119. data/modules/mu/clouds/aws/nosqldb.rb +4 -12
  120. data/modules/mu/clouds/aws/notifier.rb +6 -13
  121. data/modules/mu/clouds/aws/role.rb +69 -40
  122. data/modules/mu/clouds/aws/search_domain.rb +17 -20
  123. data/modules/mu/clouds/aws/server.rb +184 -94
  124. data/modules/mu/clouds/aws/server_pool.rb +33 -38
  125. data/modules/mu/clouds/aws/storage_pool.rb +5 -12
  126. data/modules/mu/clouds/aws/user.rb +59 -33
  127. data/modules/mu/clouds/aws/userdata/linux.erb +18 -30
  128. data/modules/mu/clouds/aws/userdata/windows.erb +9 -9
  129. data/modules/mu/clouds/aws/vpc.rb +214 -145
  130. data/modules/mu/clouds/azure.rb +978 -44
  131. data/modules/mu/clouds/azure/container_cluster.rb +413 -0
  132. data/modules/mu/clouds/azure/firewall_rule.rb +500 -0
  133. data/modules/mu/clouds/azure/habitat.rb +167 -0
  134. data/modules/mu/clouds/azure/loadbalancer.rb +205 -0
  135. data/modules/mu/clouds/azure/role.rb +211 -0
  136. data/modules/mu/clouds/azure/server.rb +810 -0
  137. data/modules/mu/clouds/azure/user.rb +257 -0
  138. data/modules/mu/clouds/azure/userdata/README.md +4 -0
  139. data/modules/mu/clouds/azure/userdata/linux.erb +137 -0
  140. data/modules/mu/clouds/azure/userdata/windows.erb +275 -0
  141. data/modules/mu/clouds/azure/vpc.rb +782 -0
  142. data/modules/mu/clouds/cloudformation.rb +12 -9
  143. data/modules/mu/clouds/cloudformation/firewall_rule.rb +5 -13
  144. data/modules/mu/clouds/cloudformation/server.rb +10 -1
  145. data/modules/mu/clouds/cloudformation/server_pool.rb +1 -0
  146. data/modules/mu/clouds/cloudformation/vpc.rb +0 -2
  147. data/modules/mu/clouds/google.rb +554 -117
  148. data/modules/mu/clouds/google/bucket.rb +173 -32
  149. data/modules/mu/clouds/google/container_cluster.rb +1112 -157
  150. data/modules/mu/clouds/google/database.rb +24 -47
  151. data/modules/mu/clouds/google/firewall_rule.rb +344 -89
  152. data/modules/mu/clouds/google/folder.rb +156 -79
  153. data/modules/mu/clouds/google/group.rb +272 -82
  154. data/modules/mu/clouds/google/habitat.rb +177 -52
  155. data/modules/mu/clouds/google/loadbalancer.rb +9 -34
  156. data/modules/mu/clouds/google/role.rb +1211 -0
  157. data/modules/mu/clouds/google/server.rb +491 -227
  158. data/modules/mu/clouds/google/server_pool.rb +233 -48
  159. data/modules/mu/clouds/google/user.rb +479 -125
  160. data/modules/mu/clouds/google/userdata/linux.erb +3 -3
  161. data/modules/mu/clouds/google/userdata/windows.erb +9 -9
  162. data/modules/mu/clouds/google/vpc.rb +381 -223
  163. data/modules/mu/config.rb +689 -214
  164. data/modules/mu/config/bucket.rb +1 -1
  165. data/modules/mu/config/cache_cluster.rb +1 -1
  166. data/modules/mu/config/cache_cluster.yml +0 -4
  167. data/modules/mu/config/container_cluster.rb +18 -9
  168. data/modules/mu/config/database.rb +6 -23
  169. data/modules/mu/config/firewall_rule.rb +9 -15
  170. data/modules/mu/config/folder.rb +22 -21
  171. data/modules/mu/config/habitat.rb +22 -21
  172. data/modules/mu/config/loadbalancer.rb +2 -2
  173. data/modules/mu/config/role.rb +9 -40
  174. data/modules/mu/config/server.rb +26 -5
  175. data/modules/mu/config/server_pool.rb +1 -1
  176. data/modules/mu/config/storage_pool.rb +2 -2
  177. data/modules/mu/config/user.rb +4 -0
  178. data/modules/mu/config/vpc.rb +350 -110
  179. data/modules/mu/defaults/{amazon_images.yaml → AWS.yaml} +37 -39
  180. data/modules/mu/defaults/Azure.yaml +17 -0
  181. data/modules/mu/defaults/Google.yaml +24 -0
  182. data/modules/mu/defaults/README.md +1 -1
  183. data/modules/mu/deploy.rb +168 -125
  184. data/modules/mu/groomer.rb +2 -1
  185. data/modules/mu/groomers/ansible.rb +104 -32
  186. data/modules/mu/groomers/chef.rb +96 -44
  187. data/modules/mu/kittens.rb +20602 -0
  188. data/modules/mu/logger.rb +38 -11
  189. data/modules/mu/master.rb +90 -8
  190. data/modules/mu/master/chef.rb +2 -3
  191. data/modules/mu/master/ldap.rb +0 -1
  192. data/modules/mu/master/ssl.rb +250 -0
  193. data/modules/mu/mommacat.rb +917 -513
  194. data/modules/scratchpad.erb +1 -1
  195. data/modules/tests/super_complex_bok.yml +0 -0
  196. data/modules/tests/super_simple_bok.yml +0 -0
  197. data/roles/mu-master.json +2 -1
  198. data/spec/azure_creds +5 -0
  199. data/spec/mu.yaml +56 -0
  200. data/spec/mu/clouds/azure_spec.rb +164 -27
  201. data/spec/spec_helper.rb +5 -0
  202. data/test/clean_up.py +0 -0
  203. data/test/exec_inspec.py +0 -0
  204. data/test/exec_mu_install.py +0 -0
  205. data/test/exec_retry.py +0 -0
  206. data/test/smoke_test.rb +0 -0
  207. metadata +90 -118
  208. data/cookbooks/mu-jenkins/Berksfile +0 -14
  209. data/cookbooks/mu-jenkins/CHANGELOG.md +0 -13
  210. data/cookbooks/mu-jenkins/LICENSE +0 -37
  211. data/cookbooks/mu-jenkins/README.md +0 -105
  212. data/cookbooks/mu-jenkins/attributes/default.rb +0 -42
  213. data/cookbooks/mu-jenkins/files/default/cleanup_deploy_config.xml +0 -73
  214. data/cookbooks/mu-jenkins/files/default/deploy_config.xml +0 -44
  215. data/cookbooks/mu-jenkins/metadata.rb +0 -21
  216. data/cookbooks/mu-jenkins/recipes/default.rb +0 -195
  217. data/cookbooks/mu-jenkins/recipes/node-ssh-config.rb +0 -54
  218. data/cookbooks/mu-jenkins/recipes/public_key.rb +0 -24
  219. data/cookbooks/mu-jenkins/templates/default/example_job.config.xml.erb +0 -24
  220. data/cookbooks/mu-jenkins/templates/default/org.jvnet.hudson.plugins.SSHBuildWrapper.xml.erb +0 -14
  221. data/cookbooks/mu-jenkins/templates/default/ssh_config.erb +0 -6
  222. data/cookbooks/nagios/Berksfile +0 -11
  223. data/cookbooks/nagios/CHANGELOG.md +0 -589
  224. data/cookbooks/nagios/CONTRIBUTING.md +0 -11
  225. data/cookbooks/nagios/LICENSE +0 -37
  226. data/cookbooks/nagios/README.md +0 -328
  227. data/cookbooks/nagios/TESTING.md +0 -2
  228. data/cookbooks/nagios/attributes/config.rb +0 -171
  229. data/cookbooks/nagios/attributes/default.rb +0 -228
  230. data/cookbooks/nagios/chefignore +0 -102
  231. data/cookbooks/nagios/definitions/command.rb +0 -33
  232. data/cookbooks/nagios/definitions/contact.rb +0 -33
  233. data/cookbooks/nagios/definitions/contactgroup.rb +0 -33
  234. data/cookbooks/nagios/definitions/host.rb +0 -33
  235. data/cookbooks/nagios/definitions/hostdependency.rb +0 -33
  236. data/cookbooks/nagios/definitions/hostescalation.rb +0 -34
  237. data/cookbooks/nagios/definitions/hostgroup.rb +0 -33
  238. data/cookbooks/nagios/definitions/nagios_conf.rb +0 -38
  239. data/cookbooks/nagios/definitions/resource.rb +0 -33
  240. data/cookbooks/nagios/definitions/service.rb +0 -33
  241. data/cookbooks/nagios/definitions/servicedependency.rb +0 -33
  242. data/cookbooks/nagios/definitions/serviceescalation.rb +0 -34
  243. data/cookbooks/nagios/definitions/servicegroup.rb +0 -33
  244. data/cookbooks/nagios/definitions/timeperiod.rb +0 -33
  245. data/cookbooks/nagios/libraries/base.rb +0 -314
  246. data/cookbooks/nagios/libraries/command.rb +0 -91
  247. data/cookbooks/nagios/libraries/contact.rb +0 -230
  248. data/cookbooks/nagios/libraries/contactgroup.rb +0 -112
  249. data/cookbooks/nagios/libraries/custom_option.rb +0 -36
  250. data/cookbooks/nagios/libraries/data_bag_helper.rb +0 -23
  251. data/cookbooks/nagios/libraries/default.rb +0 -90
  252. data/cookbooks/nagios/libraries/host.rb +0 -412
  253. data/cookbooks/nagios/libraries/hostdependency.rb +0 -181
  254. data/cookbooks/nagios/libraries/hostescalation.rb +0 -173
  255. data/cookbooks/nagios/libraries/hostgroup.rb +0 -119
  256. data/cookbooks/nagios/libraries/nagios.rb +0 -282
  257. data/cookbooks/nagios/libraries/resource.rb +0 -59
  258. data/cookbooks/nagios/libraries/service.rb +0 -455
  259. data/cookbooks/nagios/libraries/servicedependency.rb +0 -215
  260. data/cookbooks/nagios/libraries/serviceescalation.rb +0 -195
  261. data/cookbooks/nagios/libraries/servicegroup.rb +0 -144
  262. data/cookbooks/nagios/libraries/timeperiod.rb +0 -160
  263. data/cookbooks/nagios/libraries/users_helper.rb +0 -54
  264. data/cookbooks/nagios/metadata.rb +0 -25
  265. data/cookbooks/nagios/recipes/_load_databag_config.rb +0 -153
  266. data/cookbooks/nagios/recipes/_load_default_config.rb +0 -241
  267. data/cookbooks/nagios/recipes/apache.rb +0 -48
  268. data/cookbooks/nagios/recipes/default.rb +0 -204
  269. data/cookbooks/nagios/recipes/nginx.rb +0 -82
  270. data/cookbooks/nagios/recipes/pagerduty.rb +0 -143
  271. data/cookbooks/nagios/recipes/server_package.rb +0 -40
  272. data/cookbooks/nagios/recipes/server_source.rb +0 -164
  273. data/cookbooks/nagios/templates/default/apache2.conf.erb +0 -96
  274. data/cookbooks/nagios/templates/default/cgi.cfg.erb +0 -266
  275. data/cookbooks/nagios/templates/default/commands.cfg.erb +0 -13
  276. data/cookbooks/nagios/templates/default/contacts.cfg.erb +0 -37
  277. data/cookbooks/nagios/templates/default/hostgroups.cfg.erb +0 -25
  278. data/cookbooks/nagios/templates/default/hosts.cfg.erb +0 -15
  279. data/cookbooks/nagios/templates/default/htpasswd.users.erb +0 -6
  280. data/cookbooks/nagios/templates/default/nagios.cfg.erb +0 -22
  281. data/cookbooks/nagios/templates/default/nginx.conf.erb +0 -62
  282. data/cookbooks/nagios/templates/default/pagerduty.cgi.erb +0 -185
  283. data/cookbooks/nagios/templates/default/resource.cfg.erb +0 -27
  284. data/cookbooks/nagios/templates/default/servicedependencies.cfg.erb +0 -15
  285. data/cookbooks/nagios/templates/default/servicegroups.cfg.erb +0 -14
  286. data/cookbooks/nagios/templates/default/services.cfg.erb +0 -14
  287. data/cookbooks/nagios/templates/default/templates.cfg.erb +0 -31
  288. data/cookbooks/nagios/templates/default/timeperiods.cfg.erb +0 -13
  289. data/extras/image-generators/aws/centos6.yaml +0 -18
  290. data/modules/mu/defaults/google_images.yaml +0 -16
  291. data/roles/mu-master-jenkins.json +0 -24
@@ -39,12 +39,40 @@ module MU
39
39
  class MuCloudFlagNotImplemented < StandardError;
40
40
  end
41
41
 
42
+ # Exception we throw when we attempt to make an API call against a project
43
+ # that is already deleted.
44
+ class MuDefunctHabitat < StandardError;
45
+ end
46
+
42
47
  # Methods which a cloud resource implementation, e.g. Server, must implement
43
48
  generic_class_methods = [:find, :cleanup, :validateConfig, :schema, :isGlobal?]
44
49
  generic_instance_methods = [:create, :notify, :mu_name, :cloud_id, :config]
45
50
 
46
51
  # Class methods which the base of a cloud implementation must implement
47
- generic_class_methods_toplevel = [:required_instance_methods, :myRegion, :listRegions, :listAZs, :hosted?, :hosted_config, :config_example, :writeDeploySecret, :listCredentials, :credConfig, :listInstanceTypes, :adminBucketName, :adminBucketUrl]
52
+ generic_class_methods_toplevel = [:required_instance_methods, :myRegion, :listRegions, :listAZs, :hosted?, :hosted_config, :config_example, :writeDeploySecret, :listCredentials, :credConfig, :listInstanceTypes, :adminBucketName, :adminBucketUrl, :habitat]
53
+
54
+ # Public attributes which will be available on all instantiated cloud resource objects
55
+ #
56
+ # +:config+: The fully-resolved {MU::Config} hash describing the object, aka the Basket of Kittens entry
57
+ #
58
+ # +:mu_name+: The unique internal name of the object, if one already exists
59
+ #
60
+ # +:cloud+: The cloud in which this object is resident
61
+ #
62
+ # +:cloud_id+: The cloud provider's official identifier for this object
63
+ #
64
+ # +:environment+: The declared environment string for the deployment of which this object is a member
65
+ #
66
+ # +:deploy:+ The {MU::MommaCat} object representing the deployment of which this object is a member
67
+ #
68
+ # +:deploy_id:+ The unique string which identifies the deployment of which this object is a member
69
+ #
70
+ # +:deploydata:+ A Hash containing all metadata reported by resources in this deploy method, via their +notify+ methods
71
+ #
72
+ # +:appname:+ The declared application name of this deployment
73
+ #
74
+ # +:credentials:+ The name of the cloud provider credential set from +mu.yaml+ which is used to manage this object
75
+ PUBLIC_ATTRS = [:config, :mu_name, :cloud, :cloud_id, :environment, :deploy, :deploy_id, :deploydata, :appname, :credentials]
48
76
 
49
77
  # Initialize empty classes for each of these. We'll fill them with code
50
78
  # later; we're doing this here because otherwise the parser yells about
@@ -161,7 +189,7 @@ module MU
161
189
  :interface => self.const_get("Habitat"),
162
190
  :deps_wait_on_my_creation => true,
163
191
  :waits_on_parent_completion => true,
164
- :class => generic_class_methods,
192
+ :class => generic_class_methods + [:isLive?],
165
193
  :instance => generic_instance_methods + [:groom]
166
194
  },
167
195
  :Collection => {
@@ -217,7 +245,7 @@ module MU
217
245
  :deps_wait_on_my_creation => true,
218
246
  :waits_on_parent_completion => false,
219
247
  :class => generic_class_methods,
220
- :instance => generic_instance_methods + [:registerNode]
248
+ :instance => generic_instance_methods + [:groom, :registerNode]
221
249
  },
222
250
  :Server => {
223
251
  :has_multiples => true,
@@ -227,8 +255,8 @@ module MU
227
255
  :interface => self.const_get("Server"),
228
256
  :deps_wait_on_my_creation => false,
229
257
  :waits_on_parent_completion => false,
230
- :class => generic_class_methods + [:validateInstanceType],
231
- :instance => generic_instance_methods + [:groom, :postBoot, :getSSHConfig, :canonicalIP, :getWindowsAdminPassword, :active?, :groomer, :mu_windows_name, :mu_windows_name=, :reboot, :addVolume]
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]
232
260
  },
233
261
  :ServerPool => {
234
262
  :has_multiples => false,
@@ -403,8 +431,8 @@ module MU
403
431
  :interface => self.const_get("Bucket"),
404
432
  :deps_wait_on_my_creation => true,
405
433
  :waits_on_parent_completion => true,
406
- :class => generic_class_methods,
407
- :instance => generic_instance_methods + [:groom]
434
+ :class => generic_class_methods + [:upload],
435
+ :instance => generic_instance_methods + [:groom, :upload]
408
436
  },
409
437
  :NoSQLDB => {
410
438
  :has_multiples => false,
@@ -419,6 +447,195 @@ module MU
419
447
  }
420
448
  }.freeze
421
449
 
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
422
639
 
423
640
  # A list of supported cloud resource types as Mu classes
424
641
  def self.resource_types;
@@ -429,6 +646,7 @@ module MU
429
646
  # @param type [String]: A string that looks like our short or full class name or singular or plural configuration names.
430
647
  # @return [Array]: Class name (Symbol), singular config name (String), plural config name (String), full class name (Object)
431
648
  def self.getResourceNames(type)
649
+ return [nil, nil, nil, nil, {}] if !type
432
650
  @@resource_types.each_pair { |name, cloudclass|
433
651
  if name == type.to_sym or
434
652
  cloudclass[:cfg_name] == type or
@@ -462,10 +680,28 @@ module MU
462
680
  @@supportedCloudList = ['AWS', 'CloudFormation', 'Google', 'Azure']
463
681
 
464
682
  # List of known/supported Cloud providers
683
+ # @return [Array<String>]
465
684
  def self.supportedClouds
466
685
  @@supportedCloudList
467
686
  end
468
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
699
+ end
700
+ }
701
+
702
+ available
703
+ end
704
+
469
705
  # Load the container class for each cloud we know about, and inject autoload
470
706
  # code for each of its supported resource type classes.
471
707
  failed = []
@@ -495,7 +731,7 @@ module MU
495
731
  # @param template_variables [Hash]: A list of variable substitutions to pass as globals to the ERB parser when loading the userdata script.
496
732
  # @param custom_append [String]: Arbitrary extra code to append to our default userdata behavior.
497
733
  # @return [String]
498
- def self.fetchUserdata(platform: "linux", template_variables: {}, custom_append: nil, cloud: "aws", scrub_mu_isms: false)
734
+ def self.fetchUserdata(platform: "linux", template_variables: {}, custom_append: nil, cloud: "AWS", scrub_mu_isms: false, credentials: nil)
499
735
  return nil if platform.nil? or platform.empty?
500
736
  userdata_mutex.synchronize {
501
737
  script = ""
@@ -503,10 +739,16 @@ module MU
503
739
  if template_variables.nil? or !template_variables.is_a?(Hash)
504
740
  raise MuError, "My second argument should be a hash of variables to pass into ERB templates"
505
741
  end
742
+ template_variables["credentials"] ||= credentials
506
743
  $mu = OpenStruct.new(template_variables)
507
- userdata_dir = File.expand_path(MU.myRoot+"/modules/mu/clouds/#{cloud}/userdata")
508
- platform = "linux" if %w{centos centos6 centos7 ubuntu ubuntu14 rhel rhel7 rhel71 amazon}.include? platform
509
- platform = "windows" if %w{win2k12r2 win2k12 win2k8 win2k8r2 win2k16}.include? platform
744
+ userdata_dir = File.expand_path(MU.myRoot+"/modules/mu/clouds/#{cloud.downcase}/userdata")
745
+
746
+ platform = if %w{win2k12r2 win2k12 win2k8 win2k8r2 win2k16 windows win2k19}.include?(platform)
747
+ "windows"
748
+ else
749
+ "linux"
750
+ end
751
+
510
752
  erbfile = "#{userdata_dir}/#{platform}.erb"
511
753
  if !File.exist?(erbfile)
512
754
  MU.log "No such userdata template '#{erbfile}'", MU::WARN, details: caller
@@ -590,12 +832,12 @@ module MU
590
832
  }
591
833
  @@resource_types[type.to_sym][:instance].each { |instance_method|
592
834
  if !myclass.public_instance_methods.include?(instance_method)
593
- raise MuError, "MU::Cloud::#{cloud}::#{type} has not implemented required instance method #{instance_method}"
835
+ raise MuCloudResourceNotImplemented, "MU::Cloud::#{cloud}::#{type} has not implemented required instance method #{instance_method}"
594
836
  end
595
837
  }
596
838
  cloudclass.required_instance_methods.each { |instance_method|
597
839
  if !myclass.public_instance_methods.include?(instance_method)
598
- raise MuError, "MU::Cloud::#{cloud}::#{type} has not implemented required instance method #{instance_method}"
840
+ MU.log "MU::Cloud::#{cloud}::#{type} has not implemented required instance method #{instance_method}, will declare as attr_accessor", MU::DEBUG
599
841
  end
600
842
  }
601
843
 
@@ -603,7 +845,7 @@ module MU
603
845
  return myclass
604
846
  rescue NameError => e
605
847
  @cloud_class_cache[cloud][type] = nil
606
- raise MuError, "The '#{type}' resource is not supported in cloud #{cloud} (tried MU::#{cloud}::#{type})", e.backtrace
848
+ raise MuCloudResourceNotImplemented, "The '#{type}' resource is not supported in cloud #{cloud} (tried MU::#{cloud}::#{type})", e.backtrace
607
849
  end
608
850
  end
609
851
 
@@ -624,20 +866,9 @@ module MU
624
866
 
625
867
  @@resource_types.each_pair { |name, attrs|
626
868
  Object.const_get("MU").const_get("Cloud").const_get(name).class_eval {
627
- attr_reader :cloud
628
- attr_reader :environment
629
869
  attr_reader :cloudclass
630
870
  attr_reader :cloudobj
631
- attr_reader :deploy_id
632
- attr_reader :mu_name
633
- attr_reader :cloud_id
634
- attr_reader :credentials
635
- attr_reader :url
636
- attr_reader :config
637
- attr_reader :deploydata
638
871
  attr_reader :destroyed
639
- attr_reader :cfm_template
640
- attr_reader :cfm_name
641
872
  attr_reader :delayed_save
642
873
 
643
874
  def self.shortname
@@ -668,11 +899,6 @@ module MU
668
899
  MU::Cloud.resource_types[shortname.to_sym][:deps_wait_on_my_creation]
669
900
  end
670
901
 
671
- def groomer
672
- return @cloudobj.groomer if !@cloudobj.nil?
673
- nil
674
- end
675
-
676
902
  # Print something palatable when we're called in a string context.
677
903
  def to_s
678
904
  fullname = "#{self.class.shortname}"
@@ -688,86 +914,232 @@ module MU
688
914
  return fullname
689
915
  end
690
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
691
932
 
692
933
  # @param mommacat [MU::MommaCat]: The deployment containing this cloud resource
693
934
  # @param mu_name [String]: Optional- specify the full Mu resource name of an existing resource to load, instead of creating a new one
694
935
  # @param cloud_id [String]: Optional- specify the cloud provider's identifier for an existing resource to load, instead of creating a new one
695
936
  # @param kitten_cfg [Hash]: The parse configuration for this object from {MU::Config}
696
- def initialize(mommacat: nil,
697
- mu_name: nil,
698
- cloud_id: nil,
699
- credentials: nil,
700
- kitten_cfg: nil,
701
- delayed_save: false)
702
- raise MuError, "Cannot invoke Cloud objects without a configuration" if kitten_cfg.nil?
703
- @live = true
704
- @deploy = mommacat
705
- @config = kitten_cfg
706
- @delayed_save = delayed_save
707
- @cloud_id = cloud_id
708
- @credentials = credentials
709
- @credentials ||= kitten_cfg['credentials']
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
710
954
 
711
- if !@deploy.nil?
712
- @deploy_id = @deploy.deploy_id
713
- MU.log "Initializing an instance of #{self.class.name} in #{@deploy_id} #{mu_name}", MU::DEBUG, details: kitten_cfg
714
- elsif mu_name.nil?
715
- raise MuError, "Can't instantiate a MU::Cloud object with a live deploy or giving us a mu_name"
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.
716
989
  else
717
- MU.log "Initializing an independent instance of #{self.class.name} named #{mu_name}", MU::DEBUG, details: kitten_cfg
718
- end
719
- if !kitten_cfg.has_key?("cloud")
720
- kitten_cfg['cloud'] = MU::Config.defaultCloud
721
- end
722
- @cloud = kitten_cfg['cloud']
723
- @cloudclass = MU::Cloud.loadCloudType(@cloud, self.class.shortname)
724
- @environment = kitten_cfg['environment']
725
- @method_semaphore = Mutex.new
726
- @method_locks = {}
727
- # XXX require subclass to provide attr_readers of @config and @deploy
728
990
 
729
- @cloudobj = @cloudclass.new(mommacat: mommacat, kitten_cfg: kitten_cfg, cloud_id: cloud_id, mu_name: mu_name)
991
+ # Declare the attributes that everyone should have
992
+ class << self
993
+ PUBLIC_ATTRS.each { |a|
994
+ attr_reader a
995
+ }
996
+ end
730
997
 
731
- raise MuError, "Unknown error instantiating #{self}" if @cloudobj.nil?
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])
732
1000
 
733
- # If we just loaded an existing object, go ahead and prepopulate the
734
- # describe() cache
735
- if !cloud_id.nil? or !mu_name.nil?
736
- @cloudobj.describe(cloud_id: cloud_id)
737
- @cloud_id ||= @cloudobj.cloud_id
738
- end
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
739
1005
 
740
- @deploydata = @cloudobj.deploydata
741
- @config = @cloudobj.config
1006
+ @deploy = args[:mommacat] || args[:deploy]
742
1007
 
743
- # If we're going to be integrated into AD or otherwise need a short
744
- # hostname, generate it now.
745
- if self.class.shortname == "Server" and (@cloudobj.windows? or @config['active_directory']) and @cloudobj.mu_windows_name.nil?
746
- if !@deploydata.nil? and !@deploydata['mu_windows_name'].nil?
747
- @cloudobj.mu_windows_name = @deploydata['mu_windows_name']
748
- else
749
- # Use the same random differentiator as the "real" name if we're
750
- # from a ServerPool. Helpful for admin sanity.
751
- unq = @cloudobj.mu_name.sub(/^.*?-(...)$/, '\1')
752
- if @config['basis'] and !unq.nil? and !unq.empty?
753
- @cloudobj.mu_windows_name = @deploy.getResourceName(@config['name'], max_length: 15, need_unique_string: true, use_unique_string: unq, reuse_unique_string: true)
754
- else
755
- @cloudobj.mu_windows_name = @deploy.getResourceName(@config['name'], max_length: 15, need_unique_string: true)
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
756
1018
  end
757
1019
  end
758
- 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
759
1092
 
760
- # Register us with our parent deploy so that we can be found by our
761
- # littermates if needed.
762
- if !@deploy.nil? and !@cloudobj.mu_name.nil? and !@cloudobj.mu_name.empty?
763
- describe # XXX is this actually safe here?
764
- @deploy.addKitten(self.class.cfg_name, @config['name'], self)
765
- elsif !@deploy.nil?
766
- MU.log "#{self} didn't generate a mu_name after being loaded/initialized, dependencies on this resource will probably be confused!", MU::ERR
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
767
1121
  end
768
1122
 
769
1123
  end
770
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
+
771
1143
  # Remove all metadata and cloud resources associated with this object
772
1144
  def destroy
773
1145
  if !@cloudobj.nil? and !@cloudobj.groomer.nil?
@@ -796,23 +1168,99 @@ module MU
796
1168
  end
797
1169
  end
798
1170
  end
1171
+
1172
+ # Return the cloud object's idea of where it lives (project, account,
1173
+ # etc) in the form of an identifier. If not applicable for this object,
1174
+ # we expect to return +nil+.
1175
+ # @return [String,nil]
1176
+ def habitat(nolookup: true)
1177
+ return nil if ["folder", "habitat"].include?(self.class.cfg_name)
1178
+ if @cloudobj
1179
+ @cloudparentclass.habitat(@cloudobj, nolookup: nolookup, deploy: @deploy)
1180
+ else
1181
+ @cloudparentclass.habitat(self, nolookup: nolookup, deploy: @deploy)
1182
+ end
1183
+ end
1184
+
1185
+ def habitat_id(nolookup: false)
1186
+ @habitat_id ||= habitat(nolookup: nolookup)
1187
+ @habitat_id
1188
+ end
1189
+
1190
+ # We're fundamentally a wrapper class, so go ahead and reroute requests
1191
+ # that are meant for our wrapped object.
1192
+ def method_missing(method_sym, *arguments)
1193
+ if @cloudobj
1194
+ MU.log "INVOKING #{method_sym.to_s} FROM PARENT CLOUD OBJECT #{self}", MU::DEBUG, details: arguments
1195
+ @cloudobj.method(method_sym).call(*arguments)
1196
+ else
1197
+ raise NoMethodError, "No such instance method #{method_sym.to_s} available on #{self.class.name}"
1198
+ end
1199
+ end
1200
+
1201
+ # Merge the passed hash into the existing configuration hash of this
1202
+ # cloud object. Currently this is only used by the {MU::Adoption}
1203
+ # module. I don't love exposing this to the whole internal API, but I'm
1204
+ # probably overthinking that.
1205
+ # @param newcfg [Hash]
1206
+ def config!(newcfg)
1207
+ @config.merge!(newcfg)
1208
+ end
799
1209
 
800
- def cloud_desc()
1210
+ def cloud_desc(use_cache: true)
801
1211
  describe
1212
+
802
1213
  if !@cloudobj.nil?
803
- @cloud_desc_cache ||= @cloudobj.cloud_desc
804
- @url = @cloudobj.url if @cloudobj.respond_to?(:url)
1214
+ if @cloudobj.class.instance_methods(false).include?(:cloud_desc)
1215
+ @cloud_desc_cache ||= @cloudobj.cloud_desc
1216
+ end
805
1217
  end
806
- if !@config.nil? and !@cloud_id.nil? and @cloud_desc_cache.nil?
1218
+ if !@config.nil? and !@cloud_id.nil? and (!use_cache or @cloud_desc_cache.nil?)
807
1219
  # The find() method should be returning a Hash with the cloud_id
808
1220
  # as a key and a cloud platform descriptor as the value.
809
1221
  begin
1222
+ args = {
1223
+ :region => @config['region'],
1224
+ :cloud => @config['cloud'],
1225
+ :cloud_id => @cloud_id,
1226
+ :credentials => @credentials,
1227
+ :project => habitat_id, # XXX this belongs in our required_instance_methods hack
1228
+ :flags => @config
1229
+ }
1230
+ @cloudparentclass.required_instance_methods.each { |m|
1231
+ # if respond_to?(m)
1232
+ # args[m] = method(m).call
1233
+ # else
1234
+ args[m] = instance_variable_get(("@"+m.to_s).to_sym)
1235
+ # end
1236
+ }
810
1237
 
811
- matches = self.class.find(region: @config['region'], cloud_id: @cloud_id, flags: @config, credentials: @credentials)
812
- if !matches.nil? and matches.is_a?(Hash) and matches.has_key?(@cloud_id)
813
- @cloud_desc_cache = matches[@cloud_id]
814
- else
815
- MU.log "Failed to find a live #{self.class.shortname} with identifier #{@cloud_id} in #{@credentials}/#{@config['region']}, which has a record in deploy #{@deploy.deploy_id}", MU::WARN, details: caller
1238
+ matches = self.class.find(args)
1239
+ if !matches.nil? and matches.is_a?(Hash)
1240
+ # XXX or if the hash is keyed with an ::Id element, oh boy
1241
+ # puts matches[@cloud_id][:self_link]
1242
+ # puts matches[@cloud_id][:url]
1243
+ # if matches[@cloud_id][:self_link]
1244
+ # @url ||= matches[@cloud_id][:self_link]
1245
+ # elsif matches[@cloud_id][:url]
1246
+ # @url ||= matches[@cloud_id][:url]
1247
+ # elsif matches[@cloud_id][:arn]
1248
+ # @arn ||= matches[@cloud_id][:arn]
1249
+ # end
1250
+ if matches[@cloud_id]
1251
+ @cloud_desc_cache = matches[@cloud_id]
1252
+ else
1253
+ matches.each_pair { |k, v| # flatten out ::Id objects just in case
1254
+ if @cloud_id.to_s == k.to_s
1255
+ @cloud_desc_cache = v
1256
+ break
1257
+ end
1258
+ }
1259
+ end
1260
+ end
1261
+
1262
+ if !@cloud_desc_cache
1263
+ MU.log "cloud_desc via #{self.class.name}.find() failed to locate a live object.\nWas called by #{caller[0]}", MU::WARN, details: args
816
1264
  end
817
1265
  rescue Exception => e
818
1266
  MU.log "Got #{e.inspect} trying to find cloud handle for #{self.class.shortname} #{@mu_name} (#{@cloud_id})", MU::WARN
@@ -858,19 +1306,6 @@ module MU
858
1306
  end
859
1307
  if @deploydata.has_key?('cloud_id')
860
1308
  @cloud_id ||= @deploydata['cloud_id']
861
- else
862
- # XXX temp hack to catch old Amazon-style identifiers. Remove this
863
- # before supporting any other cloud layers, otherwise name
864
- # collision is possible.
865
- ["group_id", "instance_id", "awsname", "identifier", "vpc_id", "id"].each { |identifier|
866
- if @deploydata.has_key?(identifier)
867
- @cloud_id ||= @deploydata[identifier]
868
- if @mu_name.nil? and (identifier == "awsname" or identifier == "identifier" or identifier == "group_id")
869
- @mu_name = @deploydata[identifier]
870
- end
871
- break
872
- end
873
- }
874
1309
  end
875
1310
  end
876
1311
 
@@ -886,7 +1321,7 @@ module MU
886
1321
  # resources in this deployment), as well as for certain config stanzas
887
1322
  # which can refer to external resources (@vpc, @loadbalancers,
888
1323
  # @add_firewall_rules)
889
- def dependencies(use_cache: false)
1324
+ def dependencies(use_cache: false, debug: false)
890
1325
  @dependencies = {} if @dependencies.nil?
891
1326
  @loadbalancers = [] if @loadbalancers.nil?
892
1327
  if @config.nil?
@@ -897,6 +1332,8 @@ module MU
897
1332
  end
898
1333
  @config['dependencies'] = [] if @config['dependencies'].nil?
899
1334
 
1335
+ loglevel = debug ? MU::NOTICE : MU::DEBUG
1336
+
900
1337
  # First, general dependencies. These should all be fellow members of
901
1338
  # the current deployment.
902
1339
  @config['dependencies'].each { |dep|
@@ -904,7 +1341,7 @@ module MU
904
1341
  next if @dependencies[dep['type']].has_key?(dep['name'])
905
1342
  handle = @deploy.findLitterMate(type: dep['type'], name: dep['name']) if !@deploy.nil?
906
1343
  if !handle.nil?
907
- MU.log "Loaded dependency for #{self}: #{dep['name']} => #{handle}", MU::DEBUG
1344
+ MU.log "Loaded dependency for #{self}: #{dep['name']} => #{handle}", loglevel
908
1345
  @dependencies[dep['type']][dep['name']] = handle
909
1346
  else
910
1347
  # XXX yell under circumstances where we should expect to have
@@ -914,62 +1351,100 @@ module MU
914
1351
 
915
1352
  # Special dependencies: my containing VPC
916
1353
  if self.class.can_live_in_vpc and !@config['vpc'].nil?
917
- MU.log "Loading VPC for #{self}", MU::DEBUG, details: @config['vpc']
918
- if !@config['vpc']["vpc_name"].nil? and @deploy
919
- sib_by_name = @deploy.findLitterMate(name: @config['vpc']['vpc_name'], type: "vpcs", return_all: true)
1354
+ # If something hash-ified a MU::Config::Ref here, fix it
1355
+ if !@config['vpc']["id"].nil? and @config['vpc']["id"].is_a?(Hash)
1356
+ @config['vpc']["id"] = MU::Config::Ref.new(@config['vpc']["id"])
1357
+ end
1358
+ if !@config['vpc']["id"].nil? and @config['vpc']["id"].is_a?(MU::Config::Ref) and !@config['vpc']["id"].kitten.nil?
1359
+ @vpc = @config['vpc']["id"].kitten
1360
+ elsif !@config['vpc']["name"].nil? and @deploy
1361
+ MU.log "Attempting findLitterMate on VPC for #{self}", loglevel, details: @config['vpc']
1362
+
1363
+ sib_by_name = @deploy.findLitterMate(name: @config['vpc']['name'], type: "vpcs", return_all: true, habitat: @config['vpc']['project'], debug: debug)
920
1364
  if sib_by_name.is_a?(Array)
921
1365
  if sib_by_name.size == 1
922
1366
  @vpc = matches.first
1367
+ MU.log "Single VPC match for #{self}", loglevel, details: @vpc.to_s
923
1368
  else
924
1369
  # XXX ok but this is the wrong place for this really the config parser needs to sort this out somehow
925
1370
  # we got multiple matches, try to pick one by preferred subnet
926
1371
  # behavior
1372
+ MU.log "Sorting a bunch of VPC matches for #{self}", loglevel, details: sib_by_name.map { |s| s.to_s }.join(", ")
927
1373
  sib_by_name.each { |sibling|
928
1374
  all_private = sibling.subnets.map { |s| s.private? }.all?(true)
929
1375
  all_public = sibling.subnets.map { |s| s.private? }.all?(false)
1376
+ names = sibling.subnets.map { |s| s.name }
1377
+ ids = sibling.subnets.map { |s| s.cloud_id }
930
1378
  if all_private and ["private", "all_private"].include?(@config['vpc']['subnet_pref'])
931
1379
  @vpc = sibling
932
1380
  break
933
1381
  elsif all_public and ["public", "all_public"].include?(@config['vpc']['subnet_pref'])
934
1382
  @vpc = sibling
935
1383
  break
936
- else
937
- MU.log "Got multiple matching VPCs for #{@mu_name}, so I'm arbitrarily choosing #{sibling.mu_name}"
1384
+ elsif @config['vpc']['subnet_name'] and
1385
+ names.include?(@config['vpc']['subnet_name'])
1386
+ puts "CHOOSING #{@vpc.to_s} 'cause it has #{@config['vpc']['subnet_name']}"
1387
+ @vpc = sibling
1388
+ break
1389
+ elsif @config['vpc']['subnet_id'] and
1390
+ ids.include?(@config['vpc']['subnet_id'])
938
1391
  @vpc = sibling
939
1392
  break
940
1393
  end
941
1394
  }
1395
+ if !@vpc
1396
+ sibling = sib_by_name.sample
1397
+ 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']
1398
+ @vpc = sibling
1399
+ end
942
1400
  end
943
1401
  else
944
1402
  @vpc = sib_by_name
1403
+ MU.log "Found exact VPC match for #{self}", loglevel, details: sib_by_name.to_s
945
1404
  end
1405
+ else
1406
+ MU.log "No shortcuts available to fetch VPC for #{self}", loglevel, details: @config['vpc']
946
1407
  end
947
1408
 
948
- if !@vpc and !@config['vpc']["vpc_name"].nil? and
1409
+ if !@vpc and !@config['vpc']["name"].nil? and
949
1410
  @dependencies.has_key?("vpc") and
950
- @dependencies["vpc"].has_key?(@config['vpc']["vpc_name"])
951
- @vpc = @dependencies["vpc"][@config['vpc']["vpc_name"]]
1411
+ @dependencies["vpc"].has_key?(@config['vpc']["name"])
1412
+ MU.log "Grabbing VPC I see in @dependencies['vpc']['#{@config['vpc']["name"]}'] for #{self}", loglevel, details: @config['vpc']
1413
+ @vpc = @dependencies["vpc"][@config['vpc']["name"]]
952
1414
  elsif !@vpc
953
1415
  tag_key, tag_value = @config['vpc']['tag'].split(/=/, 2) if !@config['vpc']['tag'].nil?
954
- if !@config['vpc'].has_key?("vpc_id") and
1416
+ if !@config['vpc'].has_key?("id") and
955
1417
  !@config['vpc'].has_key?("deploy_id") and !@deploy.nil?
956
1418
  @config['vpc']["deploy_id"] = @deploy.deploy_id
957
1419
  end
1420
+ MU.log "Doing findStray for VPC for #{self}", loglevel, details: @config['vpc']
958
1421
  vpcs = MU::MommaCat.findStray(
959
1422
  @config['cloud'],
960
1423
  "vpc",
961
1424
  deploy_id: @config['vpc']["deploy_id"],
962
- cloud_id: @config['vpc']["vpc_id"],
963
- name: @config['vpc']["vpc_name"],
1425
+ cloud_id: @config['vpc']["id"],
1426
+ name: @config['vpc']["name"],
964
1427
  tag_key: tag_key,
965
1428
  tag_value: tag_value,
1429
+ habitats: [@project_id],
966
1430
  region: @config['vpc']["region"],
967
1431
  calling_deploy: @deploy,
968
- dummy_ok: true
1432
+ credentials: @credentials,
1433
+ dummy_ok: true,
1434
+ debug: debug
969
1435
  )
970
1436
  @vpc = vpcs.first if !vpcs.nil? and vpcs.size > 0
971
1437
  end
972
- if !@vpc.nil? and (
1438
+ if @vpc and @vpc.config and @vpc.config['bastion'] and
1439
+ @vpc.config['bastion'].to_h['name'] != @config['name']
1440
+ refhash = @vpc.config['bastion'].to_h
1441
+ refhash['deploy_id'] ||= @vpc.deploy.deploy_id
1442
+ natref = MU::Config::Ref.get(refhash)
1443
+ if natref and natref.kitten(@vpc.deploy)
1444
+ @nat = natref.kitten(@vpc.deploy)
1445
+ end
1446
+ end
1447
+ if @nat.nil? and !@vpc.nil? and (
973
1448
  @config['vpc'].has_key?("nat_host_id") or
974
1449
  @config['vpc'].has_key?("nat_host_tag") or
975
1450
  @config['vpc'].has_key?("nat_host_ip") or
@@ -1008,6 +1483,22 @@ module MU
1008
1483
  @vpc = self
1009
1484
  end
1010
1485
 
1486
+ # Google accounts usually have a useful default VPC we can use
1487
+ if @vpc.nil? and @project_id and @cloud == "Google" and
1488
+ self.class.can_live_in_vpc
1489
+ MU.log "Seeing about default VPC for #{self.to_s}", MU::NOTICE
1490
+ vpcs = MU::MommaCat.findStray(
1491
+ "Google",
1492
+ "vpc",
1493
+ cloud_id: "default",
1494
+ habitats: [@project_id],
1495
+ credentials: @credentials,
1496
+ dummy_ok: true,
1497
+ debug: debug
1498
+ )
1499
+ @vpc = vpcs.first if !vpcs.nil? and vpcs.size > 0
1500
+ end
1501
+
1011
1502
  # Special dependencies: LoadBalancers I've asked to attach to an
1012
1503
  # instance.
1013
1504
  if @config.has_key?("loadbalancers")
@@ -1046,17 +1537,46 @@ module MU
1046
1537
  MU::Cloud::ALPHA
1047
1538
  end
1048
1539
 
1540
+ # Return a list of "container" artifacts, by class, that apply to this
1541
+ # resource type in a cloud provider. This is so methods that call find
1542
+ # know whether to call +find+ with identifiers for parent resources.
1543
+ # This is similar in purpose to the +isGlobal?+ resource class method,
1544
+ # which tells our search functions whether or not a resource scopes to
1545
+ # a region. In almost all cases this is one-entry list consisting of
1546
+ # +:Habitat+. Notable exceptions include most implementations of
1547
+ # +Habitat+, which either reside inside a +:Folder+ or nothing at all;
1548
+ # whereas a +:Folder+ tends to not have any containing parent. Very few
1549
+ # resource implementations will need to override this.
1550
+ # A +nil+ entry in this list is interpreted as "this resource can be
1551
+ # global."
1552
+ # @return [Array<Symbol,nil>]
1553
+ def self.canLiveIn
1554
+ if self.shortname == "Folder"
1555
+ [nil, :Folder]
1556
+ elsif self.shortname == "Habitat"
1557
+ [:Folder]
1558
+ else
1559
+ [:Habitat]
1560
+ end
1561
+ end
1562
+
1049
1563
  def self.find(*flags)
1050
1564
  allfound = {}
1051
1565
 
1052
- MU::Cloud.supportedClouds.each { |cloud|
1566
+ MU::Cloud.availableClouds.each { |cloud|
1053
1567
  begin
1054
1568
  args = flags.first
1569
+ next if args[:cloud] and args[:cloud] != cloud
1055
1570
  # skip this cloud if we have a region argument that makes no
1056
1571
  # sense there
1057
1572
  cloudbase = Object.const_get("MU").const_get("Cloud").const_get(cloud)
1573
+ next if cloudbase.listCredentials.nil? or cloudbase.listCredentials.empty? or cloudbase.credConfig(args[:credentials]).nil?
1058
1574
  if args[:region] and cloudbase.respond_to?(:listRegions)
1059
- next if !cloudbase.listRegions(credentials: args[:credentials]).include?(args[:region])
1575
+ if !cloudbase.listRegions(credentials: args[:credentials])
1576
+ MU.log "Failed to get region list for credentials #{args[:credentials]} in cloud #{cloud}", MU::ERR, details: caller
1577
+ else
1578
+ next if !cloudbase.listRegions(credentials: args[:credentials]).include?(args[:region])
1579
+ end
1060
1580
  end
1061
1581
  begin
1062
1582
  cloudclass = MU::Cloud.loadCloudType(cloud, shortname)
@@ -1094,9 +1614,9 @@ module MU
1094
1614
  end
1095
1615
  end
1096
1616
 
1097
- if shortname == "Server"
1617
+ if shortname == "Server" or shortname == "ServerPool"
1098
1618
  def windows?
1099
- return true if %w{win2k16 win2k12r2 win2k12 win2k8 win2k8r2 windows}.include?(@config['platform'])
1619
+ return true if %w{win2k16 win2k12r2 win2k12 win2k8 win2k8r2 win2k19 windows}.include?(@config['platform'])
1100
1620
  begin
1101
1621
  return true if cloud_desc.respond_to?(:platform) and cloud_desc.platform == "Windows"
1102
1622
  # 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?
@@ -1330,7 +1850,9 @@ module MU
1330
1850
  if !output.nil? and !output.empty?
1331
1851
  raise MU::Cloud::BootstrapTempFail, "Linux package manager is still doing something, need to wait (#{output})"
1332
1852
  end
1333
- if !@config['skipinitialupdates']
1853
+ if !@config['skipinitialupdates'] and
1854
+ !@config['scrub_mu_isms'] and
1855
+ !@config['userdata_script']
1334
1856
  output = ssh.exec!(lnx_updates_check)
1335
1857
  if !output.nil? and output.match(/userdata still running/)
1336
1858
  raise MU::Cloud::BootstrapTempFail, "Waiting for initial userdata system updates to complete"
@@ -1464,7 +1986,7 @@ module MU
1464
1986
 
1465
1987
  if retries < max_retries
1466
1988
  retries = retries + 1
1467
- msg = "ssh #{ssh_user}@#{@config['mu_name']}: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})", MU::WARN
1989
+ msg = "ssh #{ssh_user}@#{@mu_name}: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})", MU::WARN
1468
1990
  if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0)
1469
1991
  MU.log msg, MU::NOTICE
1470
1992
  elsif retries/max_retries > 0.5
@@ -1473,7 +1995,7 @@ module MU
1473
1995
  sleep retry_interval
1474
1996
  retry
1475
1997
  else
1476
- raise MuError, "#{@config['mu_name']}: #{e.inspect} trying to connect with SSH, max_retries exceeded", e.backtrace
1998
+ raise MuError, "#{@mu_name}: #{e.inspect} trying to connect with SSH, max_retries exceeded", e.backtrace
1477
1999
  end
1478
2000
  end
1479
2001
  return session
@@ -1501,6 +2023,17 @@ module MU
1501
2023
  MU::MommaCat.unlockAll
1502
2024
  end
1503
2025
 
2026
+ # A hook that is always called just before each instance method is
2027
+ # invoked, so that we can ensure that repetitive setup tasks (like
2028
+ # resolving +:resource_group+ for Azure resources) have always been
2029
+ # done.
2030
+ def resourceInitHook
2031
+ @cloud ||= cloud
2032
+ if @cloudparentclass.respond_to?(:resourceInitHook)
2033
+ @cloudparentclass.resourceInitHook(@cloudobj, @deploy)
2034
+ end
2035
+ end
2036
+
1504
2037
  # Wrap the instance methods that this cloud resource type has to
1505
2038
  # implement.
1506
2039
  MU::Cloud.resource_types[name.to_sym][:instance].each { |method|
@@ -1538,15 +2071,19 @@ module MU
1538
2071
  if (method == :create or method == :groom or method == :postBoot) and
1539
2072
  (!@destroyed and !@cloudobj.destroyed)
1540
2073
  deploydata = @cloudobj.method(:notify).call
2074
+ @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?
1541
2075
  if deploydata.nil? or !deploydata.is_a?(Hash)
1542
- MU.log "#{self} notify method did not return a Hash of deployment data", MU::WARN
2076
+ 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
1543
2077
  deploydata = MU.structToHash(@cloudobj.cloud_desc)
2078
+ raise MuError, "Failed to collect metadata about #{self}" if deploydata.nil?
1544
2079
  end
1545
- deploydata['cloud_id'] = @cloudobj.cloud_id if !@cloudobj.cloud_id.nil?
2080
+ deploydata['cloud_id'] ||= @cloudobj.cloud_id if !@cloudobj.cloud_id.nil?
1546
2081
  deploydata['mu_name'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
2082
+ deploydata['nodename'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
2083
+ deploydata.delete("#MUOBJECT")
1547
2084
  @deploy.notify(self.class.cfg_plural, @config['name'], deploydata, triggering_node: @cloudobj, delayed_save: @delayed_save) if !@deploy.nil?
1548
2085
  elsif method == :notify
1549
- retval['cloud_id'] = @cloudobj.cloud_id if !@cloudobj.cloud_id.nil?
2086
+ retval['cloud_id'] = @cloudobj.cloud_id.to_s if !@cloudobj.cloud_id.nil?
1550
2087
  retval['mu_name'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
1551
2088
  @deploy.notify(self.class.cfg_plural, @config['name'], retval, triggering_node: @cloudobj, delayed_save: @delayed_save) if !@deploy.nil?
1552
2089
  end