cloud-mu 1.9.0.pre.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (618) hide show
  1. checksums.yaml +7 -0
  2. data/Berksfile +56 -0
  3. data/Berksfile.lock +250 -0
  4. data/Jenkinsfile +184 -0
  5. data/LICENSE.md +37 -0
  6. data/README.md +26 -0
  7. data/bin/mu-aws-setup +376 -0
  8. data/bin/mu-cleanup +68 -0
  9. data/bin/mu-configure +1133 -0
  10. data/bin/mu-deploy +166 -0
  11. data/bin/mu-firewall-allow-clients +30 -0
  12. data/bin/mu-gcp-setup +200 -0
  13. data/bin/mu-gen-docs +34 -0
  14. data/bin/mu-gen-env +42 -0
  15. data/bin/mu-load-config.rb +158 -0
  16. data/bin/mu-node-manage +683 -0
  17. data/bin/mu-self-update +228 -0
  18. data/bin/mu-ssh +23 -0
  19. data/bin/mu-tunnel-nagios +144 -0
  20. data/bin/mu-upload-chef-artifacts +757 -0
  21. data/bin/mu-user-manage +275 -0
  22. data/cookbooks/awscli/LICENSE +37 -0
  23. data/cookbooks/awscli/README.md +58 -0
  24. data/cookbooks/awscli/attributes/default.rb +1 -0
  25. data/cookbooks/awscli/libraries/instance_metadata.rb +21 -0
  26. data/cookbooks/awscli/metadata.rb +20 -0
  27. data/cookbooks/awscli/recipes/default.rb +56 -0
  28. data/cookbooks/awscli/templates/default/config.erb +18 -0
  29. data/cookbooks/mu-activedirectory/CHANGELOG.md +13 -0
  30. data/cookbooks/mu-activedirectory/LICENSE +37 -0
  31. data/cookbooks/mu-activedirectory/README.md +6 -0
  32. data/cookbooks/mu-activedirectory/attributes/default.rb +98 -0
  33. data/cookbooks/mu-activedirectory/files/default/password-auth +32 -0
  34. data/cookbooks/mu-activedirectory/files/default/sshd_pol.pp +0 -0
  35. data/cookbooks/mu-activedirectory/files/default/sshd_pol.te +32 -0
  36. data/cookbooks/mu-activedirectory/files/default/syslogd_oddjobd.pp +0 -0
  37. data/cookbooks/mu-activedirectory/files/default/syslogd_oddjobd.te +10 -0
  38. data/cookbooks/mu-activedirectory/files/default/system-auth +34 -0
  39. data/cookbooks/mu-activedirectory/files/default/winbindpol.pp +0 -0
  40. data/cookbooks/mu-activedirectory/files/default/winbindpol.te +37 -0
  41. data/cookbooks/mu-activedirectory/libraries/config.rb +106 -0
  42. data/cookbooks/mu-activedirectory/libraries/helper.rb +86 -0
  43. data/cookbooks/mu-activedirectory/metadata.rb +17 -0
  44. data/cookbooks/mu-activedirectory/providers/domain.rb +152 -0
  45. data/cookbooks/mu-activedirectory/providers/domain_controller.rb +89 -0
  46. data/cookbooks/mu-activedirectory/providers/domain_node.rb +275 -0
  47. data/cookbooks/mu-activedirectory/recipes/default.rb +8 -0
  48. data/cookbooks/mu-activedirectory/recipes/domain-controller.rb +44 -0
  49. data/cookbooks/mu-activedirectory/recipes/domain-node.rb +50 -0
  50. data/cookbooks/mu-activedirectory/recipes/domain.rb +43 -0
  51. data/cookbooks/mu-activedirectory/recipes/sssd.rb +185 -0
  52. data/cookbooks/mu-activedirectory/resources/domain.rb +25 -0
  53. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +25 -0
  54. data/cookbooks/mu-activedirectory/resources/domain_node.rb +20 -0
  55. data/cookbooks/mu-activedirectory/templates/default/dhclient-eth0.conf.erb +4 -0
  56. data/cookbooks/mu-activedirectory/templates/default/interface +0 -0
  57. data/cookbooks/mu-activedirectory/templates/default/krb5.conf.erb +23 -0
  58. data/cookbooks/mu-activedirectory/templates/default/ntp.conf.erb +56 -0
  59. data/cookbooks/mu-activedirectory/templates/default/smb.conf.erb +33 -0
  60. data/cookbooks/mu-activedirectory/templates/default/sssd.conf.erb +60 -0
  61. data/cookbooks/mu-activedirectory/templates/windows/Backup.xml.erb +20 -0
  62. data/cookbooks/mu-activedirectory/templates/windows/bkupInfo.xml.erb +1 -0
  63. data/cookbooks/mu-activedirectory/templates/windows/gpreprt.xml.erb +198 -0
  64. data/cookbooks/mu-activedirectory/templates/windows/gptmpl.inf.erb +12 -0
  65. data/cookbooks/mu-activedirectory/templates/windows/manifest.xml.erb +1 -0
  66. data/cookbooks/mu-firewall/CHANGELOG.md +11 -0
  67. data/cookbooks/mu-firewall/LICENSE +37 -0
  68. data/cookbooks/mu-firewall/README.md +5 -0
  69. data/cookbooks/mu-firewall/attributes/default.rb +3 -0
  70. data/cookbooks/mu-firewall/metadata.rb +16 -0
  71. data/cookbooks/mu-firewall/recipes/default.rb +10 -0
  72. data/cookbooks/mu-glusterfs/CHANGELOG.md +13 -0
  73. data/cookbooks/mu-glusterfs/LICENSE +37 -0
  74. data/cookbooks/mu-glusterfs/README.md +5 -0
  75. data/cookbooks/mu-glusterfs/attributes/default.rb +34 -0
  76. data/cookbooks/mu-glusterfs/metadata.rb +17 -0
  77. data/cookbooks/mu-glusterfs/recipes/client.rb +62 -0
  78. data/cookbooks/mu-glusterfs/recipes/default.rb +16 -0
  79. data/cookbooks/mu-glusterfs/recipes/samba.rb +57 -0
  80. data/cookbooks/mu-glusterfs/recipes/server.rb +200 -0
  81. data/cookbooks/mu-glusterfs/templates/default/mu-gluster-client.erb +71 -0
  82. data/cookbooks/mu-glusterfs/templates/default/smb.conf.erb +14 -0
  83. data/cookbooks/mu-jenkins/CHANGELOG.md +13 -0
  84. data/cookbooks/mu-jenkins/LICENSE +37 -0
  85. data/cookbooks/mu-jenkins/README.md +105 -0
  86. data/cookbooks/mu-jenkins/attributes/default.rb +42 -0
  87. data/cookbooks/mu-jenkins/files/default/cleanup_deploy_config.xml +73 -0
  88. data/cookbooks/mu-jenkins/files/default/deploy_config.xml +44 -0
  89. data/cookbooks/mu-jenkins/metadata.rb +21 -0
  90. data/cookbooks/mu-jenkins/recipes/default.rb +195 -0
  91. data/cookbooks/mu-jenkins/recipes/node-ssh-config.rb +54 -0
  92. data/cookbooks/mu-jenkins/recipes/public_key.rb +24 -0
  93. data/cookbooks/mu-jenkins/templates/default/example_job.config.xml.erb +24 -0
  94. data/cookbooks/mu-jenkins/templates/default/org.jvnet.hudson.plugins.SSHBuildWrapper.xml.erb +14 -0
  95. data/cookbooks/mu-jenkins/templates/default/ssh_config.erb +6 -0
  96. data/cookbooks/mu-master/CHANGELOG.md +13 -0
  97. data/cookbooks/mu-master/LICENSE +37 -0
  98. data/cookbooks/mu-master/README.md +6 -0
  99. data/cookbooks/mu-master/attributes/default.rb +95 -0
  100. data/cookbooks/mu-master/files/default/0-mu-log-server.conf +19 -0
  101. data/cookbooks/mu-master/files/default/addRSA.ldif +8 -0
  102. data/cookbooks/mu-master/files/default/check_mem.pl +197 -0
  103. data/cookbooks/mu-master/files/default/cloudamatic.png +0 -0
  104. data/cookbooks/mu-master/files/default/dirsrv_admin.pp +0 -0
  105. data/cookbooks/mu-master/files/default/dirsrv_admin.te +13 -0
  106. data/cookbooks/mu-master/files/default/nagios_selinux.pp +0 -0
  107. data/cookbooks/mu-master/files/default/nagios_selinux.te +51 -0
  108. data/cookbooks/mu-master/files/default/nagios_selinux_7.pp +0 -0
  109. data/cookbooks/mu-master/files/default/nagios_selinux_7.te +17 -0
  110. data/cookbooks/mu-master/files/default/pam_sshd +18 -0
  111. data/cookbooks/mu-master/files/default/ssl_enable.ldif +18 -0
  112. data/cookbooks/mu-master/files/default/syslogd_oddjobd.pp +0 -0
  113. data/cookbooks/mu-master/files/default/syslogd_oddjobd.te +10 -0
  114. data/cookbooks/mu-master/files/default/vimrc +19 -0
  115. data/cookbooks/mu-master/libraries/mu.rb +29 -0
  116. data/cookbooks/mu-master/metadata.rb +30 -0
  117. data/cookbooks/mu-master/providers/user.rb +41 -0
  118. data/cookbooks/mu-master/recipes/389ds.rb +164 -0
  119. data/cookbooks/mu-master/recipes/basepackages.rb +58 -0
  120. data/cookbooks/mu-master/recipes/caching_nameserver.rb +37 -0
  121. data/cookbooks/mu-master/recipes/default.rb +451 -0
  122. data/cookbooks/mu-master/recipes/eks-kubectl.rb +41 -0
  123. data/cookbooks/mu-master/recipes/firewall-holes.rb +70 -0
  124. data/cookbooks/mu-master/recipes/init.rb +542 -0
  125. data/cookbooks/mu-master/recipes/ssl-certs.rb +109 -0
  126. data/cookbooks/mu-master/recipes/sssd.rb +89 -0
  127. data/cookbooks/mu-master/recipes/update_nagios_only.rb +242 -0
  128. data/cookbooks/mu-master/recipes/vault.rb +111 -0
  129. data/cookbooks/mu-master/resources/user.rb +19 -0
  130. data/cookbooks/mu-master/templates/default/389-directory-setup.inf.erb +28 -0
  131. data/cookbooks/mu-master/templates/default/chef-server.rb.erb +18 -0
  132. data/cookbooks/mu-master/templates/default/dhclient-eth0.conf.erb +9 -0
  133. data/cookbooks/mu-master/templates/default/mu-momma-cat.erb +149 -0
  134. data/cookbooks/mu-master/templates/default/mu.rc.erb +9 -0
  135. data/cookbooks/mu-master/templates/default/openssl.cnf.erb +354 -0
  136. data/cookbooks/mu-master/templates/default/sssd.conf.erb +44 -0
  137. data/cookbooks/mu-master/templates/default/web_app.conf.erb +90 -0
  138. data/cookbooks/mu-mongo/CHANGELOG.md +13 -0
  139. data/cookbooks/mu-mongo/LICENSE +37 -0
  140. data/cookbooks/mu-mongo/README.md +5 -0
  141. data/cookbooks/mu-mongo/attributes/default.rb +22 -0
  142. data/cookbooks/mu-mongo/files/default/keyfile +16 -0
  143. data/cookbooks/mu-mongo/files/default/remove_nodes.js +5 -0
  144. data/cookbooks/mu-mongo/metadata.rb +17 -0
  145. data/cookbooks/mu-mongo/recipes/default.rb +149 -0
  146. data/cookbooks/mu-mongo/recipes/yum-update-rule.rb +18 -0
  147. data/cookbooks/mu-mongo/templates/default/mongo_create_openfema_db.js.erb +2 -0
  148. data/cookbooks/mu-mongo/templates/default/mongo_init.js.erb +1 -0
  149. data/cookbooks/mu-mongo/templates/default/mongo_logrotate.erb +14 -0
  150. data/cookbooks/mu-mongo/templates/default/mongo_replset_addnodes.js.erb +6 -0
  151. data/cookbooks/mu-mongo/templates/default/replset_init.js.erb +2 -0
  152. data/cookbooks/mu-openvpn/CHANGELOG.md +13 -0
  153. data/cookbooks/mu-openvpn/LICENSE +37 -0
  154. data/cookbooks/mu-openvpn/README.md +6 -0
  155. data/cookbooks/mu-openvpn/attributes/default.rb +119 -0
  156. data/cookbooks/mu-openvpn/metadata.rb +18 -0
  157. data/cookbooks/mu-openvpn/recipes/default.rb +108 -0
  158. data/cookbooks/mu-openvpn/templates/default/users.json.erb +42 -0
  159. data/cookbooks/mu-php54/CHANGELOG.md +12 -0
  160. data/cookbooks/mu-php54/LICENSE +37 -0
  161. data/cookbooks/mu-php54/README.md +0 -0
  162. data/cookbooks/mu-php54/files/centos/php.ini +1802 -0
  163. data/cookbooks/mu-php54/files/ubuntu/php.ini +1870 -0
  164. data/cookbooks/mu-php54/metadata.rb +21 -0
  165. data/cookbooks/mu-php54/recipes/default.rb +97 -0
  166. data/cookbooks/mu-splunk/CHANGELOG.md +37 -0
  167. data/cookbooks/mu-splunk/LICENSE +37 -0
  168. data/cookbooks/mu-splunk/README.md +451 -0
  169. data/cookbooks/mu-splunk/attributes/default.rb +95 -0
  170. data/cookbooks/mu-splunk/attributes/upgrade.rb +49 -0
  171. data/cookbooks/mu-splunk/definitions/splunk_installer.rb +103 -0
  172. data/cookbooks/mu-splunk/files/default/splunk-nocheck +10 -0
  173. data/cookbooks/mu-splunk/libraries/helpers.rb +72 -0
  174. data/cookbooks/mu-splunk/libraries/splunk_app_provider.rb +156 -0
  175. data/cookbooks/mu-splunk/libraries/splunk_app_resource.rb +43 -0
  176. data/cookbooks/mu-splunk/metadata.json +30 -0
  177. data/cookbooks/mu-splunk/metadata.rb +17 -0
  178. data/cookbooks/mu-splunk/recipes/client.rb +143 -0
  179. data/cookbooks/mu-splunk/recipes/default.rb +31 -0
  180. data/cookbooks/mu-splunk/recipes/disabled.rb +41 -0
  181. data/cookbooks/mu-splunk/recipes/install_forwarder.rb +23 -0
  182. data/cookbooks/mu-splunk/recipes/install_server.rb +23 -0
  183. data/cookbooks/mu-splunk/recipes/server.rb +53 -0
  184. data/cookbooks/mu-splunk/recipes/service.rb +95 -0
  185. data/cookbooks/mu-splunk/recipes/setup_auth.rb +49 -0
  186. data/cookbooks/mu-splunk/recipes/setup_ssl.rb +63 -0
  187. data/cookbooks/mu-splunk/recipes/upgrade.rb +94 -0
  188. data/cookbooks/mu-splunk/recipes/user.rb +34 -0
  189. data/cookbooks/mu-splunk/templates/default/base_logs_unix_inputs.conf.erb +26 -0
  190. data/cookbooks/mu-splunk/templates/default/inputs.conf.erb +13 -0
  191. data/cookbooks/mu-splunk/templates/default/outputs.conf.erb +9 -0
  192. data/cookbooks/mu-splunk/templates/default/splunk-init.erb +74 -0
  193. data/cookbooks/mu-splunk/templates/default/system-web.conf.erb +7 -0
  194. data/cookbooks/mu-tools/CHANGELOG.md +12 -0
  195. data/cookbooks/mu-tools/LICENSE +37 -0
  196. data/cookbooks/mu-tools/README.md +188 -0
  197. data/cookbooks/mu-tools/attributes/default.rb +142 -0
  198. data/cookbooks/mu-tools/attributes/ebs_rolling_snapshots.rb +3 -0
  199. data/cookbooks/mu-tools/files/amazon/etc/freshclam.conf +235 -0
  200. data/cookbooks/mu-tools/files/centos/CentOS-Base.repo +52 -0
  201. data/cookbooks/mu-tools/files/centos/etc/bashrc +93 -0
  202. data/cookbooks/mu-tools/files/centos/etc/freshclam.conf +235 -0
  203. data/cookbooks/mu-tools/files/centos/etc/login.defs +72 -0
  204. data/cookbooks/mu-tools/files/centos/etc/profile +77 -0
  205. data/cookbooks/mu-tools/files/centos/etc/security/limits.conf +57 -0
  206. data/cookbooks/mu-tools/files/centos/etc/sysconfig/init +19 -0
  207. data/cookbooks/mu-tools/files/centos/etc/sysctl.conf +82 -0
  208. data/cookbooks/mu-tools/files/centos-6/README_MU +0 -0
  209. data/cookbooks/mu-tools/files/centos-6/etc/audit/stig.rules +173 -0
  210. data/cookbooks/mu-tools/files/centos-6/etc/bashrc +90 -0
  211. data/cookbooks/mu-tools/files/centos-6/etc/login.defs +70 -0
  212. data/cookbooks/mu-tools/files/centos-6/etc/pam.d/su +12 -0
  213. data/cookbooks/mu-tools/files/centos-6/etc/profile +83 -0
  214. data/cookbooks/mu-tools/files/centos-6/etc/securetty +12 -0
  215. data/cookbooks/mu-tools/files/centos-6/etc/sysconfig/init +30 -0
  216. data/cookbooks/mu-tools/files/centos-6/etc/sysctl.conf +40 -0
  217. data/cookbooks/mu-tools/files/default/Mu_CA.pem +34 -0
  218. data/cookbooks/mu-tools/files/default/PSWindowsUpdate.zip +0 -0
  219. data/cookbooks/mu-tools/files/default/ebs_snapshots.py +123 -0
  220. data/cookbooks/mu-tools/files/default/etc/BANNER +0 -0
  221. data/cookbooks/mu-tools/files/default/etc/BANNER-FEDERAL +19 -0
  222. data/cookbooks/mu-tools/files/default/gpo_no_uac.zip +0 -0
  223. data/cookbooks/mu-tools/files/default/mypol.pp +0 -0
  224. data/cookbooks/mu-tools/files/default/mypol.te +37 -0
  225. data/cookbooks/mu-tools/files/default/nrpe_c7.pp +0 -0
  226. data/cookbooks/mu-tools/files/default/nrpe_c7.te +31 -0
  227. data/cookbooks/mu-tools/files/default/nrpe_check_disk.pp +0 -0
  228. data/cookbooks/mu-tools/files/default/nrpe_check_disk.te +11 -0
  229. data/cookbooks/mu-tools/files/default/nrpe_disk.pp +0 -0
  230. data/cookbooks/mu-tools/files/default/nrpe_disk.te +10 -0
  231. data/cookbooks/mu-tools/files/default/nrpe_file.pp +0 -0
  232. data/cookbooks/mu-tools/files/default/nrpe_file.te +31 -0
  233. data/cookbooks/mu-tools/files/default/ntrights +0 -0
  234. data/cookbooks/mu-tools/files/default/serverclass.conf +18 -0
  235. data/cookbooks/mu-tools/files/default/splunk-apps/base_logs_unix/local/app.conf +1 -0
  236. data/cookbooks/mu-tools/files/default/splunk-apps/base_logs_unix/local/inputs.conf +13 -0
  237. data/cookbooks/mu-tools/files/default/splunk-apps/base_logs_windows/local/app.conf +1 -0
  238. data/cookbooks/mu-tools/files/default/splunk-apps/base_logs_windows/local/inputs.conf +8 -0
  239. data/cookbooks/mu-tools/files/default/sshd_pol.pp +0 -0
  240. data/cookbooks/mu-tools/files/default/sshd_pol.te +32 -0
  241. data/cookbooks/mu-tools/files/redhat/etc/bashrc +93 -0
  242. data/cookbooks/mu-tools/files/redhat/etc/freshclam.conf +235 -0
  243. data/cookbooks/mu-tools/files/redhat/etc/login.defs +72 -0
  244. data/cookbooks/mu-tools/files/redhat/etc/profile +77 -0
  245. data/cookbooks/mu-tools/files/redhat/etc/security/limits.conf +57 -0
  246. data/cookbooks/mu-tools/files/redhat/etc/sysconfig/init +19 -0
  247. data/cookbooks/mu-tools/files/redhat/etc/sysctl.conf +82 -0
  248. data/cookbooks/mu-tools/files/redhat-6/README_MU +0 -0
  249. data/cookbooks/mu-tools/files/redhat-6/etc/audit/stig.rules +173 -0
  250. data/cookbooks/mu-tools/files/redhat-6/etc/bashrc +90 -0
  251. data/cookbooks/mu-tools/files/redhat-6/etc/login.defs +70 -0
  252. data/cookbooks/mu-tools/files/redhat-6/etc/pam.d/su +12 -0
  253. data/cookbooks/mu-tools/files/redhat-6/etc/profile +83 -0
  254. data/cookbooks/mu-tools/files/redhat-6/etc/securetty +12 -0
  255. data/cookbooks/mu-tools/files/redhat-6/etc/sysconfig/init +30 -0
  256. data/cookbooks/mu-tools/files/redhat-6/etc/sysctl.conf +40 -0
  257. data/cookbooks/mu-tools/files/redhat-7.1/etc/freshclam.conf +235 -0
  258. data/cookbooks/mu-tools/files/ubuntu-12.04/etc/bash.bashrc +64 -0
  259. data/cookbooks/mu-tools/files/ubuntu-12.04/etc/common-session +30 -0
  260. data/cookbooks/mu-tools/files/ubuntu-12.04/etc/login.defs +338 -0
  261. data/cookbooks/mu-tools/files/ubuntu-12.04/etc/profile +30 -0
  262. data/cookbooks/mu-tools/files/ubuntu-12.04/etc/security/limits.conf +56 -0
  263. data/cookbooks/mu-tools/files/ubuntu-12.04/etc/sysctl.conf +60 -0
  264. data/cookbooks/mu-tools/libraries/helper.rb +292 -0
  265. data/cookbooks/mu-tools/metadata.rb +28 -0
  266. data/cookbooks/mu-tools/recipes/add_admin_ssh_keys.rb +35 -0
  267. data/cookbooks/mu-tools/recipes/apply_security.rb +440 -0
  268. data/cookbooks/mu-tools/recipes/aws_api.rb +23 -0
  269. data/cookbooks/mu-tools/recipes/base_repositories.rb +31 -0
  270. data/cookbooks/mu-tools/recipes/cisbenchmark.rb +59 -0
  271. data/cookbooks/mu-tools/recipes/clamav.rb +53 -0
  272. data/cookbooks/mu-tools/recipes/cloudinit.rb +58 -0
  273. data/cookbooks/mu-tools/recipes/configure_oracle_tools.rb +81 -0
  274. data/cookbooks/mu-tools/recipes/disable-requiretty.rb +22 -0
  275. data/cookbooks/mu-tools/recipes/ebs_rolling_snapshots.rb +75 -0
  276. data/cookbooks/mu-tools/recipes/efs.rb +70 -0
  277. data/cookbooks/mu-tools/recipes/eks.rb +160 -0
  278. data/cookbooks/mu-tools/recipes/gcloud.rb +98 -0
  279. data/cookbooks/mu-tools/recipes/google_api.rb +25 -0
  280. data/cookbooks/mu-tools/recipes/maldet.rb +67 -0
  281. data/cookbooks/mu-tools/recipes/nagios.rb +19 -0
  282. data/cookbooks/mu-tools/recipes/newclient.rb +23 -0
  283. data/cookbooks/mu-tools/recipes/nrpe.rb +115 -0
  284. data/cookbooks/mu-tools/recipes/python_pip.rb +35 -0
  285. data/cookbooks/mu-tools/recipes/retrieve_application.rb +51 -0
  286. data/cookbooks/mu-tools/recipes/rsyslog.rb +65 -0
  287. data/cookbooks/mu-tools/recipes/set_local_fw.rb +57 -0
  288. data/cookbooks/mu-tools/recipes/set_mu_hostname.rb +81 -0
  289. data/cookbooks/mu-tools/recipes/split_var_partitions.rb +86 -0
  290. data/cookbooks/mu-tools/recipes/splunk-client.rb +69 -0
  291. data/cookbooks/mu-tools/recipes/splunk-server.rb +104 -0
  292. data/cookbooks/mu-tools/recipes/store_inspec_attr.rb +8 -0
  293. data/cookbooks/mu-tools/recipes/updates.rb +96 -0
  294. data/cookbooks/mu-tools/recipes/windows-client.rb +202 -0
  295. data/cookbooks/mu-tools/resources/aws_windows.rb +33 -0
  296. data/cookbooks/mu-tools/resources/disk.rb +88 -0
  297. data/cookbooks/mu-tools/resources/mommacat_request.rb +11 -0
  298. data/cookbooks/mu-tools/resources/scheduled_tasks.rb +29 -0
  299. data/cookbooks/mu-tools/resources/sshd_service.rb +45 -0
  300. data/cookbooks/mu-tools/resources/windows_users.rb +242 -0
  301. data/cookbooks/mu-tools/templates/amazon/sshd_config.erb +168 -0
  302. data/cookbooks/mu-tools/templates/centos-6/sshd_config.erb +212 -0
  303. data/cookbooks/mu-tools/templates/centos-7/sshd_config.erb +215 -0
  304. data/cookbooks/mu-tools/templates/default/0-mu-log-client.conf.erb +13 -0
  305. data/cookbooks/mu-tools/templates/default/conf.maldet.erb +137 -0
  306. data/cookbooks/mu-tools/templates/default/etc_hosts.erb +30 -0
  307. data/cookbooks/mu-tools/templates/default/etc_pamd_password-auth.erb +14 -0
  308. data/cookbooks/mu-tools/templates/default/etc_pamd_system-auth.erb +14 -0
  309. data/cookbooks/mu-tools/templates/default/etc_sysconfig_network.erb +12 -0
  310. data/cookbooks/mu-tools/templates/default/kubeconfig.erb +29 -0
  311. data/cookbooks/mu-tools/templates/default/kubelet.service.erb +35 -0
  312. data/cookbooks/mu-tools/templates/default/maldet_scanall.sh.erb +15 -0
  313. data/cookbooks/mu-tools/templates/default/nrpe.cfg.erb +233 -0
  314. data/cookbooks/mu-tools/templates/redhat-6/sshd_config.erb +213 -0
  315. data/cookbooks/mu-tools/templates/redhat-7/sshd_config.erb +215 -0
  316. data/cookbooks/mu-tools/templates/ubuntu-12.04/sshd_config.erb +146 -0
  317. data/cookbooks/mu-tools/templates/ubuntu-14.04/sshd_config.erb +145 -0
  318. data/cookbooks/mu-tools/templates/windows/Backup.xml.erb +20 -0
  319. data/cookbooks/mu-tools/templates/windows/bkupInfo.xml.erb +1 -0
  320. data/cookbooks/mu-tools/templates/windows/gpreprt.xml.erb +214 -0
  321. data/cookbooks/mu-tools/templates/windows/gptmpl.inf.erb +12 -0
  322. data/cookbooks/mu-tools/templates/windows/manifest.xml.erb +1 -0
  323. data/cookbooks/mu-tools/templates/windows/set_ad_dns_scheduled_task.ps1.erb +6 -0
  324. data/cookbooks/mu-tools/templates/windows/sshd_config.erb +136 -0
  325. data/cookbooks/mu-utility/CHANGELOG.md +12 -0
  326. data/cookbooks/mu-utility/LICENSE +37 -0
  327. data/cookbooks/mu-utility/README.md +6 -0
  328. data/cookbooks/mu-utility/attributes/default.rb +1 -0
  329. data/cookbooks/mu-utility/libraries/matchers.rb +21 -0
  330. data/cookbooks/mu-utility/metadata.rb +16 -0
  331. data/cookbooks/mu-utility/recipes/apt.rb +23 -0
  332. data/cookbooks/mu-utility/recipes/cleanup_image_helper.rb +118 -0
  333. data/cookbooks/mu-utility/recipes/iptables.rb +26 -0
  334. data/cookbooks/mu-utility/recipes/luks.rb +18 -0
  335. data/cookbooks/mu-utility/recipes/nat.rb +104 -0
  336. data/cookbooks/mu-utility/recipes/php.rb +33 -0
  337. data/cookbooks/mu-utility/recipes/rdp_gateway.rb +83 -0
  338. data/cookbooks/mu-utility/recipes/remi.rb +44 -0
  339. data/cookbooks/mu-utility/recipes/vim.rb +26 -0
  340. data/cookbooks/mu-utility/recipes/windows_basics.rb +37 -0
  341. data/cookbooks/mu-utility/recipes/zip.rb +26 -0
  342. data/cookbooks/mu-utility/templates/default/BundleConfig.xml.erb +34 -0
  343. data/cookbooks/mu-utility/templates/default/config.xml.erb +60 -0
  344. data/cookbooks/nagios/Berksfile +8 -0
  345. data/cookbooks/nagios/CHANGELOG.md +589 -0
  346. data/cookbooks/nagios/CONTRIBUTING.md +11 -0
  347. data/cookbooks/nagios/LICENSE +37 -0
  348. data/cookbooks/nagios/README.md +328 -0
  349. data/cookbooks/nagios/TESTING.md +2 -0
  350. data/cookbooks/nagios/attributes/config.rb +171 -0
  351. data/cookbooks/nagios/attributes/default.rb +228 -0
  352. data/cookbooks/nagios/chefignore +102 -0
  353. data/cookbooks/nagios/definitions/command.rb +33 -0
  354. data/cookbooks/nagios/definitions/contact.rb +33 -0
  355. data/cookbooks/nagios/definitions/contactgroup.rb +33 -0
  356. data/cookbooks/nagios/definitions/host.rb +33 -0
  357. data/cookbooks/nagios/definitions/hostdependency.rb +33 -0
  358. data/cookbooks/nagios/definitions/hostescalation.rb +34 -0
  359. data/cookbooks/nagios/definitions/hostgroup.rb +33 -0
  360. data/cookbooks/nagios/definitions/nagios_conf.rb +38 -0
  361. data/cookbooks/nagios/definitions/resource.rb +33 -0
  362. data/cookbooks/nagios/definitions/service.rb +33 -0
  363. data/cookbooks/nagios/definitions/servicedependency.rb +33 -0
  364. data/cookbooks/nagios/definitions/serviceescalation.rb +34 -0
  365. data/cookbooks/nagios/definitions/servicegroup.rb +33 -0
  366. data/cookbooks/nagios/definitions/timeperiod.rb +33 -0
  367. data/cookbooks/nagios/libraries/base.rb +314 -0
  368. data/cookbooks/nagios/libraries/command.rb +91 -0
  369. data/cookbooks/nagios/libraries/contact.rb +230 -0
  370. data/cookbooks/nagios/libraries/contactgroup.rb +112 -0
  371. data/cookbooks/nagios/libraries/custom_option.rb +36 -0
  372. data/cookbooks/nagios/libraries/data_bag_helper.rb +23 -0
  373. data/cookbooks/nagios/libraries/default.rb +90 -0
  374. data/cookbooks/nagios/libraries/host.rb +412 -0
  375. data/cookbooks/nagios/libraries/hostdependency.rb +181 -0
  376. data/cookbooks/nagios/libraries/hostescalation.rb +173 -0
  377. data/cookbooks/nagios/libraries/hostgroup.rb +119 -0
  378. data/cookbooks/nagios/libraries/nagios.rb +282 -0
  379. data/cookbooks/nagios/libraries/resource.rb +59 -0
  380. data/cookbooks/nagios/libraries/service.rb +455 -0
  381. data/cookbooks/nagios/libraries/servicedependency.rb +215 -0
  382. data/cookbooks/nagios/libraries/serviceescalation.rb +195 -0
  383. data/cookbooks/nagios/libraries/servicegroup.rb +144 -0
  384. data/cookbooks/nagios/libraries/timeperiod.rb +160 -0
  385. data/cookbooks/nagios/libraries/users_helper.rb +54 -0
  386. data/cookbooks/nagios/metadata.rb +25 -0
  387. data/cookbooks/nagios/recipes/_load_databag_config.rb +153 -0
  388. data/cookbooks/nagios/recipes/_load_default_config.rb +241 -0
  389. data/cookbooks/nagios/recipes/apache.rb +48 -0
  390. data/cookbooks/nagios/recipes/default.rb +204 -0
  391. data/cookbooks/nagios/recipes/nginx.rb +82 -0
  392. data/cookbooks/nagios/recipes/pagerduty.rb +143 -0
  393. data/cookbooks/nagios/recipes/server_package.rb +40 -0
  394. data/cookbooks/nagios/recipes/server_source.rb +164 -0
  395. data/cookbooks/nagios/templates/default/apache2.conf.erb +96 -0
  396. data/cookbooks/nagios/templates/default/cgi.cfg.erb +266 -0
  397. data/cookbooks/nagios/templates/default/commands.cfg.erb +13 -0
  398. data/cookbooks/nagios/templates/default/contacts.cfg.erb +37 -0
  399. data/cookbooks/nagios/templates/default/hostgroups.cfg.erb +25 -0
  400. data/cookbooks/nagios/templates/default/hosts.cfg.erb +15 -0
  401. data/cookbooks/nagios/templates/default/htpasswd.users.erb +6 -0
  402. data/cookbooks/nagios/templates/default/nagios.cfg.erb +22 -0
  403. data/cookbooks/nagios/templates/default/nginx.conf.erb +62 -0
  404. data/cookbooks/nagios/templates/default/pagerduty.cgi.erb +185 -0
  405. data/cookbooks/nagios/templates/default/resource.cfg.erb +27 -0
  406. data/cookbooks/nagios/templates/default/servicedependencies.cfg.erb +15 -0
  407. data/cookbooks/nagios/templates/default/servicegroups.cfg.erb +14 -0
  408. data/cookbooks/nagios/templates/default/services.cfg.erb +14 -0
  409. data/cookbooks/nagios/templates/default/templates.cfg.erb +31 -0
  410. data/cookbooks/nagios/templates/default/timeperiods.cfg.erb +13 -0
  411. data/cookbooks/s3fs/CHANGELOG.md +13 -0
  412. data/cookbooks/s3fs/LICENSE +37 -0
  413. data/cookbooks/s3fs/README.md +6 -0
  414. data/cookbooks/s3fs/attributes/default.rb +15 -0
  415. data/cookbooks/s3fs/files/default/fuse-2.9.3.zip +0 -0
  416. data/cookbooks/s3fs/metadata.rb +16 -0
  417. data/cookbooks/s3fs/recipes/default.rb +91 -0
  418. data/data_bags/demo/app.json +7 -0
  419. data/data_bags/nagios_services/chef.json +6 -0
  420. data/data_bags/nagios_services/linux_diskspace.json +5 -0
  421. data/data_bags/nagios_services/momma_cat.json +6 -0
  422. data/data_bags/nagios_services/mu-master-memory.json +5 -0
  423. data/data_bags/nagios_services/nagios_ui.json +6 -0
  424. data/data_bags/nagios_services/node_ssh.json +6 -0
  425. data/data_bags/nagios_services/ssh.json +6 -0
  426. data/demo/lambda_test.yaml +29 -0
  427. data/environments/DEV.json +8 -0
  428. data/environments/PROD.json +8 -0
  429. data/environments/dev.json +8 -0
  430. data/environments/development.json +8 -0
  431. data/environments/prod.json +8 -0
  432. data/extras/README.md +1 -0
  433. data/extras/admin-role-binding.yaml +16 -0
  434. data/extras/admin-user.yaml +6 -0
  435. data/extras/aws-auth-cm.yaml.erb +12 -0
  436. data/extras/clean-stock-amis +48 -0
  437. data/extras/git-fix-permissions-hook +12 -0
  438. data/extras/gitlab-eks-helper.sh.erb +20 -0
  439. data/extras/image-generators/README.md +2 -0
  440. data/extras/image-generators/aws/centos6.yaml +18 -0
  441. data/extras/image-generators/aws/centos7-govcloud.yaml +24 -0
  442. data/extras/image-generators/aws/centos7.yaml +17 -0
  443. data/extras/image-generators/aws/rhel7.yaml +17 -0
  444. data/extras/image-generators/aws/win2k12.yaml +16 -0
  445. data/extras/image-generators/aws/win2k16.yaml +16 -0
  446. data/extras/image-generators/aws/windows.yaml +18 -0
  447. data/extras/image-generators/gcp/centos6.yaml +17 -0
  448. data/extras/lambda_waf_domain_blacklist.py +103 -0
  449. data/extras/platform_berksfile_base +50 -0
  450. data/extras/ruby_rpm/build.sh +17 -0
  451. data/extras/ruby_rpm/muby.spec +44 -0
  452. data/extras/vault_tools/README.md +6 -0
  453. data/extras/vault_tools/export_vaults.sh +3 -0
  454. data/extras/vault_tools/recreate_vaults.sh +5 -0
  455. data/extras/vault_tools/test_vaults.sh +5 -0
  456. data/install/README.md +8 -0
  457. data/install/cfn_create_mu_master.json +1034 -0
  458. data/install/chef-server.rb.erb +19 -0
  459. data/install/deprecated-bash-library.sh +1891 -0
  460. data/install/images/Usage.png +0 -0
  461. data/install/installer +71 -0
  462. data/install/jenkinskeys.rb +8 -0
  463. data/install/user-dot-murc.erb +14 -0
  464. data/modules/html.erb +19 -0
  465. data/modules/mommacat.ru +426 -0
  466. data/modules/mu/cleanup.rb +339 -0
  467. data/modules/mu/cloud.rb +1446 -0
  468. data/modules/mu/clouds/README.md +201 -0
  469. data/modules/mu/clouds/aws/alarm.rb +319 -0
  470. data/modules/mu/clouds/aws/cache_cluster.rb +1010 -0
  471. data/modules/mu/clouds/aws/collection.rb +373 -0
  472. data/modules/mu/clouds/aws/container_cluster.rb +667 -0
  473. data/modules/mu/clouds/aws/database.rb +1836 -0
  474. data/modules/mu/clouds/aws/dnszone.rb +911 -0
  475. data/modules/mu/clouds/aws/firewall_rule.rb +641 -0
  476. data/modules/mu/clouds/aws/folder.rb +92 -0
  477. data/modules/mu/clouds/aws/function.rb +349 -0
  478. data/modules/mu/clouds/aws/group.rb +251 -0
  479. data/modules/mu/clouds/aws/loadbalancer.rb +888 -0
  480. data/modules/mu/clouds/aws/log.rb +363 -0
  481. data/modules/mu/clouds/aws/msg_queue.rb +480 -0
  482. data/modules/mu/clouds/aws/notification.rb +139 -0
  483. data/modules/mu/clouds/aws/role.rb +656 -0
  484. data/modules/mu/clouds/aws/search_domain.rb +646 -0
  485. data/modules/mu/clouds/aws/server.rb +2294 -0
  486. data/modules/mu/clouds/aws/server_pool.rb +1388 -0
  487. data/modules/mu/clouds/aws/storage_pool.rb +495 -0
  488. data/modules/mu/clouds/aws/user.rb +382 -0
  489. data/modules/mu/clouds/aws/userdata/README.md +4 -0
  490. data/modules/mu/clouds/aws/userdata/linux.erb +179 -0
  491. data/modules/mu/clouds/aws/userdata/windows.erb +278 -0
  492. data/modules/mu/clouds/aws/vpc.rb +1943 -0
  493. data/modules/mu/clouds/aws.rb +1009 -0
  494. data/modules/mu/clouds/cloudformation/alarm.rb +146 -0
  495. data/modules/mu/clouds/cloudformation/cache_cluster.rb +167 -0
  496. data/modules/mu/clouds/cloudformation/collection.rb +117 -0
  497. data/modules/mu/clouds/cloudformation/database.rb +278 -0
  498. data/modules/mu/clouds/cloudformation/dnszone.rb +274 -0
  499. data/modules/mu/clouds/cloudformation/firewall_rule.rb +308 -0
  500. data/modules/mu/clouds/cloudformation/loadbalancer.rb +193 -0
  501. data/modules/mu/clouds/cloudformation/log.rb +170 -0
  502. data/modules/mu/clouds/cloudformation/server.rb +370 -0
  503. data/modules/mu/clouds/cloudformation/server_pool.rb +279 -0
  504. data/modules/mu/clouds/cloudformation/vpc.rb +322 -0
  505. data/modules/mu/clouds/cloudformation.rb +733 -0
  506. data/modules/mu/clouds/docker.rb +30 -0
  507. data/modules/mu/clouds/google/container_cluster.rb +290 -0
  508. data/modules/mu/clouds/google/database.rb +152 -0
  509. data/modules/mu/clouds/google/firewall_rule.rb +267 -0
  510. data/modules/mu/clouds/google/group.rb +164 -0
  511. data/modules/mu/clouds/google/loadbalancer.rb +479 -0
  512. data/modules/mu/clouds/google/server.rb +1510 -0
  513. data/modules/mu/clouds/google/server_pool.rb +274 -0
  514. data/modules/mu/clouds/google/user.rb +266 -0
  515. data/modules/mu/clouds/google/userdata/README.md +4 -0
  516. data/modules/mu/clouds/google/userdata/linux.erb +137 -0
  517. data/modules/mu/clouds/google/userdata/windows.erb +275 -0
  518. data/modules/mu/clouds/google/vpc.rb +890 -0
  519. data/modules/mu/clouds/google.rb +811 -0
  520. data/modules/mu/config/README.md +11 -0
  521. data/modules/mu/config/alarm.rb +271 -0
  522. data/modules/mu/config/cache_cluster.rb +172 -0
  523. data/modules/mu/config/collection.rb +87 -0
  524. data/modules/mu/config/container_cluster.rb +103 -0
  525. data/modules/mu/config/container_cluster.yml +36 -0
  526. data/modules/mu/config/database.rb +458 -0
  527. data/modules/mu/config/database.yml +26 -0
  528. data/modules/mu/config/dnszone.rb +327 -0
  529. data/modules/mu/config/firewall_rule.rb +118 -0
  530. data/modules/mu/config/folder.rb +70 -0
  531. data/modules/mu/config/function.rb +140 -0
  532. data/modules/mu/config/group.rb +64 -0
  533. data/modules/mu/config/loadbalancer.rb +482 -0
  534. data/modules/mu/config/log.rb +47 -0
  535. data/modules/mu/config/log.yml +6 -0
  536. data/modules/mu/config/msg_queue.rb +47 -0
  537. data/modules/mu/config/msg_queue.yml +9 -0
  538. data/modules/mu/config/notification.rb +44 -0
  539. data/modules/mu/config/project.rb +71 -0
  540. data/modules/mu/config/role.rb +102 -0
  541. data/modules/mu/config/search_domain.rb +61 -0
  542. data/modules/mu/config/search_domain.yml +25 -0
  543. data/modules/mu/config/server.rb +587 -0
  544. data/modules/mu/config/server.yml +8 -0
  545. data/modules/mu/config/server_pool.rb +216 -0
  546. data/modules/mu/config/server_pool.yml +71 -0
  547. data/modules/mu/config/storage_pool.rb +145 -0
  548. data/modules/mu/config/user.rb +78 -0
  549. data/modules/mu/config/vpc.rb +743 -0
  550. data/modules/mu/config/vpc.yml +6 -0
  551. data/modules/mu/config.rb +2000 -0
  552. data/modules/mu/defaults/README.md +2 -0
  553. data/modules/mu/defaults/amazon_images.yaml +121 -0
  554. data/modules/mu/defaults/google_images.yaml +16 -0
  555. data/modules/mu/deploy.rb +686 -0
  556. data/modules/mu/groomer.rb +123 -0
  557. data/modules/mu/groomers/README.md +58 -0
  558. data/modules/mu/groomers/chef.rb +1024 -0
  559. data/modules/mu/kittens.rb +11319 -0
  560. data/modules/mu/logger.rb +208 -0
  561. data/modules/mu/master/README.md +27 -0
  562. data/modules/mu/master/chef.rb +471 -0
  563. data/modules/mu/master/ldap.rb +1005 -0
  564. data/modules/mu/master.rb +415 -0
  565. data/modules/mu/mommacat.rb +2703 -0
  566. data/modules/mu-load-config.rb +1 -0
  567. data/modules/mu.rb +724 -0
  568. data/modules/scratchpad.erb +1 -0
  569. data/modules/tests/super_complex_bok.yml +41 -0
  570. data/modules/tests/super_simple_bok.yml +40 -0
  571. data/mu.gemspec +62 -0
  572. data/roles/demo-dbservice-configure.json +19 -0
  573. data/roles/demo-portal-configure.json +19 -0
  574. data/roles/mu-master-jenkins.json +24 -0
  575. data/roles/mu-master-nagios-only.json +13 -0
  576. data/roles/mu-master.json +12 -0
  577. data/roles/mu-node.json +19 -0
  578. data/roles/mu-splunk-server.json +13 -0
  579. data/roles/mu-splunk.json +13 -0
  580. data/test/clean_up.py +25 -0
  581. data/test/demo-test-profile/README.md +3 -0
  582. data/test/demo-test-profile/controls/flask.rb +84 -0
  583. data/test/demo-test-profile/inspec.lock +7 -0
  584. data/test/demo-test-profile/inspec.yml +11 -0
  585. data/test/etco-test-profile/README.md +3 -0
  586. data/test/etco-test-profile/controls/all-in-one.rb +182 -0
  587. data/test/etco-test-profile/inspec.lock +7 -0
  588. data/test/etco-test-profile/inspec.yml +11 -0
  589. data/test/exec_inspec.py +246 -0
  590. data/test/exec_mu_install.py +241 -0
  591. data/test/exec_retry.py +44 -0
  592. data/test/mu-master-test/README.md +3 -0
  593. data/test/mu-master-test/controls/all_in_one.rb +557 -0
  594. data/test/mu-master-test/inspec.lock +3 -0
  595. data/test/mu-master-test/inspec.yml +11 -0
  596. data/test/mu-tools-test/README.md +3 -0
  597. data/test/mu-tools-test/controls/base.rb +265 -0
  598. data/test/mu-tools-test/inspec.lock +3 -0
  599. data/test/mu-tools-test/inspec.yml +8 -0
  600. data/test/simple-server-php-test/README.md +3 -0
  601. data/test/simple-server-php-test/controls/apachephp.rb +25 -0
  602. data/test/simple-server-php-test/controls/example.rb +19 -0
  603. data/test/simple-server-php-test/inspec.lock +7 -0
  604. data/test/simple-server-php-test/inspec.yml +12 -0
  605. data/test/simple-server-rails-test/README.md +3 -0
  606. data/test/simple-server-rails-test/controls/rails.rb +188 -0
  607. data/test/simple-server-rails-test/inspec.lock +7 -0
  608. data/test/simple-server-rails-test/inspec.yml +11 -0
  609. data/test/simple-windows-test/README.md +3 -0
  610. data/test/simple-windows-test/controls/windows.rb +20 -0
  611. data/test/simple-windows-test/inspec.lock +7 -0
  612. data/test/simple-windows-test/inspec.yml +11 -0
  613. data/test/smoke_test.rb +75 -0
  614. data/test/wordpress-test/README.md +3 -0
  615. data/test/wordpress-test/controls/wordpress.rb +97 -0
  616. data/test/wordpress-test/inspec.lock +7 -0
  617. data/test/wordpress-test/inspec.yml +11 -0
  618. metadata +979 -0
@@ -0,0 +1,2294 @@
1
+ # Copyright:: Copyright (c) 2014 eGlobalTech, Inc., all rights reserved
2
+ #
3
+ # Licensed under the BSD-3 license (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License in the root of the project or at
6
+ #
7
+ # http://egt-labs.com/mu/LICENSE.html
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'net/ssh'
16
+ require 'net/ssh/multi'
17
+ require 'net/ssh/proxy/command'
18
+ autoload :OpenStruct, "ostruct"
19
+ autoload :Timeout, "timeout"
20
+ autoload :ERB, "erb"
21
+ autoload :Base64, "base64"
22
+ require 'open-uri'
23
+
24
+ module MU
25
+ class Cloud
26
+ class AWS
27
+
28
+ # A server as configured in {MU::Config::BasketofKittens::servers}
29
+ class Server < MU::Cloud::Server
30
+
31
+ # A list of block device names to use if we get a storage block that
32
+ # doesn't declare one explicitly.
33
+ # This probably fails on some AMIs. It's crude.
34
+ @disk_devices = [
35
+ "/dev/sdf",
36
+ "/dev/sdg",
37
+ "/dev/sdh",
38
+ "/dev/sdi",
39
+ "/dev/sdj",
40
+ "/dev/sdk",
41
+ "/dev/sdl",
42
+ "/dev/sdm",
43
+ "/dev/sdn"
44
+ ]
45
+ # List of standard disk device names to present to instances.
46
+ # @return [Array<String>]
47
+ def self.disk_devices
48
+ @disk_devices
49
+ end
50
+
51
+ # See that we get our ephemeral storage devices with AMIs that don't do it
52
+ # for us
53
+ @ephemeral_mappings = [
54
+ {
55
+ :device_name => "/dev/sdr",
56
+ :virtual_name => "ephemeral0"
57
+ },
58
+ {
59
+ :device_name => "/dev/sds",
60
+ :virtual_name => "ephemeral1"
61
+ },
62
+ {
63
+ :device_name => "/dev/sdt",
64
+ :virtual_name => "ephemeral2"
65
+ },
66
+ {
67
+ :device_name => "/dev/sdu",
68
+ :virtual_name => "ephemeral3"
69
+ }
70
+ ]
71
+ # Ephemeral storage device mappings. Useful for AMIs that don't do this
72
+ # for us.
73
+ # @return [Hash]
74
+ def self.ephemeral_mappings
75
+ @ephemeral_mappings
76
+ end
77
+
78
+ attr_reader :mu_name
79
+ attr_reader :config
80
+ attr_reader :deploy
81
+ attr_reader :cloud_id
82
+ attr_reader :cloud_desc
83
+ attr_reader :groomer
84
+ attr_accessor :mu_windows_name
85
+
86
+ # @param mommacat [MU::MommaCat]: A {MU::Mommacat} object containing the deploy of which this resource is/will be a member.
87
+ # @param kitten_cfg [Hash]: The fully parsed and resolved {MU::Config} resource descriptor as defined in {MU::Config::BasketofKittens::servers}
88
+ def initialize(mommacat: nil, kitten_cfg: nil, mu_name: nil, cloud_id: nil)
89
+ @deploy = mommacat
90
+ @config = MU::Config.manxify(kitten_cfg)
91
+ @cloud_id = cloud_id
92
+
93
+ if @deploy
94
+ @userdata = MU::Cloud.fetchUserdata(
95
+ platform: @config["platform"],
96
+ cloud: "aws",
97
+ template_variables: {
98
+ "deployKey" => Base64.urlsafe_encode64(@deploy.public_key),
99
+ "deploySSHKey" => @deploy.ssh_public_key,
100
+ "muID" => MU.deploy_id,
101
+ "muUser" => MU.mu_user,
102
+ "publicIP" => MU.mu_public_ip,
103
+ "skipApplyUpdates" => @config['skipinitialupdates'],
104
+ "windowsAdminName" => @config['windows_admin_username'],
105
+ "resourceName" => @config["name"],
106
+ "resourceType" => "server",
107
+ "platform" => @config["platform"]
108
+ },
109
+ custom_append: @config['userdata_script']
110
+ )
111
+ end
112
+
113
+ @disk_devices = MU::Cloud::AWS::Server.disk_devices
114
+ @ephemeral_mappings = MU::Cloud::AWS::Server.ephemeral_mappings
115
+
116
+ if !mu_name.nil?
117
+ @mu_name = mu_name
118
+ @config['mu_name'] = @mu_name
119
+ # describe
120
+ @mu_windows_name = @deploydata['mu_windows_name'] if @mu_windows_name.nil? and @deploydata
121
+ else
122
+ if kitten_cfg.has_key?("basis")
123
+ @mu_name = @deploy.getResourceName(@config['name'], need_unique_string: true)
124
+ else
125
+ @mu_name = @deploy.getResourceName(@config['name'])
126
+ end
127
+ @config['mu_name'] = @mu_name
128
+
129
+ @config['instance_secret'] = Password.random(50)
130
+ end
131
+ @groomer = MU::Groomer.new(self)
132
+
133
+ end
134
+
135
+ @@userdata_semaphore = Mutex.new
136
+
137
+ # Fetch our baseline userdata argument (read: "script that runs on first
138
+ # boot") for a given platform.
139
+ # *XXX* both the eval() and the blind File.read() based on the platform
140
+ # variable are dangerous without cleaning. Clean them.
141
+ # @param platform [String]: The target OS.
142
+ # @param template_variables [Hash]: A list of variable substitutions to pass as globals to the ERB parser when loading the userdata script.
143
+ # @param custom_append [String]: Arbitrary extra code to append to our default userdata behavior.
144
+ # @return [String]
145
+ def self.fetchUserdata(platform: "linux", template_variables: {}, custom_append: nil, scrub_mu_isms: false)
146
+ return nil if platform.nil? or platform.empty?
147
+ @@userdata_semaphore.synchronize {
148
+ script = ""
149
+ if !scrub_mu_isms
150
+ if template_variables.nil? or !template_variables.is_a?(Hash)
151
+ raise MuError, "My second argument should be a hash of variables to pass into ERB templates"
152
+ end
153
+ $mu = OpenStruct.new(template_variables)
154
+ userdata_dir = File.expand_path(MU.myRoot+"/modules/mu/clouds/aws/userdata")
155
+ platform = "linux" if %w{centos centos6 centos7 ubuntu ubuntu14 rhel rhel7 rhel71 amazon}.include? platform
156
+ platform = "windows" if %w{win2k12r2 win2k12 win2k8 win2k8r2 win2k16}.include? platform
157
+ erbfile = "#{userdata_dir}/#{platform}.erb"
158
+ if !File.exist?(erbfile)
159
+ MU.log "No such userdata template '#{erbfile}'", MU::WARN, details: caller
160
+ return ""
161
+ end
162
+ userdata = File.read(erbfile)
163
+ begin
164
+ erb = ERB.new(userdata, nil, "<>")
165
+ script = erb.result
166
+ rescue NameError => e
167
+ raise MuError, "Error parsing userdata script #{erbfile} as an ERB template: #{e.inspect}"
168
+ end
169
+ MU.log "Parsed #{erbfile} as ERB", MU::DEBUG, details: script
170
+ end
171
+
172
+ if !custom_append.nil?
173
+ if custom_append['path'].nil?
174
+ raise MuError, "Got a custom userdata script argument, but no ['path'] component"
175
+ end
176
+ erbfile = File.read(custom_append['path'])
177
+ MU.log "Loaded userdata script from #{custom_append['path']}"
178
+ if custom_append['use_erb']
179
+ begin
180
+ erb = ERB.new(erbfile, 1, "<>")
181
+ if custom_append['skip_std']
182
+ script = +erb.result
183
+ else
184
+ script = script+"\n"+erb.result
185
+ end
186
+ rescue NameError => e
187
+ raise MuError, "Error parsing userdata script #{erbfile} as an ERB template: #{e.inspect}"
188
+ end
189
+ MU.log "Parsed #{custom_append['path']} as ERB", MU::DEBUG, details: script
190
+ else
191
+ if custom_append['skip_std']
192
+ script = erbfile
193
+ else
194
+ script = script+"\n"+erbfile
195
+ end
196
+ MU.log "Parsed #{custom_append['path']} as flat file", MU::DEBUG, details: script
197
+ end
198
+ end
199
+ return script
200
+ }
201
+ end
202
+
203
+ # Find volumes attached to a given instance id and tag them. If no arguments
204
+ # besides the instance id are provided, it will add our special MU-ID
205
+ # tag. Can also be used to do things like set the resource's name, if you
206
+ # leverage the other arguments.
207
+ # @param instance_id [String]: The cloud provider's identifier for the parent instance of this volume.
208
+ # @param device [String]: The OS-level device name of the volume.
209
+ # @param tag_name [String]: The name of the tag to attach.
210
+ # @param tag_value [String]: The value of the tag to attach.
211
+ # @param region [String]: The cloud provider region
212
+ # @return [void]
213
+ def self.tagVolumes(instance_id, device: nil, tag_name: "MU-ID", tag_value: MU.deploy_id, region: MU.curRegion)
214
+ MU::Cloud::AWS.ec2(region).describe_volumes(filters: [name: "attachment.instance-id", values: [instance_id]]).each { |vol|
215
+ vol.volumes.each { |volume|
216
+ volume.attachments.each { |attachment|
217
+ vol_parent = attachment.instance_id
218
+ vol_id = attachment.volume_id
219
+ vol_dev = attachment.device
220
+ if vol_parent == instance_id and (vol_dev == device or device.nil?)
221
+ MU::MommaCat.createTag(vol_id, tag_name, tag_value, region: region)
222
+ break
223
+ end
224
+ }
225
+ }
226
+ }
227
+ end
228
+
229
+ # Called automatically by {MU::Deploy#createResources}
230
+ def create
231
+ begin
232
+ done = false
233
+ instance = createEc2Instance
234
+
235
+ @cloud_id = instance.instance_id
236
+ @deploy.saveNodeSecret(@cloud_id, @config['instance_secret'], "instance_secret")
237
+ @config.delete("instance_secret")
238
+
239
+ if !@config['async_groom']
240
+ sleep 5
241
+ MU::MommaCat.lock(instance.instance_id+"-create")
242
+ if !postBoot
243
+ MU.log "#{@config['name']} is already being groomed, skipping", MU::NOTICE
244
+ else
245
+ MU.log "Node creation complete for #{@config['name']}"
246
+ end
247
+ MU::MommaCat.unlock(instance.instance_id+"-create")
248
+ else
249
+ MU::MommaCat.createStandardTags(instance.instance_id, region: @config['region'])
250
+ MU::MommaCat.createTag(instance.instance_id, "Name", @mu_name, region: @config['region'])
251
+ end
252
+ done = true
253
+ rescue Exception => e
254
+ if !instance.nil? and !done
255
+ MU.log "Aborted before I could finish setting up #{@config['name']}, cleaning it up. Stack trace will print once cleanup is complete.", MU::WARN if !@deploy.nocleanup
256
+ MU::MommaCat.unlockAll
257
+ if !@deploy.nocleanup
258
+ parent_thread_id = Thread.current.object_id
259
+ Thread.new {
260
+ MU.dupGlobals(parent_thread_id)
261
+ MU::Cloud::AWS::Server.cleanup(noop: false, ignoremaster: false, skipsnapshots: true)
262
+ }
263
+ end
264
+ end
265
+ raise e
266
+ end
267
+
268
+ return @config
269
+ end
270
+
271
+
272
+
273
+ # Create an Amazon EC2 instance.
274
+ def createEc2Instance
275
+ name = @config["name"]
276
+ node = @config['mu_name']
277
+
278
+ instance_descriptor = {
279
+ :image_id => @config["ami_id"],
280
+ :key_name => @deploy.ssh_key_name,
281
+ :instance_type => @config["size"],
282
+ :disable_api_termination => true,
283
+ :min_count => 1,
284
+ :max_count => 1
285
+ }
286
+
287
+ arn = nil
288
+ if @config['generate_iam_role']
289
+ # @config['iam_role'], @cfm_role_name, @cfm_prof_name, arn = MU::Cloud::AWS::Server.createIAMProfile(@mu_name, base_profile: @config['iam_role'], extra_policies: @config['iam_policies'])
290
+ role = @deploy.findLitterMate(name: @config['name'], type: "roles")
291
+ s3_objs = ["#{@deploy.deploy_id}-secret", "#{role.mu_name}.pfx", "#{role.mu_name}.crt", "#{role.mu_name}.key", "#{role.mu_name}-winrm.crt", "#{role.mu_name}-winrm.key"].map { |file|
292
+ 'arn:'+(MU::Cloud::AWS.isGovCloud?(@config['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU.adminBucketName+'/'+file
293
+ }
294
+ role.cloudobj.injectPolicyTargets("MuSecrets", s3_objs)
295
+
296
+ @config['iam_role'] = role.mu_name
297
+ arn = role.cloudobj.createInstanceProfile
298
+ # @cfm_role_name, @cfm_prof_name
299
+
300
+ elsif @config['iam_role'].nil?
301
+ raise MuError, "#{@mu_name} has generate_iam_role set to false, but no iam_role assigned."
302
+ end
303
+ if !@config["iam_role"].nil?
304
+ if arn
305
+ instance_descriptor[:iam_instance_profile] = {arn: arn}
306
+ else
307
+ instance_descriptor[:iam_instance_profile] = {name: @config["iam_role"]}
308
+ end
309
+ end
310
+
311
+ security_groups = []
312
+ if @dependencies.has_key?("firewall_rule")
313
+ @dependencies['firewall_rule'].values.each { |sg|
314
+ security_groups << sg.cloud_id
315
+ }
316
+ end
317
+
318
+ if security_groups.size > 0
319
+ instance_descriptor[:security_group_ids] = security_groups
320
+ else
321
+ raise MuError, "Didn't get any security groups assigned to be in #{@mu_name}, that shouldn't happen"
322
+ end
323
+
324
+ if !@config['private_ip'].nil?
325
+ instance_descriptor[:private_ip_address] = @config['private_ip']
326
+ end
327
+
328
+ vpc_id = subnet = nil
329
+ if !@vpc.nil? and @config.has_key?("vpc")
330
+ subnet_conf = @config['vpc']
331
+ subnet_conf = @config['vpc']['subnets'].first if @config['vpc'].has_key?("subnets") and !@config['vpc']['subnets'].empty?
332
+ tag_key, tag_value = subnet_conf['tag'].split(/=/, 2) if !subnet_conf['tag'].nil?
333
+
334
+ subnet = @vpc.getSubnet(
335
+ cloud_id: subnet_conf['subnet_id'],
336
+ name: subnet_conf['subnet_name'],
337
+ tag_key: tag_key,
338
+ tag_value: tag_value
339
+ )
340
+ if subnet.nil?
341
+ raise MuError, "Got null subnet id out of #{subnet_conf['vpc']}"
342
+ end
343
+ MU.log "Deploying #{node} into VPC #{@vpc.cloud_id} Subnet #{subnet.cloud_id}"
344
+ punchAdminNAT
345
+ instance_descriptor[:subnet_id] = subnet.cloud_id
346
+ end
347
+
348
+ if !@userdata.nil? and !@userdata.empty?
349
+ instance_descriptor[:user_data] = Base64.encode64(@userdata)
350
+ end
351
+
352
+ MU::Cloud::AWS::Server.waitForAMI(@config["ami_id"], region: @config['region'])
353
+
354
+ # Figure out which devices are embedded in the AMI already.
355
+ image = MU::Cloud::AWS.ec2(@config['region']).describe_images(image_ids: [@config["ami_id"]]).images.first
356
+ ext_disks = {}
357
+ if !image.block_device_mappings.nil?
358
+ image.block_device_mappings.each { |disk|
359
+ if !disk.device_name.nil? and !disk.device_name.empty? and !disk.ebs.nil? and !disk.ebs.empty?
360
+ ext_disks[disk.device_name] = MU.structToHash(disk.ebs)
361
+ end
362
+ }
363
+ end
364
+
365
+ configured_storage = Array.new
366
+ cfm_volume_map = {}
367
+ if @config["storage"]
368
+ @config["storage"].each { |vol|
369
+ # Drop the "encrypted" flag if a snapshot for this device exists
370
+ # in the AMI, even if they both agree about the value of said
371
+ # flag. Apparently that's a thing now.
372
+ if ext_disks.has_key?(vol["device"])
373
+ if ext_disks[vol["device"]].has_key?(:snapshot_id)
374
+ vol.delete("encrypted")
375
+ end
376
+ end
377
+ mapping, cfm_mapping = MU::Cloud::AWS::Server.convertBlockDeviceMapping(vol)
378
+ configured_storage << mapping
379
+ }
380
+ end
381
+
382
+ instance_descriptor[:block_device_mappings] = configured_storage
383
+ instance_descriptor[:block_device_mappings].concat(@ephemeral_mappings)
384
+ instance_descriptor[:monitoring] = {enabled: @config['monitoring']}
385
+
386
+ MU.log "Creating EC2 instance #{node}"
387
+ MU.log "Instance details for #{node}: #{instance_descriptor}", MU::DEBUG
388
+ # if instance_descriptor[:block_device_mappings].empty?
389
+ # instance_descriptor.delete(:block_device_mappings)
390
+ # end
391
+
392
+ retries = 0
393
+ begin
394
+ response = MU::Cloud::AWS.ec2(@config['region']).run_instances(instance_descriptor)
395
+ rescue Aws::EC2::Errors::InvalidGroupNotFound, Aws::EC2::Errors::InvalidSubnetIDNotFound, Aws::EC2::Errors::InvalidParameterValue => e
396
+ if retries < 10
397
+ if retries > 7
398
+ MU.log "Seeing #{e.inspect} while trying to launch #{node}, retrying a few more times...", MU::WARN, details: instance_descriptor
399
+ end
400
+ sleep 10
401
+ retries = retries + 1
402
+ retry
403
+ else
404
+ raise MuError, e.inspect
405
+ end
406
+ end
407
+
408
+ instance = response.instances.first
409
+ MU.log "#{node} (#{instance.instance_id}) coming online"
410
+
411
+ return instance
412
+
413
+ end
414
+
415
+ # Ask the Amazon API to restart this node
416
+ def reboot(hard = false)
417
+ return if @cloud_id.nil?
418
+
419
+ if hard
420
+ groupname = nil
421
+ if !@config['basis'].nil?
422
+ resp = MU::Cloud::AWS.autoscale(@config['region']).describe_auto_scaling_instances(
423
+ instance_ids: [@cloud_id]
424
+ )
425
+ groupname = resp.auto_scaling_instances.first.auto_scaling_group_name
426
+ MU.log "Pausing Autoscale processes in #{groupname}", MU::NOTICE
427
+ MU::Cloud::AWS.autoscale(@config['region']).suspend_processes(
428
+ auto_scaling_group_name: groupname
429
+ )
430
+ end
431
+ begin
432
+ MU.log "Stopping #{@mu_name} (#{@cloud_id})", MU::NOTICE
433
+ MU::Cloud::AWS.ec2(@config['region']).stop_instances(
434
+ instance_ids: [@cloud_id]
435
+ )
436
+ MU::Cloud::AWS.ec2(@config['region']).wait_until(:instance_stopped, instance_ids: [@cloud_id]) do |waiter|
437
+ waiter.before_attempt do |attempts|
438
+ MU.log "Waiting for #{@mu_name} to stop for hard reboot"
439
+ end
440
+ end
441
+ MU.log "Starting #{@mu_name} (#{@cloud_id})"
442
+ MU::Cloud::AWS.ec2(@config['region']).start_instances(
443
+ instance_ids: [@cloud_id]
444
+ )
445
+ ensure
446
+ if !groupname.nil?
447
+ MU.log "Resuming Autoscale processes in #{groupname}", MU::NOTICE
448
+ MU::Cloud::AWS.autoscale(@config['region']).resume_processes(
449
+ auto_scaling_group_name: groupname
450
+ )
451
+ end
452
+ end
453
+ else
454
+ MU.log "Rebooting #{@mu_name} (#{@cloud_id})"
455
+ MU::Cloud::AWS.ec2(@config['region']).reboot_instances(
456
+ instance_ids: [@cloud_id]
457
+ )
458
+ end
459
+ end
460
+
461
+ # Figure out what's needed to SSH into this server.
462
+ # @return [Array<String>]: nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name, alternate_names
463
+ def getSSHConfig
464
+ node, config, deploydata = describe(cloud_id: @cloud_id)
465
+ # XXX add some awesome alternate names from metadata and make sure they end
466
+ # up in MU::MommaCat's ssh config wangling
467
+ ssh_keydir = Etc.getpwuid(Process.uid).dir+"/.ssh"
468
+ return nil if @config.nil? or @deploy.nil?
469
+
470
+ nat_ssh_key = nat_ssh_user = nat_ssh_host = nil
471
+ if !@config["vpc"].nil? and !MU::Cloud::AWS::VPC.haveRouteToInstance?(cloud_desc, region: @config['region'])
472
+ if !@nat.nil?
473
+ if @nat.is_a?(Struct) && @nat.nat_gateway_id && @nat.nat_gateway_id.start_with?("nat-")
474
+ raise MuError, "Configured to use NAT Gateway, but I have no route to instance. Either use Bastion, or configure VPC peering"
475
+ end
476
+
477
+ if @nat.cloud_desc.nil?
478
+ MU.log "NAT was missing cloud descriptor when called in #{@mu_name}'s getSSHConfig", MU::ERR
479
+ return nil
480
+ end
481
+ # XXX Yanking these things from the cloud descriptor will only work in AWS!
482
+
483
+ nat_ssh_key = @nat.cloud_desc.key_name
484
+ nat_ssh_key = @config["vpc"]["nat_ssh_key"] if !@config["vpc"]["nat_ssh_key"].nil?
485
+ nat_ssh_host = @nat.cloud_desc.public_ip_address
486
+ nat_ssh_user = @config["vpc"]["nat_ssh_user"]
487
+ if nat_ssh_user.nil? and !nat_ssh_host.nil?
488
+ MU.log "#{@config["name"]} (#{MU.deploy_id}) is configured to use #{@config['vpc']} NAT #{nat_ssh_host}, but username isn't specified. Guessing root.", MU::ERR, details: caller
489
+ nat_ssh_user = "root"
490
+ end
491
+ end
492
+ end
493
+
494
+ if @config['ssh_user'].nil?
495
+ if windows?
496
+ @config['ssh_user'] = "Administrator"
497
+ else
498
+ @config['ssh_user'] = "root"
499
+ end
500
+ end
501
+
502
+ return [nat_ssh_key, nat_ssh_user, nat_ssh_host, canonicalIP, @config['ssh_user'], @deploy.ssh_key_name]
503
+
504
+ end
505
+
506
+ # Apply tags, bootstrap our configuration management, and other
507
+ # administravia for a new instance.
508
+ def postBoot(instance_id = nil)
509
+ if !instance_id.nil?
510
+ @cloud_id = instance_id
511
+ end
512
+ node, config, deploydata = describe(cloud_id: @cloud_id)
513
+ instance = cloud_desc
514
+ raise MuError, "Couldn't find instance #{@mu_name} (#{@cloud_id})" if !instance
515
+ @cloud_id = instance.instance_id
516
+ return false if !MU::MommaCat.lock(instance.instance_id+"-orchestrate", true)
517
+ return false if !MU::MommaCat.lock(instance.instance_id+"-groom", true)
518
+
519
+ MU::MommaCat.createStandardTags(instance.instance_id, region: @config['region'])
520
+ MU::MommaCat.createTag(instance.instance_id, "Name", node, region: @config['region'])
521
+
522
+ if @config['optional_tags']
523
+ MU::MommaCat.listOptionalTags.each { |key, value|
524
+ MU::MommaCat.createTag(instance.instance_id, key, value, region: @config['region'])
525
+ }
526
+ end
527
+
528
+ if !@config['tags'].nil?
529
+ @config['tags'].each { |tag|
530
+ MU::MommaCat.createTag(instance.instance_id, tag['key'], tag['value'], region: @config['region'])
531
+ }
532
+ end
533
+ MU.log "Tagged #{node} (#{instance.instance_id}) with MU-ID=#{MU.deploy_id}", MU::DEBUG
534
+
535
+ # Make double sure we don't lose a cached mu_windows_name value.
536
+ if windows? or !@config['active_directory'].nil?
537
+ if @mu_windows_name.nil?
538
+ @mu_windows_name = deploydata['mu_windows_name']
539
+ end
540
+ end
541
+
542
+ retries = -1
543
+ max_retries = 30
544
+ begin
545
+ if instance.nil? or instance.state.name != "running"
546
+ retries = retries + 1
547
+ if !instance.nil? and instance.state.name == "terminated"
548
+ raise MuError, "#{@cloud_id} appears to have been terminated mid-bootstrap!"
549
+ end
550
+ if retries % 3 == 0
551
+ MU.log "Waiting for EC2 instance #{node} (#{@cloud_id}) to be ready...", MU::NOTICE
552
+ end
553
+ sleep 40
554
+ # Get a fresh AWS descriptor
555
+ instance = MU::Cloud::Server.find(cloud_id: @cloud_id, region: @config['region']).values.first
556
+ if instance and instance.state.name == "terminated"
557
+ raise MuError, "EC2 instance #{node} (#{@cloud_id}) terminating during bootstrap!"
558
+ end
559
+ end
560
+ rescue Aws::EC2::Errors::ServiceError => e
561
+ if retries < max_retries
562
+ MU.log "Got #{e.inspect} during initial instance creation of #{@cloud_id}, retrying...", MU::NOTICE, details: instance
563
+ retries = retries + 1
564
+ retry
565
+ else
566
+ raise MuError, "Too many retries creating #{node} (#{e.inspect})"
567
+ end
568
+ end while instance.nil? or (instance.state.name != "running" and retries < max_retries)
569
+
570
+ punchAdminNAT
571
+
572
+
573
+ # If we came up via AutoScale, the Alarm module won't have had our
574
+ # instance ID to associate us with itself. So invoke that here.
575
+ if !@config['basis'].nil? and @config["alarms"] and !@config["alarms"].empty?
576
+ @config["alarms"].each { |alarm|
577
+ alarm_obj = MU::MommaCat.findStray(
578
+ "AWS",
579
+ "alarms",
580
+ region: @config["region"],
581
+ deploy_id: @deploy.deploy_id,
582
+ name: alarm['name']
583
+ ).first
584
+ alarm["dimensions"] = [{:name => "InstanceId", :value => @cloud_id}]
585
+
586
+ if alarm["enable_notifications"]
587
+ topic_arn = MU::Cloud::AWS::Notification.createTopic(alarm["notification_group"], region: @config["region"])
588
+ MU::Cloud::AWS::Notification.subscribe(arn: topic_arn, protocol: alarm["notification_type"], endpoint: alarm["notification_endpoint"], region: @config["region"])
589
+ alarm["alarm_actions"] = [topic_arn]
590
+ alarm["ok_actions"] = [topic_arn]
591
+ end
592
+
593
+ alarm_name = alarm_obj ? alarm_obj.cloud_id : "#{node}-#{alarm['name']}".upcase
594
+
595
+ MU::Cloud::AWS::Alarm.setAlarm(
596
+ name: alarm_name,
597
+ ok_actions: alarm["ok_actions"],
598
+ alarm_actions: alarm["alarm_actions"],
599
+ insufficient_data_actions: alarm["no_data_actions"],
600
+ metric_name: alarm["metric_name"],
601
+ namespace: alarm["namespace"],
602
+ statistic: alarm["statistic"],
603
+ dimensions: alarm["dimensions"],
604
+ period: alarm["period"],
605
+ unit: alarm["unit"],
606
+ evaluation_periods: alarm["evaluation_periods"],
607
+ threshold: alarm["threshold"],
608
+ comparison_operator: alarm["comparison_operator"],
609
+ region: @config["region"]
610
+ )
611
+ }
612
+ end
613
+
614
+ # We have issues sometimes where our dns_records are pointing at the wrong node name and IP address.
615
+ # Make sure that doesn't happen. Happens with server pools only
616
+ if @config['dns_records'] && !@config['dns_records'].empty?
617
+ @config['dns_records'].each { |dnsrec|
618
+ if dnsrec.has_key?("name")
619
+ if dnsrec['name'].start_with?(MU.deploy_id.downcase) && !dnsrec['name'].start_with?(node.downcase)
620
+ MU.log "DNS records for #{node} seem to be wrong, deleting from current config", MU::WARN, details: dnsrec
621
+ dnsrec.delete('name')
622
+ dnsrec.delete('target')
623
+ end
624
+ end
625
+ }
626
+ end
627
+
628
+ # Unless we're planning on associating a different IP later, set up a
629
+ # DNS entry for this thing and let it sync in the background. We'll come
630
+ # back to it later.
631
+ if @config['static_ip'].nil? && !@named
632
+ MU::MommaCat.nameKitten(self)
633
+ @named = true
634
+ end
635
+
636
+ if !@config['src_dst_check'] and !@config["vpc"].nil?
637
+ MU.log "Disabling source_dest_check #{node} (making it NAT-worthy)"
638
+ MU::Cloud::AWS.ec2(@config['region']).modify_instance_attribute(
639
+ instance_id: @cloud_id,
640
+ source_dest_check: {:value => false}
641
+ )
642
+ end
643
+
644
+ # Set console termination protection. Autoscale nodes won't set this
645
+ # by default.
646
+ MU::Cloud::AWS.ec2(@config['region']).modify_instance_attribute(
647
+ instance_id: @cloud_id,
648
+ disable_api_termination: {:value => true}
649
+ )
650
+
651
+ has_elastic_ip = false
652
+ if !instance.public_ip_address.nil?
653
+ begin
654
+ resp = MU::Cloud::AWS.ec2((@config['region'])).describe_addresses(public_ips: [instance.public_ip_address])
655
+ if resp.addresses.size > 0 and resp.addresses.first.instance_id == @cloud_id
656
+ has_elastic_ip = true
657
+ end
658
+ rescue Aws::EC2::Errors::InvalidAddressNotFound => e
659
+ # XXX this is ok to ignore, it means the public IP isn't Elastic
660
+ end
661
+ end
662
+
663
+ win_admin_password = nil
664
+ ec2config_password = nil
665
+ sshd_password = nil
666
+ if windows?
667
+ ssh_keydir = "#{Etc.getpwuid(Process.uid).dir}/.ssh"
668
+ ssh_key_name = @deploy.ssh_key_name
669
+
670
+ if @config['use_cloud_provider_windows_password']
671
+ win_admin_password = getWindowsAdminPassword
672
+ elsif @config['windows_auth_vault'] && !@config['windows_auth_vault'].empty?
673
+ if @config["windows_auth_vault"].has_key?("password_field")
674
+ win_admin_password = @groomer.getSecret(
675
+ vault: @config['windows_auth_vault']['vault'],
676
+ item: @config['windows_auth_vault']['item'],
677
+ field: @config["windows_auth_vault"]["password_field"]
678
+ )
679
+ else
680
+ win_admin_password = getWindowsAdminPassword
681
+ end
682
+
683
+ if @config["windows_auth_vault"].has_key?("ec2config_password_field")
684
+ ec2config_password = @groomer.getSecret(
685
+ vault: @config['windows_auth_vault']['vault'],
686
+ item: @config['windows_auth_vault']['item'],
687
+ field: @config["windows_auth_vault"]["ec2config_password_field"]
688
+ )
689
+ end
690
+
691
+ if @config["windows_auth_vault"].has_key?("sshd_password_field")
692
+ sshd_password = @groomer.getSecret(
693
+ vault: @config['windows_auth_vault']['vault'],
694
+ item: @config['windows_auth_vault']['item'],
695
+ field: @config["windows_auth_vault"]["sshd_password_field"]
696
+ )
697
+ end
698
+ end
699
+
700
+ win_admin_password = MU.generateWindowsPassword if win_admin_password.nil?
701
+ ec2config_password = MU.generateWindowsPassword if ec2config_password.nil?
702
+ sshd_password = MU.generateWindowsPassword if sshd_password.nil?
703
+
704
+ # We're creating the vault here so when we run
705
+ # MU::Cloud::Server.initialSSHTasks and we need to set the Windows
706
+ # Admin password we can grab it from said vault.
707
+ creds = {
708
+ "username" => @config['windows_admin_username'],
709
+ "password" => win_admin_password,
710
+ "ec2config_username" => "ec2config",
711
+ "ec2config_password" => ec2config_password,
712
+ "sshd_username" => "sshd_service",
713
+ "sshd_password" => sshd_password
714
+ }
715
+ @groomer.saveSecret(vault: @mu_name, item: "windows_credentials", data: creds, permissions: "name:#{@mu_name}")
716
+ end
717
+
718
+ subnet = nil
719
+ if !@vpc.nil? and @config.has_key?("vpc") and !instance.subnet_id.nil?
720
+ subnet = @vpc.getSubnet(
721
+ cloud_id: instance.subnet_id
722
+ )
723
+ if subnet.nil?
724
+ raise MuError, "Got null subnet id out of #{@config['vpc']} when asking for #{instance.subnet_id}"
725
+ end
726
+ end
727
+
728
+ if !subnet.nil?
729
+ if !subnet.private? or (!@config['static_ip'].nil? and !@config['static_ip']['assign_ip'].nil?)
730
+ if !@config['static_ip'].nil?
731
+ if !@config['static_ip']['ip'].nil?
732
+ public_ip = MU::Cloud::AWS::Server.associateElasticIp(instance.instance_id, classic: false, ip: @config['static_ip']['ip'])
733
+ elsif !has_elastic_ip
734
+ public_ip = MU::Cloud::AWS::Server.associateElasticIp(instance.instance_id)
735
+ end
736
+ end
737
+ end
738
+
739
+ nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = getSSHConfig
740
+ if subnet.private? and !nat_ssh_host and !MU::Cloud::AWS::VPC.haveRouteToInstance?(cloud_desc, region: @config['region'])
741
+ raise MuError, "#{node} is in a private subnet (#{subnet}), but has no NAT host configured, and I have no other route to it"
742
+ end
743
+
744
+ # If we've asked for additional subnets (and this @config is not a
745
+ # member of a Server Pool, which has different semantics), create
746
+ # extra interfaces to accomodate.
747
+ if !@config['vpc']['subnets'].nil? and @config['basis'].nil?
748
+ device_index = 1
749
+ @vpc.subnets { |subnet|
750
+ subnet_id = subnet.cloud_id
751
+ MU.log "Adding network interface on subnet #{subnet_id} for #{node}"
752
+ iface = MU::Cloud::AWS.ec2(@config['region']).create_network_interface(subnet_id: subnet_id).network_interface
753
+ MU::MommaCat.createStandardTags(iface.network_interface_id, region: @config['region'])
754
+ MU::MommaCat.createTag(iface.network_interface_id, "Name", node+"-ETH"+device_index.to_s, region: @config['region'])
755
+
756
+ if @config['optional_tags']
757
+ MU::MommaCat.listOptionalTags.each { |key, value|
758
+ MU::MommaCat.createTag(iface.network_interface_id, key, value, region: @config['region'])
759
+ }
760
+ end
761
+
762
+ if !@config['tags'].nil?
763
+ @config['tags'].each { |tag|
764
+ MU::MommaCat.createTag(iface.network_interface_id, tag['key'], tag['value'], region: @config['region'])
765
+ }
766
+ end
767
+
768
+ MU::Cloud::AWS.ec2(@config['region']).attach_network_interface(
769
+ network_interface_id: iface.network_interface_id,
770
+ instance_id: instance.instance_id,
771
+ device_index: device_index
772
+ )
773
+ device_index = device_index + 1
774
+ }
775
+ end
776
+ elsif !@config['static_ip'].nil?
777
+ if !@config['static_ip']['ip'].nil?
778
+ public_ip = MU::Cloud::AWS::Server.associateElasticIp(instance.instance_id, classic: true, ip: @config['static_ip']['ip'])
779
+ elsif !has_elastic_ip
780
+ public_ip = MU::Cloud::AWS::Server.associateElasticIp(instance.instance_id, classic: true)
781
+ end
782
+ end
783
+
784
+
785
+ if !@config['image_then_destroy']
786
+ notify
787
+ end
788
+
789
+ MU.log "EC2 instance #{node} has id #{instance.instance_id}", MU::DEBUG
790
+
791
+ @config["private_dns_name"] = instance.private_dns_name
792
+ @config["public_dns_name"] = instance.public_dns_name
793
+ @config["private_ip_address"] = instance.private_ip_address
794
+ @config["public_ip_address"] = instance.public_ip_address
795
+
796
+ ext_mappings = MU.structToHash(instance.block_device_mappings)
797
+
798
+ # Root disk on standard CentOS AMI
799
+ # tagVolumes(instance.instance_id, "/dev/sda", "Name", "ROOT-"+MU.deploy_id+"-"+@config["name"].upcase)
800
+ # Root disk on standard Ubuntu AMI
801
+ # tagVolumes(instance.instance_id, "/dev/sda1", "Name", "ROOT-"+MU.deploy_id+"-"+@config["name"].upcase)
802
+
803
+ # Generic deploy ID tag
804
+ # tagVolumes(instance.instance_id)
805
+
806
+ # Tag volumes with all our standard tags.
807
+ # Maybe replace tagVolumes with this? There is one more place tagVolumes is called from
808
+ volumes = MU::Cloud::AWS.ec2(@config['region']).describe_volumes(filters: [name: "attachment.instance-id", values: [instance.instance_id]])
809
+ volumes.each { |vol|
810
+ vol.volumes.each { |volume|
811
+ volume.attachments.each { |attachment|
812
+ MU::MommaCat.listStandardTags.each_pair { |key, value|
813
+ MU::MommaCat.createTag(attachment.volume_id, key, value, region: @config['region'])
814
+
815
+ if attachment.device == "/dev/sda" or attachment.device == "/dev/sda1"
816
+ MU::MommaCat.createTag(attachment.volume_id, "Name", "ROOT-#{MU.deploy_id}-#{@config["name"].upcase}", region: @config['region'])
817
+ else
818
+ MU::MommaCat.createTag(attachment.volume_id, "Name", "#{MU.deploy_id}-#{@config["name"].upcase}-#{attachment.device.upcase}", region: @config['region'])
819
+ end
820
+ }
821
+
822
+ if @config['optional_tags']
823
+ MU::MommaCat.listOptionalTags.each { |key, value|
824
+ MU::MommaCat.createTag(attachment.volume_id, key, value, region: @config['region'])
825
+ }
826
+ end
827
+
828
+ if @config['tags']
829
+ @config['tags'].each { |tag|
830
+ MU::MommaCat.createTag(attachment.volume_id, tag['key'], tag['value'], region: @config['region'])
831
+ }
832
+ end
833
+ }
834
+ }
835
+ }
836
+
837
+ canonical_name = instance.public_dns_name
838
+ canonical_name = instance.private_dns_name if !canonical_name or nat_ssh_host != nil
839
+ @config['canonical_name'] = canonical_name
840
+
841
+ if !@config['add_private_ips'].nil?
842
+ instance.network_interfaces.each { |int|
843
+ if int.private_ip_address == instance.private_ip_address and int.private_ip_addresses.size < (@config['add_private_ips'] + 1)
844
+ MU.log "Adding #{@config['add_private_ips']} extra private IP addresses to #{instance.instance_id}"
845
+ MU::Cloud::AWS.ec2(@config['region']).assign_private_ip_addresses(
846
+ network_interface_id: int.network_interface_id,
847
+ secondary_private_ip_address_count: @config['add_private_ips'],
848
+ allow_reassignment: false
849
+ )
850
+ end
851
+ }
852
+ notify
853
+ end
854
+
855
+ begin
856
+ if windows?
857
+ # kick off certificate generation early; WinRM will need it
858
+ cert, key = @deploy.nodeSSLCerts(self)
859
+ if @config.has_key?("basis")
860
+ @deploy.nodeSSLCerts(self, true)
861
+ end
862
+ if !@groomer.haveBootstrapped?
863
+ session = getWinRMSession(50, 60, reboot_on_problems: true)
864
+ initialWinRMTasks(session)
865
+ begin
866
+ session.close
867
+ rescue Exception
868
+ # this is allowed to fail- we're probably rebooting anyway
869
+ end
870
+ else # for an existing Windows node: WinRM, then SSH if it fails
871
+ begin
872
+ session = getWinRMSession(1, 60)
873
+ rescue Exception # yeah, yeah
874
+ session = getSSHSession(1, 60)
875
+ # XXX maybe loop at least once if this also fails?
876
+ end
877
+ end
878
+ else
879
+ session = getSSHSession(40, 30)
880
+ initialSSHTasks(session)
881
+ end
882
+ rescue BootstrapTempFail
883
+ sleep 45
884
+ retry
885
+ ensure
886
+ session.close if !session.nil? and !windows?
887
+ end
888
+
889
+ if @config["existing_deploys"] && !@config["existing_deploys"].empty?
890
+ @config["existing_deploys"].each { |ext_deploy|
891
+ if ext_deploy["cloud_id"]
892
+ found = MU::MommaCat.findStray(
893
+ @config['cloud'],
894
+ ext_deploy["cloud_type"],
895
+ cloud_id: ext_deploy["cloud_id"],
896
+ region: @config['region'],
897
+ dummy_ok: false
898
+ ).first
899
+
900
+ MU.log "Couldn't find existing resource #{ext_deploy["cloud_id"]}, #{ext_deploy["cloud_type"]}", MU::ERR if found.nil?
901
+ @deploy.notify(ext_deploy["cloud_type"], found.config["name"], found.deploydata, mu_name: found.mu_name, triggering_node: @mu_name)
902
+ elsif ext_deploy["mu_name"] && ext_deploy["deploy_id"]
903
+ MU.log "#{ext_deploy["mu_name"]} / #{ext_deploy["deploy_id"]}"
904
+ found = MU::MommaCat.findStray(
905
+ @config['cloud'],
906
+ ext_deploy["cloud_type"],
907
+ deploy_id: ext_deploy["deploy_id"],
908
+ mu_name: ext_deploy["mu_name"],
909
+ region: @config['region'],
910
+ dummy_ok: false
911
+ ).first
912
+
913
+ MU.log "Couldn't find existing resource #{ext_deploy["mu_name"]}/#{ext_deploy["deploy_id"]}, #{ext_deploy["cloud_type"]}", MU::ERR if found.nil?
914
+ @deploy.notify(ext_deploy["cloud_type"], found.config["name"], found.deploydata, mu_name: ext_deploy["mu_name"], triggering_node: @mu_name)
915
+ else
916
+ MU.log "Trying to find existing deploy, but either the cloud_id is not valid or no mu_name and deploy_id where provided", MU::ERR
917
+ end
918
+ }
919
+ end
920
+
921
+ # See if this node already exists in our config management. If it does,
922
+ # we're done.
923
+ if @groomer.haveBootstrapped?
924
+ MU.log "Node #{node} has already been bootstrapped, skipping groomer setup.", MU::NOTICE
925
+ @groomer.saveDeployData
926
+ MU::MommaCat.unlock(instance.instance_id+"-orchestrate")
927
+ MU::MommaCat.unlock(instance.instance_id+"-groom")
928
+ return true
929
+ end
930
+
931
+ begin
932
+ @groomer.bootstrap
933
+ rescue MU::Groomer::RunError
934
+ MU::MommaCat.unlock(instance.instance_id+"-groom")
935
+ MU::MommaCat.unlock(instance.instance_id+"-orchestrate")
936
+ return false
937
+ end
938
+
939
+ # Make sure we got our name written everywhere applicable
940
+ if !@named
941
+ MU::MommaCat.nameKitten(self)
942
+ @named = true
943
+ end
944
+
945
+ MU::MommaCat.unlock(instance.instance_id+"-groom")
946
+ MU::MommaCat.unlock(instance.instance_id+"-orchestrate")
947
+ return true
948
+ end
949
+
950
+ # postBoot
951
+
952
+ # Locate an existing instance or instances and return an array containing matching AWS resource descriptors for those that match.
953
+ # @param cloud_id [String]: The cloud provider's identifier for this resource.
954
+ # @param region [String]: The cloud provider region
955
+ # @param tag_key [String]: A tag key to search.
956
+ # @param tag_value [String]: The value of the tag specified by tag_key to match when searching by tag.
957
+ # @param ip [String]: An IP address associated with the instance
958
+ # @param flags [Hash]: Optional flags
959
+ # @return [Array<Hash<String,OpenStruct>>]: The cloud provider's complete descriptions of matching instances
960
+ def self.find(cloud_id: nil, region: MU.curRegion, tag_key: "Name", tag_value: nil, ip: nil, flags: {})
961
+ # XXX put that 'ip' value into opts
962
+ instance = nil
963
+ if !region.nil?
964
+ regions = [region]
965
+ else
966
+ regions = MU::Cloud::AWS.listRegions
967
+ end
968
+
969
+ found_instances = {}
970
+ search_semaphore = Mutex.new
971
+ search_threads = []
972
+
973
+ # If we got an instance id, go get it
974
+ if !cloud_id.nil? and !cloud_id.empty?
975
+ regions.each { |region|
976
+ search_threads << Thread.new {
977
+ MU.log "Hunting for instance with cloud id '#{cloud_id}' in #{region}", MU::DEBUG
978
+ retries = 0
979
+ begin
980
+ MU::Cloud::AWS.ec2(region).describe_instances(
981
+ instance_ids: [cloud_id],
982
+ filters: [
983
+ {name: "instance-state-name", values: ["running", "pending"]}
984
+ ]
985
+ ).reservations.each { |resp|
986
+ if !resp.nil? and !resp.instances.nil?
987
+ resp.instances.each { |instance|
988
+ search_semaphore.synchronize {
989
+ found_instances[instance.instance_id] = instance
990
+ }
991
+ }
992
+ end
993
+ }
994
+ rescue Aws::EC2::Errors::InvalidInstanceIDNotFound => e
995
+ if retries < 5
996
+ retries = retries + 1
997
+ sleep 5
998
+ else
999
+ raise MuError, "#{e.inspect} in region #{region}"
1000
+ end
1001
+ end
1002
+ }
1003
+ }
1004
+ done_threads = []
1005
+ begin
1006
+ search_threads.each { |t|
1007
+ joined = t.join(2)
1008
+ done_threads << joined if !joined.nil?
1009
+ }
1010
+ end while found_instances.size < 1 and done_threads.size != search_threads.size
1011
+ end
1012
+
1013
+ if found_instances.size > 0
1014
+ return found_instances
1015
+ end
1016
+
1017
+ # Ok, well, let's try looking it up by IP then
1018
+ if instance.nil? and !ip.nil?
1019
+ MU.log "Hunting for instance by IP '#{ip}'", MU::DEBUG
1020
+ ["ip-address", "private-ip-address"].each { |filter|
1021
+ response = MU::Cloud::AWS.ec2(region).describe_instances(
1022
+ filters: [
1023
+ {name: filter, values: [ip]},
1024
+ {name: "instance-state-name", values: ["running", "pending"]}
1025
+ ]
1026
+ ).reservations.first
1027
+ instance = response.instances.first if !response.nil?
1028
+ }
1029
+ end
1030
+
1031
+ if !instance.nil?
1032
+ return {instance.instance_id => instance} if !instance.nil?
1033
+ end
1034
+
1035
+ # Fine, let's try it by tag.
1036
+ if !tag_value.nil?
1037
+ MU.log "Searching for instance by tag '#{tag_key}=#{tag_value}'", MU::DEBUG
1038
+ MU::Cloud::AWS.ec2(region).describe_instances(
1039
+ filters: [
1040
+ {name: "tag:#{tag_key}", values: [tag_value]},
1041
+ {name: "instance-state-name", values: ["running", "pending"]}
1042
+ ]
1043
+ ).reservations.each { |resp|
1044
+ if !resp.nil? and resp.instances.size > 0
1045
+ resp.instances.each { |instance|
1046
+ found_instances[instance.instance_id] = instance
1047
+ }
1048
+ end
1049
+ }
1050
+ end
1051
+
1052
+ return found_instances
1053
+ end
1054
+
1055
+ # Return a description of this resource appropriate for deployment
1056
+ # metadata. Arguments reflect the return values of the MU::Cloud::[Resource].describe method
1057
+ def notify
1058
+ node, config, deploydata = describe(cloud_id: @cloud_id, update_cache: true)
1059
+ deploydata = {} if deploydata.nil?
1060
+
1061
+ if cloud_desc.nil?
1062
+ raise MuError, "Failed to load instance metadata for #{@mu_name}/#{@cloud_id}"
1063
+ end
1064
+
1065
+ interfaces = []
1066
+ private_ips = []
1067
+
1068
+ cloud_desc.network_interfaces.each { |iface|
1069
+ iface.private_ip_addresses.each { |priv_ip|
1070
+ private_ips << priv_ip.private_ip_address
1071
+ }
1072
+ interfaces << {
1073
+ "network_interface_id" => iface.network_interface_id,
1074
+ "subnet_id" => iface.subnet_id,
1075
+ "vpc_id" => iface.vpc_id
1076
+ }
1077
+ }
1078
+
1079
+ deploydata = {
1080
+ "nodename" => @mu_name,
1081
+ "run_list" => @config['run_list'],
1082
+ "image_created" => @config['image_created'],
1083
+ "iam_role" => @config['iam_role'],
1084
+ "cloud_desc_id" => @cloud_id,
1085
+ "private_dns_name" => cloud_desc.private_dns_name,
1086
+ "public_dns_name" => cloud_desc.public_dns_name,
1087
+ "private_ip_address" => cloud_desc.private_ip_address,
1088
+ "public_ip_address" => cloud_desc.public_ip_address,
1089
+ "private_ip_list" => private_ips,
1090
+ "key_name" => cloud_desc.key_name,
1091
+ "subnet_id" => cloud_desc.subnet_id,
1092
+ "cloud_desc_type" => cloud_desc.instance_type #,
1093
+ # "network_interfaces" => interfaces,
1094
+ # "config" => server
1095
+ }
1096
+
1097
+ if !@mu_windows_name.nil?
1098
+ deploydata["mu_windows_name"] = @mu_windows_name
1099
+ end
1100
+ if !@config['chef_data'].nil?
1101
+ deploydata.merge!(@config['chef_data'])
1102
+ end
1103
+ deploydata["region"] = @config['region'] if !@config['region'].nil?
1104
+ if !@named
1105
+ MU::MommaCat.nameKitten(self)
1106
+ @named = true
1107
+ end
1108
+
1109
+ return deploydata
1110
+ end
1111
+
1112
+ # If the specified server is in a VPC, and has a NAT, make sure we'll
1113
+ # be letting ssh traffic in from said NAT.
1114
+ def punchAdminNAT
1115
+ if @config['vpc'].nil? or
1116
+ (
1117
+ !@config['vpc'].has_key?("nat_host_id") and
1118
+ !@config['vpc'].has_key?("nat_host_tag") and
1119
+ !@config['vpc'].has_key?("nat_host_ip") and
1120
+ !@config['vpc'].has_key?("nat_host_name")
1121
+ )
1122
+ return nil
1123
+ end
1124
+
1125
+ return nil if @nat.is_a?(Struct) && @nat.nat_gateway_id && @nat.nat_gateway_id.start_with?("nat-")
1126
+
1127
+ dependencies if @nat.nil?
1128
+ if @nat.nil? or @nat.cloud_desc.nil?
1129
+ raise MuError, "#{@mu_name} (#{MU.deploy_id}) is configured to use #{@config['vpc']} but I can't find the cloud descriptor for a matching NAT instance"
1130
+ end
1131
+ MU.log "Adding administrative holes for NAT host #{@nat.cloud_desc.private_ip_address} to #{@mu_name}"
1132
+ if !@deploy.kittens['firewall_rules'].nil?
1133
+ @deploy.kittens['firewall_rules'].each_pair { |name, acl|
1134
+ if acl.config["admin"]
1135
+ acl.addRule([@nat.cloud_desc.private_ip_address], proto: "tcp")
1136
+ acl.addRule([@nat.cloud_desc.private_ip_address], proto: "udp")
1137
+ acl.addRule([@nat.cloud_desc.private_ip_address], proto: "icmp")
1138
+ end
1139
+ }
1140
+ end
1141
+ end
1142
+
1143
+ # Called automatically by {MU::Deploy#createResources}
1144
+ def groom
1145
+ MU::MommaCat.lock(@cloud_id+"-groom")
1146
+ node, config, deploydata = describe(cloud_id: @cloud_id)
1147
+
1148
+ if node.nil? or node.empty?
1149
+ raise MuError, "MU::Cloud::AWS::Server.groom was called without a mu_name"
1150
+ end
1151
+
1152
+ # Make double sure we don't lose a cached mu_windows_name value.
1153
+ if windows? or !@config['active_directory'].nil?
1154
+ if @mu_windows_name.nil?
1155
+ @mu_windows_name = deploydata['mu_windows_name']
1156
+ end
1157
+ end
1158
+
1159
+ punchAdminNAT
1160
+
1161
+ MU::Cloud::AWS::Server.tagVolumes(@cloud_id)
1162
+
1163
+ # If we have a loadbalancer configured, attach us to it
1164
+ if !@config['loadbalancers'].nil?
1165
+ if @loadbalancers.nil?
1166
+ raise MuError, "#{@mu_name} is configured to use LoadBalancers, but none have been loaded by dependencies()"
1167
+ end
1168
+ @loadbalancers.each { |lb|
1169
+ lb.registerNode(@cloud_id)
1170
+ }
1171
+ end
1172
+ MU.log %Q{Server #{@config['name']} private IP is #{@deploydata["private_ip_address"]}#{@deploydata["public_ip_address"] ? ", public IP is "+@deploydata["public_ip_address"] : ""}}, MU::SUMMARY
1173
+
1174
+ # Let us into any databases we depend on.
1175
+ # This is probelmtic with autscaling - old ips are not removed, and access to the database can easily be given at the BoK level
1176
+ # if @dependencies.has_key?("database")
1177
+ # @dependencies['database'].values.each { |db|
1178
+ # db.allowHost(@deploydata["private_ip_address"]+"/32")
1179
+ # if @deploydata["public_ip_address"]
1180
+ # db.allowHost(@deploydata["public_ip_address"]+"/32")
1181
+ # end
1182
+ # }
1183
+ # end
1184
+
1185
+ @groomer.saveDeployData
1186
+
1187
+ begin
1188
+ @groomer.run(purpose: "Full Initial Run", max_retries: 15, reboot_first_fail: windows?)
1189
+ rescue MU::Groomer::RunError => e
1190
+ MU.log "Proceeding after failed initial Groomer run, but #{node} may not behave as expected!", MU::WARN, details: e.message
1191
+ rescue Exception => e
1192
+ MU.log "Caught #{e.inspect} on #{node} in an unexpected place (after @groomer.run on Full Initial Run)", MU::ERR
1193
+ end
1194
+
1195
+ if !@config['create_image'].nil? and !@config['image_created']
1196
+ img_cfg = @config['create_image']
1197
+ # Scrub things that don't belong on an AMI
1198
+ session = getSSHSession
1199
+ sudo = purgecmd = ""
1200
+ sudo = "sudo" if @config['ssh_user'] != "root"
1201
+ if windows?
1202
+ purgecmd = "rm -rf /cygdrive/c/mu_installed_chef"
1203
+ else
1204
+ purgecmd = "rm -rf /opt/mu_installed_chef"
1205
+ end
1206
+ if img_cfg['image_then_destroy']
1207
+ if windows?
1208
+ purgecmd = "rm -rf /cygdrive/c/chef/ /home/#{@config['windows_admin_username']}/.ssh/authorized_keys /home/Administrator/.ssh/authorized_keys /cygdrive/c/mu-installer-ran-updates /cygdrive/c/mu_installed_chef"
1209
+ # session.exec!("powershell -Command \"& {(Get-WmiObject -Class Win32_Product -Filter \"Name='UniversalForwarder'\").Uninstall()}\"")
1210
+ else
1211
+ purgecmd = "#{sudo} rm -rf /root/.ssh/authorized_keys /etc/ssh/ssh_host_*key* /etc/chef /etc/opscode/* /.mu-installer-ran-updates /var/chef /opt/mu_installed_chef /opt/chef ; #{sudo} sed -i 's/^HOSTNAME=.*//' /etc/sysconfig/network"
1212
+ end
1213
+ end
1214
+ session.exec!(purgecmd)
1215
+ session.close
1216
+ ami_id = MU::Cloud::AWS::Server.createImage(
1217
+ name: @mu_name,
1218
+ instance_id: @cloud_id,
1219
+ storage: @config['storage'],
1220
+ exclude_storage: img_cfg['image_exclude_storage'],
1221
+ copy_to_regions: img_cfg['copy_to_regions'],
1222
+ make_public: img_cfg['public'],
1223
+ region: @config['region'],
1224
+ tags: @config['tags'])
1225
+ @deploy.notify("images", @config['name'], {"image_id" => ami_id})
1226
+ @config['image_created'] = true
1227
+ if img_cfg['image_then_destroy']
1228
+ MU::Cloud::AWS::Server.waitForAMI(ami_id, region: @config['region'])
1229
+ MU.log "AMI #{ami_id} ready, removing source node #{node}"
1230
+ MU::Cloud::AWS::Server.terminateInstance(id: @cloud_id, region: @config['region'], deploy_id: @deploy.deploy_id, mu_name: @mu_name)
1231
+ destroy
1232
+ end
1233
+ end
1234
+
1235
+ MU::MommaCat.unlock(@cloud_id+"-groom")
1236
+ end
1237
+
1238
+ # Canonical Amazon Resource Number for this resource
1239
+ # @return [String]
1240
+ def arn
1241
+ "arn:"+(MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws")+":ec2:"+@config['region']+":"+MU.account_number+":instance/"+@cloud_id
1242
+ end
1243
+
1244
+ def cloud_desc
1245
+ max_retries = 5
1246
+ retries = 0
1247
+ if !@cloud_id.nil?
1248
+ begin
1249
+ return MU::Cloud::AWS.ec2(@config['region']).describe_instances(instance_ids: [@cloud_id]).reservations.first.instances.first
1250
+ rescue Aws::EC2::Errors::InvalidInstanceIDNotFound
1251
+ return nil
1252
+ rescue NoMethodError => e
1253
+ if retries >= max_retries
1254
+ raise MuError, "Couldn't get a cloud descriptor for #{@mu_name} (#{@cloud_id})"
1255
+ else
1256
+ retries = retries + 1
1257
+ sleep 10
1258
+ retry
1259
+ end
1260
+ end
1261
+ end
1262
+ nil
1263
+ end
1264
+
1265
+ # Return the IP address that we, the Mu server, should be using to access
1266
+ # this host via the network. Note that this does not factor in SSH
1267
+ # bastion hosts that may be in the path, see getSSHConfig if that's what
1268
+ # you need.
1269
+ def canonicalIP
1270
+ mu_name, config, deploydata = describe(cloud_id: @cloud_id)
1271
+
1272
+ instance = cloud_desc
1273
+
1274
+ if !instance
1275
+ raise MuError, "Couldn't retrieve cloud descriptor for server #{self}"
1276
+ end
1277
+
1278
+ if deploydata.nil? or
1279
+ (!deploydata.has_key?("private_ip_address") and
1280
+ !deploydata.has_key?("public_ip_address"))
1281
+ return nil if instance.nil?
1282
+ @deploydata = {} if @deploydata.nil?
1283
+ @deploydata["public_ip_address"] = instance.public_ip_address
1284
+ @deploydata["public_dns_name"] = instance.public_dns_name
1285
+ @deploydata["private_ip_address"] = instance.private_ip_address
1286
+ @deploydata["private_dns_name"] = instance.private_dns_name
1287
+
1288
+ notify
1289
+ end
1290
+
1291
+ # Our deploydata gets corrupted often with server pools, this will cause us to use the wrong IP to identify a node
1292
+ # which will cause us to create certificates, DNS records and other artifacts with incorrect information which will cause our deploy to fail.
1293
+ # The cloud_id is always correct so lets use 'cloud_desc' to get the correct IPs
1294
+ if MU::Cloud::AWS::VPC.haveRouteToInstance?(cloud_desc, region: @config['region']) or @deploydata["public_ip_address"].nil?
1295
+ @config['canonical_ip'] = instance.private_ip_address
1296
+ @deploydata["private_ip_address"] = instance.private_ip_address
1297
+ return instance.private_ip_address
1298
+ else
1299
+ @config['canonical_ip'] = instance.public_ip_address
1300
+ @deploydata["public_ip_address"] = instance.public_ip_address
1301
+ return instance.public_ip_address
1302
+ end
1303
+ end
1304
+
1305
+ # Create an AMI out of a running server. Requires either the name of a MU resource in the current deployment, or the cloud provider id of a running instance.
1306
+ # @param name [String]: The MU resource name of the server to use as the basis for this image.
1307
+ # @param instance_id [String]: The cloud provider resource identifier of the server to use as the basis for this image.
1308
+ # @param storage [Hash]: The storage devices to include in this image.
1309
+ # @param exclude_storage [Boolean]: Do not include the storage device profile of the running instance when creating this image.
1310
+ # @param region [String]: The cloud provider region
1311
+ # @param copy_to_regions [Array<String>]: Copy the resulting AMI into the listed regions.
1312
+ # @param tags [Array<String>]: Extra/override tags to apply to the image.
1313
+ # @return [String]: The cloud provider identifier of the new machine image.
1314
+ def self.createImage(name: nil, instance_id: nil, storage: {}, exclude_storage: false, make_public: false, region: MU.curRegion, copy_to_regions: [], tags: [])
1315
+ ami_descriptor = {
1316
+ :instance_id => instance_id,
1317
+ :name => name,
1318
+ :description => "Image automatically generated by Mu from #{name}"
1319
+ }
1320
+
1321
+ storage_list = Array.new
1322
+ if exclude_storage
1323
+ instance = MU::Cloud::Server.find(cloud_id: instance_id, region: region)
1324
+ instance.block_device_mappings.each { |vol|
1325
+ if vol.device_name != instance.root_device_name
1326
+
1327
+ storage_list << MU::Cloud::AWS::Server.convertBlockDeviceMapping(
1328
+ {
1329
+ "device" => vol.device_name,
1330
+ "no-device" => ""
1331
+ }
1332
+ )[0]
1333
+ end
1334
+ }
1335
+ elsif !storage.nil?
1336
+ storage.each { |vol|
1337
+ storage_list << MU::Cloud::AWS::Server.convertBlockDeviceMapping(vol)[0]
1338
+ }
1339
+ end
1340
+ ami_descriptor[:block_device_mappings] = storage_list
1341
+ if !exclude_storage
1342
+ ami_descriptor[:block_device_mappings].concat(@ephemeral_mappings)
1343
+ end
1344
+ MU.log "Creating AMI from #{name}", details: ami_descriptor
1345
+ resp = nil
1346
+ begin
1347
+ resp = MU::Cloud::AWS.ec2(region).create_image(ami_descriptor)
1348
+ rescue Aws::EC2::Errors::InvalidAMINameDuplicate => e
1349
+ MU.log "AMI #{name} already exists, skipping", MU::WARN
1350
+ return nil
1351
+ end
1352
+ ami = resp.image_id
1353
+ MU::MommaCat.createStandardTags(ami, region: region)
1354
+ MU::MommaCat.createTag(ami, "Name", name, region: region)
1355
+ MU.log "AMI of #{name} in region #{region}: #{ami}"
1356
+ if make_public
1357
+ MU::Cloud::AWS::Server.waitForAMI(ami, region: region)
1358
+ MU::Cloud::AWS.ec2(region).modify_image_attribute(
1359
+ image_id: ami,
1360
+ launch_permission: {add: [{group: "all"}]},
1361
+ attribute: "launchPermission"
1362
+ )
1363
+ end
1364
+ copythreads = []
1365
+ if !copy_to_regions.nil? and copy_to_regions.size > 0
1366
+ parent_thread_id = Thread.current.object_id
1367
+ MU::Cloud::AWS::Server.waitForAMI(ami, region: region) if !make_public
1368
+ copy_to_regions.each { |r|
1369
+ next if r == region
1370
+ copythreads << Thread.new {
1371
+ MU.dupGlobals(parent_thread_id)
1372
+ copy = MU::Cloud::AWS.ec2(r).copy_image(
1373
+ source_region: region,
1374
+ source_image_id: ami,
1375
+ name: name,
1376
+ description: "Image automatically generated by Mu from #{name}"
1377
+ )
1378
+ MU.log "Initiated copy of #{ami} from #{region} to #{r}: #{copy.image_id}"
1379
+
1380
+ MU::MommaCat.createStandardTags(copy.image_id, region: r)
1381
+ MU::MommaCat.createTag(copy.image_id, "Name", name, region: r)
1382
+ if !tags.nil?
1383
+ tags.each { |tag|
1384
+ MU::MommaCat.createTag(instance.instance_id, tag['key'], tag['value'], region: r)
1385
+ }
1386
+ end
1387
+ MU::Cloud::AWS::Server.waitForAMI(copy.image_id, region: r)
1388
+ if make_public
1389
+ MU::Cloud::AWS.ec2(r).modify_image_attribute(
1390
+ image_id: copy.image_id,
1391
+ launch_permission: {add: [{group: "all"}]},
1392
+ attribute: "launchPermission"
1393
+ )
1394
+ end
1395
+ MU.log "AMI of #{name} in region #{r}: #{copy.image_id}"
1396
+ } # Thread
1397
+ }
1398
+ end
1399
+
1400
+ copythreads.each { |t|
1401
+ t.join
1402
+ }
1403
+
1404
+ return resp.image_id
1405
+ end
1406
+
1407
+ # Given a cloud platform identifier for a machine image, wait until it's
1408
+ # flagged as ready.
1409
+ # @param image_id [String]: The machine image to wait for.
1410
+ # @param region [String]: The cloud provider region
1411
+ def self.waitForAMI(image_id, region: MU.curRegion)
1412
+ MU.log "Checking to see if AMI #{image_id} is available", MU::DEBUG
1413
+
1414
+ retries = 0
1415
+ begin
1416
+ images = MU::Cloud::AWS.ec2(region).describe_images(image_ids: [image_id]).images
1417
+ if images.nil? or images.size == 0
1418
+ raise MuError, "No such AMI #{image_id} found"
1419
+ end
1420
+ state = images.first.state
1421
+ if state == "failed"
1422
+ raise MuError, "#{image_id} is marked as failed! I can't use this."
1423
+ end
1424
+ if state != "available"
1425
+ loglevel = MU::DEBUG
1426
+ loglevel = MU::NOTICE if retries % 3 == 0
1427
+ MU.log "Waiting for AMI #{image_id} in #{region} (#{state})", loglevel
1428
+ sleep 60
1429
+ end
1430
+ rescue Aws::EC2::Errors::InvalidAMIIDNotFound => e
1431
+ retries = retries + 1
1432
+ if retries >= 10
1433
+ raise e
1434
+ end
1435
+ sleep 5
1436
+ retry
1437
+ end while state != "available"
1438
+ MU.log "AMI #{image_id} is ready", MU::DEBUG
1439
+ end
1440
+
1441
+ # Maps our configuration language's 'storage' primitive to an Amazon-style
1442
+ # block_device_mapping.
1443
+ # @param storage [Hash]: The {MU::Config}-style storage description.
1444
+ # @return [Hash]: The Amazon-style storage description.
1445
+ def self.convertBlockDeviceMapping(storage)
1446
+ vol_struct = {}
1447
+ cfm_mapping = {}
1448
+ if storage["no_device"]
1449
+ vol_struct[:no_device] = storage["no_device"]
1450
+ cfm_mapping["NoDevice"] = storage["no_device"]
1451
+ end
1452
+
1453
+ if storage["device"]
1454
+ vol_struct[:device_name] = storage["device"]
1455
+ cfm_mapping["DeviceName"] = storage["device"]
1456
+ elsif storage["no_device"].nil?
1457
+ vol_struct[:device_name] = @disk_devices.shift
1458
+ cfm_mapping["DeviceName"] = @disk_devices.shift
1459
+ end
1460
+
1461
+ vol_struct[:virtual_name] = storage["virtual_name"] if storage["virtual_name"]
1462
+
1463
+ storage["volume_size"] = storage["size"]
1464
+ if storage["snapshot_id"] or storage["size"]
1465
+ vol_struct[:ebs] = {}
1466
+ cfm_mapping["Ebs"] = {}
1467
+ [:delete_on_termination, :snapshot_id, :volume_size, :volume_type, :encrypted].each { |arg|
1468
+ if storage.has_key?(arg.to_s) and !storage[arg.to_s].nil?
1469
+ vol_struct[:ebs][arg] = storage[arg.to_s]
1470
+ key = ""
1471
+ arg.to_s.split(/_/).each { |chunk| key = key + chunk.capitalize }
1472
+ cfm_mapping["Ebs"][key] = storage[arg.to_s]
1473
+ end
1474
+ }
1475
+ cfm_mapping["Ebs"].delete("Encrypted") if !cfm_mapping["Ebs"]["Encrypted"]
1476
+
1477
+ if storage["iops"] and storage["volume_type"] == "io1"
1478
+ vol_struct[:ebs][:iops] = storage["iops"]
1479
+ cfm_mapping["Ebs"]["Iops"] = storage["iops"]
1480
+ end
1481
+ end
1482
+
1483
+ return [vol_struct, cfm_mapping]
1484
+ end
1485
+
1486
+ # Retrieves the Cloud provider's randomly generated Windows password
1487
+ # Will only work on stock Amazon Windows AMIs or custom AMIs that where created with Administrator Password set to random in EC2Config
1488
+ # return [String]: A password string.
1489
+ def getWindowsAdminPassword
1490
+ if @cloud_id.nil?
1491
+ node, config, deploydata = describe
1492
+ @cloud_id = cloud_desc.instance_id
1493
+ end
1494
+ ssh_keydir = "#{Etc.getpwuid(Process.uid).dir}/.ssh"
1495
+ ssh_key_name = @deploy.ssh_key_name
1496
+
1497
+ retries = 0
1498
+ MU.log "Waiting for Windows instance password to be set by Amazon and flagged as available from the API. Note- if you're using a source AMI that already has its password set, this may fail. You'll want to set use_cloud_provider_windows_password to false if this is the case.", MU::NOTICE
1499
+ begin
1500
+ MU::Cloud::AWS.ec2(@config['region']).wait_until(:password_data_available, instance_id: @cloud_id) do |waiter|
1501
+ waiter.max_attempts = 60
1502
+ waiter.before_attempt do |attempts|
1503
+ MU.log "Waiting for Windows password data to be available for node #{@mu_name}", MU::NOTICE if attempts % 5 == 0
1504
+ end
1505
+ # waiter.before_wait do |attempts, resp|
1506
+ # throw :success if resp.data.password_data and !resp.data.password_data.empty?
1507
+ # end
1508
+ end
1509
+ rescue Aws::Waiters::Errors::TooManyAttemptsError => e
1510
+ if retries < 2
1511
+ retries = retries + 1
1512
+ MU.log "wait_until(:password_data_available, instance_id: #{@cloud_id}) in #{@config['region']} never got a good response, retrying (#{retries}/2)", MU::WARN, details: e.inspect
1513
+ retry
1514
+ else
1515
+ MU.log "wait_until(:password_data_available, instance_id: #{@cloud_id}) in #{@config['region']} never returned- this image may not be configured to have its password set by AWS.", MU::ERR
1516
+ return nil
1517
+ end
1518
+ end
1519
+
1520
+ resp = MU::Cloud::AWS.ec2(@config['region']).get_password_data(instance_id: @cloud_id)
1521
+ encrypted_password = resp.password_data
1522
+
1523
+ # Note: This is already implemented in the decrypt_windows_password API call
1524
+ decoded = Base64.decode64(encrypted_password)
1525
+ pem_bytes = File.open("#{ssh_keydir}/#{ssh_key_name}", 'rb') { |f| f.read }
1526
+ private_key = OpenSSL::PKey::RSA.new(pem_bytes)
1527
+ decrypted_password = private_key.private_decrypt(decoded)
1528
+ return decrypted_password
1529
+ end
1530
+
1531
+ @eips_used = Array.new
1532
+ # Find a free AWS Elastic IP.
1533
+ # @param classic [Boolean]: Toggle whether to allocate an IP in EC2 Classic
1534
+ # instead of VPC.
1535
+ # @param ip [String]: Request a specific IP address.
1536
+ # @param region [String]: The cloud provider region
1537
+ def self.findFreeElasticIp(classic: false, ip: nil, region: MU.curRegion)
1538
+ filters = Array.new
1539
+ if !classic
1540
+ filters << {name: "domain", values: ["vpc"]}
1541
+ else
1542
+ filters << {name: "domain", values: ["standard"]}
1543
+ end
1544
+ filters << {name: "public-ip", values: [ip]} if ip != nil
1545
+
1546
+ if filters.size > 0
1547
+ resp = MU::Cloud::AWS.ec2(region).describe_addresses(filters: filters)
1548
+ else
1549
+ resp = MU::Cloud::AWS.ec2(region).describe_addresses()
1550
+ end
1551
+ resp.addresses.each { |address|
1552
+ return address if (address.network_interface_id.nil? || address.network_interface_id.empty?) && !@eips_used.include?(address.public_ip)
1553
+ }
1554
+ if ip != nil
1555
+ if !classic
1556
+ raise MuError, "Requested EIP #{ip}, but no such IP exists or is avaulable in VPC"
1557
+ else
1558
+ raise MuError, "Requested EIP #{ip}, but no such IP exists or is available in EC2 Classic"
1559
+ end
1560
+ end
1561
+ if !classic
1562
+ resp = MU::Cloud::AWS.ec2(region).allocate_address(domain: "vpc")
1563
+ new_ip = resp.public_ip
1564
+ else
1565
+ new_ip = MU::Cloud::AWS.ec2(region).allocate_address().public_ip
1566
+ end
1567
+ filters = [{name: "public-ip", values: [new_ip]}]
1568
+ if resp.domain
1569
+ filters << {name: "domain", values: [resp.domain]}
1570
+ end rescue NoMethodError
1571
+ if new_ip.nil?
1572
+ MU.log "Unable to allocate new Elastic IP. Are we at quota?", MU::ERR
1573
+ raise MuError, "Unable to allocate new Elastic IP. Are we at quota?"
1574
+ end
1575
+ MU.log "Allocated new EIP #{new_ip}, fetching full description"
1576
+
1577
+
1578
+ begin
1579
+ begin
1580
+ sleep 5
1581
+ resp = MU::Cloud::AWS.ec2(region).describe_addresses(
1582
+ filters: filters
1583
+ )
1584
+ addr = resp.addresses.first
1585
+ end while resp.addresses.size < 1 or addr.public_ip.nil?
1586
+ rescue NoMethodError
1587
+ MU.log "EIP descriptor came back without a public_ip attribute for #{new_ip}, retrying", MU::WARN
1588
+ sleep 5
1589
+ retry
1590
+ end
1591
+
1592
+ return addr
1593
+ end
1594
+
1595
+ # Add a volume to this instance
1596
+ # @param dev [String]: Device name to use when attaching to instance
1597
+ # @param size [String]: Size (in gb) of the new volume
1598
+ # @param type [String]: Cloud storage type of the volume, if applicable
1599
+ def addVolume(dev, size, type: "gp2")
1600
+ if @cloud_id.nil? or @cloud_id.empty?
1601
+ MU.log "#{self} didn't have a cloud id, couldn't determine 'active?' status", MU::ERR
1602
+ return true
1603
+ end
1604
+ az = nil
1605
+ MU::Cloud::AWS.ec2(@config['region']).describe_instances(
1606
+ instance_ids: [@cloud_id]
1607
+ ).reservations.each { |resp|
1608
+ if !resp.nil? and !resp.instances.nil?
1609
+ resp.instances.each { |instance|
1610
+ az = instance.placement.availability_zone
1611
+ instance.block_device_mappings.each { |vol|
1612
+ if vol.device_name == dev
1613
+ MU.log "A volume #{dev} already attached to #{self}, skipping", MU::NOTICE
1614
+ return
1615
+ end
1616
+ }
1617
+ }
1618
+ end
1619
+ }
1620
+ MU.log "Creating #{size}GB #{type} volume on #{dev} for #{@cloud_id}"
1621
+ creation = MU::Cloud::AWS.ec2(@config['region']).create_volume(
1622
+ availability_zone: az,
1623
+ size: size,
1624
+ volume_type: type
1625
+ )
1626
+ begin
1627
+ sleep 3
1628
+ creation = MU::Cloud::AWS.ec2(@config['region']).describe_volumes(volume_ids: [creation.volume_id]).volumes.first
1629
+ if !["creating", "available"].include?(creation.state)
1630
+ raise MuError, "Saw state '#{creation.state}' while creating #{size}GB #{type} volume on #{dev} for #{@cloud_id}"
1631
+ end
1632
+ end while creation.state != "available"
1633
+
1634
+ if @deploy
1635
+ MU::MommaCat.listStandardTags.each_pair { |key, value|
1636
+ MU::MommaCat.createTag(creation.volume_id, key, value, region: @config['region'])
1637
+ }
1638
+ MU::MommaCat.createTag(creation.volume_id, "Name", "#{MU.deploy_id}-#{@config["name"].upcase}-#{dev.upcase}", region: @config['region'])
1639
+ end
1640
+
1641
+ attachment = MU::Cloud::AWS.ec2(@config['region']).attach_volume(
1642
+ device: dev,
1643
+ instance_id: @cloud_id,
1644
+ volume_id: creation.volume_id
1645
+ )
1646
+
1647
+ begin
1648
+ sleep 3
1649
+ attachment = MU::Cloud::AWS.ec2(@config['region']).describe_volumes(volume_ids: [attachment.volume_id]).volumes.first.attachments.first
1650
+ if !["attaching", "attached"].include?(attachment.state)
1651
+ raise MuError, "Saw state '#{creation.state}' while creating #{size}GB #{type} volume on #{dev} for #{@cloud_id}"
1652
+ end
1653
+ end while attachment.state != "attached"
1654
+ end
1655
+
1656
+ # Determine whether the node in question exists at the Cloud provider
1657
+ # layer.
1658
+ # @return [Boolean]
1659
+ def active?
1660
+ if @cloud_id.nil? or @cloud_id.empty?
1661
+ MU.log "#{self} didn't have a #{@cloud_id}, couldn't determine 'active?' status", MU::ERR
1662
+ return true
1663
+ end
1664
+ begin
1665
+ MU::Cloud::AWS.ec2(@config['region']).describe_instances(
1666
+ instance_ids: [@cloud_id]
1667
+ ).reservations.each { |resp|
1668
+ if !resp.nil? and !resp.instances.nil?
1669
+ resp.instances.each { |instance|
1670
+ if instance.state.name == "terminated" or
1671
+ instance.state.name == "terminating"
1672
+ return false
1673
+ end
1674
+ return true
1675
+ }
1676
+ end
1677
+ }
1678
+ rescue Aws::EC2::Errors::InvalidInstanceIDNotFound
1679
+ return false
1680
+ end
1681
+ return false
1682
+ end
1683
+
1684
+ @eip_semaphore = Mutex.new
1685
+ # Associate an Amazon Elastic IP with an instance.
1686
+ # @param instance_id [String]: The cloud provider identifier of the instance.
1687
+ # @param classic [Boolean]: Whether to assume we're using an IP in EC2 Classic instead of VPC.
1688
+ # @param ip [String]: Request a specific IP address.
1689
+ # @param region [String]: The cloud provider region
1690
+ # @return [void]
1691
+ def self.associateElasticIp(instance_id, classic: false, ip: nil, region: MU.curRegion)
1692
+ MU.log "associateElasticIp called: #{instance_id}, classic: #{classic}, ip: #{ip}, region: #{region}", MU::DEBUG
1693
+ elastic_ip = nil
1694
+ @eip_semaphore.synchronize {
1695
+ if !ip.nil?
1696
+ filters = [{name: "public-ip", values: [ip]}]
1697
+ resp = MU::Cloud::AWS.ec2(region).describe_addresses(filters: filters)
1698
+ if @eips_used.include?(ip)
1699
+ is_free = false
1700
+ resp.addresses.each { |address|
1701
+ if address.public_ip == ip and (address.instance_id.nil? and address.network_interface_id.nil?) or address.instance_id == instance_id
1702
+ @eips_used.delete(ip)
1703
+ is_free = true
1704
+ end
1705
+ }
1706
+
1707
+ raise MuError, "Requested EIP #{ip}, but we've already assigned this IP to someone else" if !is_free
1708
+ else
1709
+ resp.addresses.each { |address|
1710
+ if address.public_ip == ip and address.instance_id == instance_id
1711
+ return ip
1712
+ end
1713
+ }
1714
+ end
1715
+ end
1716
+ elastic_ip = findFreeElasticIp(classic: classic, ip: ip)
1717
+ if !ip.nil? and (elastic_ip.nil? or ip != elastic_ip.public_ip)
1718
+ raise MuError, "Requested EIP #{ip}, but this IP does not exist or is not available"
1719
+ end
1720
+ if elastic_ip.nil?
1721
+ raise MuError, "Couldn't find an Elastic IP to associate with #{instance_id}"
1722
+ end
1723
+ @eips_used << elastic_ip.public_ip
1724
+ MU.log "Associating Elastic IP #{elastic_ip.public_ip} with #{instance_id}", details: elastic_ip
1725
+ }
1726
+ attempts = 0
1727
+ begin
1728
+ if classic
1729
+ resp = MU::Cloud::AWS.ec2(region).associate_address(
1730
+ instance_id: instance_id,
1731
+ public_ip: elastic_ip.public_ip
1732
+ )
1733
+ else
1734
+ resp = MU::Cloud::AWS.ec2(region).associate_address(
1735
+ instance_id: instance_id,
1736
+ allocation_id: elastic_ip.allocation_id,
1737
+ allow_reassociation: false
1738
+ )
1739
+ end
1740
+ rescue Aws::EC2::Errors::IncorrectInstanceState => e
1741
+ attempts = attempts + 1
1742
+ if attempts < 6
1743
+ MU.log "Got #{e.message} associating #{elastic_ip.allocation_id} with #{instance_id}, retrying", MU::WARN
1744
+ sleep 5
1745
+ retry
1746
+ end
1747
+ raise MuError "#{e.message} associating #{elastic_ip.allocation_id} with #{instance_id}"
1748
+ rescue Aws::EC2::Errors::ResourceAlreadyAssociated => e
1749
+ # A previous association attempt may have succeeded, albeit slowly.
1750
+ resp = MU::Cloud::AWS.ec2(region).describe_addresses(
1751
+ allocation_ids: [elastic_ip.allocation_id]
1752
+ )
1753
+ first_addr = resp.addresses.first
1754
+ if !first_addr.nil? and first_addr.instance_id == instance_id
1755
+ MU.log "#{elastic_ip.public_ip} already associated with #{instance_id}", MU::WARN
1756
+ else
1757
+ MU.log "#{elastic_ip.public_ip} shows as already associated!", MU::ERR, details: resp
1758
+ raise MuError, "#{elastic_ip.public_ip} shows as already associated with #{first_addr.instance_id}!"
1759
+ end
1760
+ end
1761
+
1762
+ instance = MU::Cloud::AWS.ec2(region).describe_instances(instance_ids: [instance_id]).reservations.first.instances.first
1763
+ waited = false
1764
+ if instance.public_ip_address != elastic_ip.public_ip
1765
+ waited = true
1766
+ begin
1767
+ sleep 10
1768
+ MU.log "Waiting for Elastic IP association of #{elastic_ip.public_ip} to #{instance_id} to take effect", MU::NOTICE
1769
+ instance = MU::Cloud::AWS.ec2(region).describe_instances(instance_ids: [instance_id]).reservations.first.instances.first
1770
+ end while instance.public_ip_address != elastic_ip.public_ip
1771
+ end
1772
+
1773
+ MU.log "Elastic IP #{elastic_ip.public_ip} now associated with #{instance_id}" if waited
1774
+
1775
+ return elastic_ip.public_ip
1776
+ end
1777
+
1778
+ # Remove all instances associated with the currently loaded deployment. Also cleans up associated volumes, droppings in the MU master's /etc/hosts and ~/.ssh, and in whatever Groomer was used.
1779
+ # @param noop [Boolean]: If true, will only print what would be done
1780
+ # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
1781
+ # @param region [String]: The cloud provider region
1782
+ # @return [void]
1783
+ def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, skipsnapshots: false, onlycloud: false, flags: {})
1784
+ tagfilters = [
1785
+ {name: "tag:MU-ID", values: [MU.deploy_id]}
1786
+ ]
1787
+ if !ignoremaster
1788
+ tagfilters << {name: "tag:MU-MASTER-IP", values: [MU.mu_public_ip]}
1789
+ end
1790
+ instances = Array.new
1791
+ unterminated = Array.new
1792
+ name_tags = Array.new
1793
+
1794
+ # Build a list of instances we need to clean up. We guard against
1795
+ # accidental deletion here by requiring someone to have hand-terminated
1796
+ # these, by default.
1797
+ resp = MU::Cloud::AWS.ec2(region).describe_instances(
1798
+ filters: tagfilters
1799
+ )
1800
+
1801
+ return if resp.data.reservations.nil?
1802
+ resp.data.reservations.each { |reservation|
1803
+ reservation.instances.each { |instance|
1804
+ if instance.state.name != "terminated"
1805
+ unterminated << instance
1806
+ instance.tags.each { |tag|
1807
+ name_tags << tag.value if tag.key == "Name"
1808
+ }
1809
+ end
1810
+ }
1811
+ }
1812
+
1813
+ parent_thread_id = Thread.current.object_id
1814
+
1815
+ threads = []
1816
+ unterminated.each { |instance|
1817
+ threads << Thread.new(instance) { |myinstance|
1818
+ MU.dupGlobals(parent_thread_id)
1819
+ Thread.abort_on_exception = true
1820
+ MU::Cloud::AWS::Server.terminateInstance(id: myinstance.instance_id, noop: noop, onlycloud: onlycloud, region: region, deploy_id: MU.deploy_id)
1821
+ }
1822
+ }
1823
+
1824
+ resp = MU::Cloud::AWS.ec2(region).describe_volumes(
1825
+ filters: tagfilters
1826
+ )
1827
+ resp.data.volumes.each { |volume|
1828
+ threads << Thread.new(volume) { |myvolume|
1829
+ MU.dupGlobals(parent_thread_id)
1830
+ Thread.abort_on_exception = true
1831
+ MU::Cloud::AWS::Server.delete_volume(myvolume, noop, skipsnapshots)
1832
+ }
1833
+ }
1834
+
1835
+ # Wait for all of the instances to finish cleanup before proceeding
1836
+ threads.each { |t|
1837
+ t.join
1838
+ }
1839
+ end
1840
+
1841
+ # Terminate an instance.
1842
+ # @param instance [OpenStruct]: The cloud provider's description of the instance.
1843
+ # @param id [String]: The cloud provider's identifier for the instance, to use if the full description is not available.
1844
+ # @param region [String]: The cloud provider region
1845
+ # @return [void]
1846
+ def self.terminateInstance(instance: nil, noop: false, id: nil, onlycloud: false, region: MU.curRegion, deploy_id: MU.deploy_id, mu_name: nil)
1847
+ ips = Array.new
1848
+ if !instance
1849
+ if id
1850
+ begin
1851
+ resp = MU::Cloud::AWS.ec2(region).describe_instances(instance_ids: [id])
1852
+ rescue Aws::EC2::Errors::InvalidInstanceIDNotFound => e
1853
+ MU.log "Instance #{id} no longer exists", MU::WARN
1854
+ end
1855
+ if !resp.nil? and !resp.reservations.nil? and !resp.reservations.first.nil?
1856
+ instance = resp.reservations.first.instances.first
1857
+ ips << instance.public_ip_address if !instance.public_ip_address.nil?
1858
+ ips << instance.private_ip_address if !instance.private_ip_address.nil?
1859
+ end
1860
+ else
1861
+ MU.log "You must supply an instance handle or id to terminateInstance", MU::ERR
1862
+ end
1863
+ else
1864
+ id = instance.instance_id
1865
+ end
1866
+ if !MU.deploy_id.empty?
1867
+ deploy_dir = File.expand_path("#{MU.dataDir}/deployments/"+MU.deploy_id)
1868
+ if Dir.exist?(deploy_dir) and !noop
1869
+ FileUtils.touch("#{deploy_dir}/.cleanup-"+id)
1870
+ end
1871
+ end
1872
+
1873
+ server_obj = MU::MommaCat.findStray(
1874
+ "AWS",
1875
+ "servers",
1876
+ region: region,
1877
+ deploy_id: deploy_id,
1878
+ cloud_id: id,
1879
+ mu_name: mu_name
1880
+ ).first
1881
+
1882
+ begin
1883
+ MU::Cloud::AWS.ec2(region).describe_instances(instance_ids: [id])
1884
+ rescue Aws::EC2::Errors::InvalidInstanceIDNotFound => e
1885
+ MU.log "Instance #{id} no longer exists", MU::DEBUG
1886
+ end
1887
+
1888
+ if !server_obj.nil? and MU::Cloud::AWS.hosted and !MU::Cloud::AWS.isGovCloud?
1889
+ # DNS cleanup is now done in MU::Cloud::DNSZone. Keeping this for now
1890
+ cleaned_dns = false
1891
+ mu_name = server_obj.mu_name
1892
+ mu_zone = MU::Cloud::DNSZone.find(cloud_id: "platform-mu").values.first
1893
+ if !mu_zone.nil?
1894
+ zone_rrsets = []
1895
+ rrsets = MU::Cloud::AWS.route53(region).list_resource_record_sets(hosted_zone_id: mu_zone.id)
1896
+ rrsets.resource_record_sets.each{ |record|
1897
+ zone_rrsets << record
1898
+ }
1899
+
1900
+ # AWS API returns a maximum of 100 results. DNS zones are likely to have more than 100 records, lets page and make sure we grab all records in a given zone
1901
+ while rrsets.next_record_name && rrsets.next_record_type
1902
+ rrsets = MU::Cloud::AWS.route53(region).list_resource_record_sets(hosted_zone_id: mu_zone.id, start_record_name: rrsets.next_record_name, start_record_type: rrsets.next_record_type)
1903
+ rrsets.resource_record_sets.each{ |record|
1904
+ zone_rrsets << record
1905
+ }
1906
+ end
1907
+ end
1908
+ if !onlycloud and !mu_name.nil?
1909
+ # DNS cleanup is now done in MU::Cloud::DNSZone. Keeping this for now
1910
+ if !zone_rrsets.empty?
1911
+ zone_rrsets.each { |rrset|
1912
+ if rrset.name.match(/^#{mu_name.downcase}\.server\.#{MU.myInstanceId}\.platform-mu/i)
1913
+ rrset.resource_records.each { |record|
1914
+ MU::Cloud::DNSZone.genericMuDNSEntry(name: mu_name, target: record.value, cloudclass: MU::Cloud::Server, delete: true)
1915
+ cleaned_dns = true
1916
+ }
1917
+ end
1918
+ }
1919
+ end
1920
+
1921
+ # Expunge traces left in Chef, Puppet or what have you
1922
+ MU::Groomer.supportedGroomers.each { |groomer|
1923
+ groomclass = MU::Groomer.loadGroomer(groomer)
1924
+ if !server_obj.nil? and !server_obj.config.nil? and !server_obj.config['vault_access'].nil?
1925
+ groomclass.cleanup(mu_name, server_obj.config['vault_access'], noop)
1926
+ else
1927
+ groomclass.cleanup(mu_name, [], noop)
1928
+ end
1929
+ }
1930
+
1931
+ if !noop
1932
+ if !server_obj.nil? and !server_obj.config.nil?
1933
+ MU.mommacat.notify(MU::Cloud::Server.cfg_plural, server_obj.config['name'], {}, mu_name: server_obj.mu_name, remove: true) if MU.mommacat
1934
+ end
1935
+ end
1936
+
1937
+ # If we didn't manage to find this instance's Route53 entry by sifting
1938
+ # deployment metadata, see if we can get it with the Name tag.
1939
+ if !mu_zone.nil? and !cleaned_dns and !instance.nil?
1940
+ instance.tags.each { |tag|
1941
+ if tag.key == "Name"
1942
+ zone_rrsets.each { |rrset|
1943
+ if rrset.name.match(/^#{tag.value.downcase}\.server\.#{MU.myInstanceId}\.platform-mu/i)
1944
+ rrset.resource_records.each { |record|
1945
+ MU::Cloud::DNSZone.genericMuDNSEntry(name: tag.value, target: record.value, cloudclass: MU::Cloud::Server, delete: true) if !noop
1946
+ }
1947
+ end
1948
+ }
1949
+ end
1950
+ }
1951
+ end
1952
+ end
1953
+ end
1954
+
1955
+ if ips.size > 0 and !onlycloud
1956
+ known_hosts_files = [Etc.getpwuid(Process.uid).dir+"/.ssh/known_hosts"]
1957
+ if Etc.getpwuid(Process.uid).name == "root"
1958
+ known_hosts_files << Etc.getpwnam("nagios").dir+"/.ssh/known_hosts"
1959
+ end
1960
+ known_hosts_files.each { |known_hosts|
1961
+ next if !File.exists?(known_hosts)
1962
+ MU.log "Cleaning up #{ips} from #{known_hosts}"
1963
+ if !noop
1964
+ File.open(known_hosts, File::CREAT|File::RDWR, 0644) { |f|
1965
+ f.flock(File::LOCK_EX)
1966
+ newlines = Array.new
1967
+ f.readlines.each { |line|
1968
+ ip_match = false
1969
+ ips.each { |ip|
1970
+ if line.match(/(^|,| )#{ip}( |,)/)
1971
+ MU.log "Expunging #{ip} from #{known_hosts}"
1972
+ ip_match = true
1973
+ end
1974
+ }
1975
+ newlines << line if !ip_match
1976
+ }
1977
+ f.rewind
1978
+ f.truncate(0)
1979
+ f.puts(newlines)
1980
+ f.flush
1981
+ f.flock(File::LOCK_UN)
1982
+ }
1983
+ end
1984
+ }
1985
+ end
1986
+
1987
+ return if instance.nil?
1988
+
1989
+ name = ""
1990
+ instance.tags.each { |tag|
1991
+ name = tag.value if tag.key == "Name"
1992
+ }
1993
+
1994
+ if instance.state.name == "terminated"
1995
+ MU.log "#{instance.instance_id} (#{name}) has already been terminated, skipping"
1996
+ else
1997
+ if instance.state.name == "terminating"
1998
+ MU.log "#{instance.instance_id} (#{name}) already terminating, waiting"
1999
+ elsif instance.state.name != "running" and instance.state.name != "pending" and instance.state.name != "stopping" and instance.state.name != "stopped"
2000
+ MU.log "#{instance.instance_id} (#{name}) is in state #{instance.state.name}, waiting"
2001
+ else
2002
+ MU.log "Terminating #{instance.instance_id} (#{name}) #{noop}"
2003
+ if !noop
2004
+ begin
2005
+ MU::Cloud::AWS.ec2(region).modify_instance_attribute(
2006
+ instance_id: instance.instance_id,
2007
+ disable_api_termination: {value: false}
2008
+ )
2009
+ MU::Cloud::AWS.ec2(region).terminate_instances(instance_ids: [instance.instance_id])
2010
+ # Small race window here with the state changing from under us
2011
+ rescue Aws::EC2::Errors::IncorrectInstanceState => e
2012
+ resp = MU::Cloud::AWS.ec2(region).describe_instances(instance_ids: [id])
2013
+ if !resp.nil? and !resp.reservations.nil? and !resp.reservations.first.nil?
2014
+ instance = resp.reservations.first.instances.first
2015
+ if !instance.nil? and instance.state.name != "terminated" and instance.state.name != "terminating"
2016
+ sleep 5
2017
+ retry
2018
+ end
2019
+ end
2020
+ rescue Aws::EC2::Errors::InternalError => e
2021
+ MU.log "Error #{e.inspect} while Terminating instance #{instance.instance_id} (#{name}), retrying", MU::WARN, details: e.inspect
2022
+ sleep 5
2023
+ retry
2024
+ end
2025
+ end
2026
+ end
2027
+ while instance.state.name != "terminated" and !noop
2028
+ sleep 30
2029
+ instance_response = MU::Cloud::AWS.ec2(region).describe_instances(instance_ids: [instance.instance_id])
2030
+ instance = instance_response.reservations.first.instances.first
2031
+ end
2032
+ MU.log "#{instance.instance_id} (#{name}) terminated" if !noop
2033
+ end
2034
+ end
2035
+
2036
+ # Cloud-specific configuration properties.
2037
+ # @param config [MU::Config]: The calling MU::Config object
2038
+ # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
2039
+ def self.schema(config)
2040
+ toplevel_required = []
2041
+ schema = {
2042
+ "ami_id" => {
2043
+ "type" => "string",
2044
+ "description" => "The Amazon EC2 AMI on which to base this instance. Will use the default appropriate for the platform, if not specified."
2045
+ },
2046
+ "image_id" => {
2047
+ "type" => "string",
2048
+ "description" => "Synonymous with ami_id"
2049
+ },
2050
+ "generate_iam_role" => {
2051
+ "type" => "boolean",
2052
+ "default" => true,
2053
+ "description" => "Generate a unique IAM profile for this Server or ServerPool.",
2054
+ },
2055
+ "iam_role" => {
2056
+ "type" => "string",
2057
+ "description" => "An Amazon IAM instance profile, from which to harvest role policies to merge into this node's own instance profile. If generate_iam_role is false, will simple use this profile.",
2058
+ },
2059
+ "canned_iam_policies" => {
2060
+ "type" => "array",
2061
+ "items" => {
2062
+ "description" => "IAM policies to attach, pre-defined by Amazon (e.g. AmazonEKSWorkerNodePolicy)",
2063
+ "type" => "string"
2064
+ }
2065
+ },
2066
+ "iam_policies" => {
2067
+ "type" => "array",
2068
+ "items" => {
2069
+ "description" => "Amazon-compatible role policies which will be merged into this node's own instance profile. Not valid with generate_iam_role set to false. Our parser expects the role policy document to me embedded under a named container, e.g. { 'name_of_policy':'{ <policy document> } }",
2070
+ "type" => "object"
2071
+ }
2072
+ },
2073
+ "ingress_rules" => {
2074
+ "items" => {
2075
+ "properties" => {
2076
+ "sgs" => {
2077
+ "type" => "array",
2078
+ "items" => {
2079
+ "description" => "Other AWS Security Groups; resources that are associated with this group will have this rule applied to their traffic",
2080
+ "type" => "string"
2081
+ }
2082
+ },
2083
+ "lbs" => {
2084
+ "type" => "array",
2085
+ "items" => {
2086
+ "description" => "AWS Load Balancers which will have this rule applied to their traffic",
2087
+ "type" => "string"
2088
+ }
2089
+ }
2090
+ }
2091
+ }
2092
+ }
2093
+ }
2094
+ [toplevel_required, schema]
2095
+ end
2096
+
2097
+ # Confirm that the given instance size is valid for the given region.
2098
+ # If someone accidentally specified an equivalent size from some other cloud provider, return something that makes sense. If nothing makes sense, return nil.
2099
+ # @param size [String]: Instance type to check
2100
+ # @param region [String]: Region to check against
2101
+ # @return [String,nil]
2102
+ def self.validateInstanceType(size, region)
2103
+ begin
2104
+ types = (MU::Cloud::AWS.listInstanceTypes(region))[region]
2105
+ rescue Aws::Pricing::Errors::UnrecognizedClientException
2106
+ MU.log "Saw authentication error communicating with Pricing API, going to assume our instance type is correct", MU::WARN
2107
+ return size
2108
+ end
2109
+ if size.nil? or !types.has_key?(size)
2110
+ # See if it's a type we can approximate from one of the other clouds
2111
+ gtypes = (MU::Cloud::Google.listInstanceTypes)[MU::Cloud::Google.myRegion]
2112
+ foundmatch = false
2113
+ if gtypes and gtypes.size > 0 and gtypes.has_key?(size)
2114
+ vcpu = gtypes[size]["vcpu"]
2115
+ mem = gtypes[size]["memory"]
2116
+ ecu = gtypes[size]["ecu"]
2117
+ types.keys.sort.reverse.each { |type|
2118
+ features = types[type]
2119
+ next if ecu == "Variable" and ecu != features["ecu"]
2120
+ next if features["vcpu"] != vcpu
2121
+ if (features["memory"] - mem.to_f).abs < 0.10*mem
2122
+ foundmatch = true
2123
+ MU.log "You specified a Google Compute instance type '#{size}.' Approximating with Amazon EC2 type '#{type}.'", MU::WARN
2124
+ size = type
2125
+ break
2126
+ end
2127
+ }
2128
+ end
2129
+ if !foundmatch
2130
+ MU.log "Invalid size '#{size}' for AWS EC2 instance in #{region}. Supported types:", MU::ERR, details: types.keys.sort.join(", ")
2131
+ return nil
2132
+ end
2133
+ end
2134
+ size
2135
+ end
2136
+
2137
+ # Cloud-specific pre-processing of {MU::Config::BasketofKittens::servers}, bare and unvalidated.
2138
+ # @param server [Hash]: The resource to process and validate
2139
+ # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
2140
+ # @return [Boolean]: True if validation succeeded, False otherwise
2141
+ def self.validateConfig(server, configurator)
2142
+ ok = true
2143
+
2144
+ server['size'] = validateInstanceType(server["size"], server["region"])
2145
+ ok = false if server['size'].nil?
2146
+
2147
+ if !server['generate_iam_role']
2148
+ if !server['iam_role'] and server['cloud'] != "CloudFormation"
2149
+ MU.log "Must set iam_role if generate_iam_role set to false", MU::ERR
2150
+ ok = false
2151
+ end
2152
+ if !server['iam_policies'].nil? and server['iam_policies'].size > 0
2153
+ MU.log "Cannot mix iam_policies with generate_iam_role set to false", MU::ERR
2154
+ ok = false
2155
+ end
2156
+ else
2157
+ role = {
2158
+ "name" => server["name"],
2159
+ "can_assume" => [
2160
+ {
2161
+ "entity_id" => "ec2.amazonaws.com",
2162
+ "entity_type" => "service"
2163
+ }
2164
+ ],
2165
+ "policies" => [
2166
+ {
2167
+ "name" => "MuSecrets",
2168
+ "permissions" => ["s3:GetObject"],
2169
+ "targets" => [
2170
+ {
2171
+ "identifier" => 'arn:'+(MU::Cloud::AWS.isGovCloud?(server['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU.adminBucketName+'/Mu_CA.pem'
2172
+ }
2173
+ ]
2174
+ }
2175
+ ]
2176
+ }
2177
+ if server['iam_policies']
2178
+ role['iam_policies'] = server['iam_policies'].dup
2179
+ end
2180
+ if server['canned_policies']
2181
+ role['import'] = server['canned_policies'].dup
2182
+ end
2183
+ if server['iam_role']
2184
+ # XXX maybe break this down into policies and add those?
2185
+ end
2186
+
2187
+ configurator.insertKitten(role, "roles")
2188
+ server["dependencies"] ||= []
2189
+ server["dependencies"] << {
2190
+ "type" => "role",
2191
+ "name" => server["name"]
2192
+ }
2193
+ end
2194
+ if !server['create_image'].nil?
2195
+ if server['create_image'].has_key?('copy_to_regions') and
2196
+ (server['create_image']['copy_to_regions'].nil? or
2197
+ server['create_image']['copy_to_regions'].include?("#ALL") or
2198
+ server['create_image']['copy_to_regions'].size == 0
2199
+ )
2200
+ server['create_image']['copy_to_regions'] = MU::Cloud::AWS.listRegions(server['us_only'])
2201
+ end
2202
+ end
2203
+
2204
+ server['ami_id'] ||= server['image_id']
2205
+
2206
+ if server['ami_id'].nil?
2207
+ if MU::Config.amazon_images.has_key?(server['platform']) and
2208
+ MU::Config.amazon_images[server['platform']].has_key?(server['region'])
2209
+ server['ami_id'] = configurator.getTail("server"+server['name']+"AMI", value: MU::Config.amazon_images[server['platform']][server['region']], prettyname: "server"+server['name']+"AMI", cloudtype: "AWS::EC2::Image::Id")
2210
+ else
2211
+ MU.log "No AMI specified for #{server['name']} and no default available for platform #{server['platform']} in region #{server['region']}", MU::ERR, details: server
2212
+ ok = false
2213
+ end
2214
+ end
2215
+
2216
+ if !server["loadbalancers"].nil?
2217
+ server["loadbalancers"].each { |lb|
2218
+ if lb["concurrent_load_balancer"] != nil
2219
+ server["dependencies"] << {
2220
+ "type" => "loadbalancer",
2221
+ "name" => lb["concurrent_load_balancer"]
2222
+ }
2223
+ end
2224
+ }
2225
+ end
2226
+
2227
+ if !server["vpc"].nil?
2228
+ if server["vpc"]["subnet_name"].nil? and server["vpc"]["subnet_id"].nil? and server["vpc"]["subnet_pref"].nil?
2229
+ MU.log "A server VPC block must specify a target subnet", MU::ERR
2230
+ ok = false
2231
+ end
2232
+ end
2233
+
2234
+ ok
2235
+ end
2236
+
2237
+ private
2238
+
2239
+ # Destroy a volume.
2240
+ # @param volume [OpenStruct]: The cloud provider's description of the volume.
2241
+ # @param id [String]: The cloud provider's identifier for the volume, to use if the full description is not available.
2242
+ # @param region [String]: The cloud provider region
2243
+ # @return [void]
2244
+ def self.delete_volume(volume, noop, skipsnapshots, id: nil, region: MU.curRegion)
2245
+ if !volume.nil?
2246
+ resp = MU::Cloud::AWS.ec2(region).describe_volumes(volume_ids: [volume.volume_id])
2247
+ volume = resp.data.volumes.first
2248
+ end
2249
+ name = ""
2250
+ volume.tags.each { |tag|
2251
+ name = tag.value if tag.key == "Name"
2252
+ }
2253
+
2254
+ MU.log("Deleting volume #{volume.volume_id} (#{name})")
2255
+ if !noop
2256
+ if !skipsnapshots
2257
+ if !name.nil? and !name.empty?
2258
+ desc = "#{MU.deploy_id}-MUfinal (#{name})"
2259
+ else
2260
+ desc = "#{MU.deploy_id}-MUfinal"
2261
+ end
2262
+
2263
+ MU::Cloud::AWS.ec2(region).create_snapshot(
2264
+ volume_id: volume.volume_id,
2265
+ description: desc
2266
+ )
2267
+ end
2268
+
2269
+ retries = 0
2270
+ begin
2271
+ MU::Cloud::AWS.ec2(region).delete_volume(volume_id: volume.volume_id)
2272
+ rescue Aws::EC2::Errors::InvalidVolumeNotFound
2273
+ MU.log "Volume #{volume.volume_id} (#{name}) disappeared before I could remove it!", MU::WARN
2274
+ rescue Aws::EC2::Errors::VolumeInUse
2275
+ if retries < 10
2276
+ volume.attachments.each { |attachment|
2277
+ MU.log "#{volume.volume_id} is attached to #{attachment.instance_id} as #{attachment.device}", MU::NOTICE
2278
+ }
2279
+ MU.log "Volume '#{name}' is still attached, waiting...", MU::NOTICE
2280
+ sleep 30
2281
+ retries = retries + 1
2282
+ retry
2283
+ else
2284
+ MU.log "Failed to delete #{name}", MU::ERR
2285
+ end
2286
+ end
2287
+ end
2288
+ end
2289
+
2290
+
2291
+ end #class
2292
+ end #class
2293
+ end
2294
+ end #module