cloud-mu 1.9.0.pre.beta

Sign up to get free protection for your applications and to get access to all the features.
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