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,2703 @@
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
+ autoload :Net, 'net/ssh'
16
+ require 'fileutils'
17
+ require 'json'
18
+ require 'stringio'
19
+ require 'securerandom'
20
+ gem "chef"
21
+ autoload :Chef, 'chef'
22
+ gem "chef-vault"
23
+ autoload :ChefVault, 'chef-vault'
24
+ require 'timeout'
25
+
26
+ module MU
27
+
28
+ # MommaCat is in charge of managing metadata about resources we've created,
29
+ # as well as orchestrating amongst them and bootstrapping nodes outside of
30
+ # the normal synchronous deploy sequence invoked by *mu-deploy*.
31
+ class MommaCat
32
+
33
+ # An exception denoting a failure in MommaCat#fetchSecret and related methods
34
+ class SecretError < MuError;
35
+ end
36
+
37
+ # Failure to load or create a deploy
38
+ class DeployInitializeError < MuError;
39
+ end
40
+
41
+ # Failure to groom a node
42
+ class GroomError < MuError;
43
+ end
44
+
45
+ @@litters = {}
46
+ @@litter_semaphore = Mutex.new
47
+
48
+ # Return a {MU::MommaCat} instance for an existing deploy. Use this instead
49
+ # of using #initialize directly to avoid loading deploys multiple times or
50
+ # stepping on the global context for the deployment you're really working
51
+ # on..
52
+ # @param deploy_id [String]: The deploy ID of the deploy to load.
53
+ # @param set_context_to_me [Boolean]: Whether new MommaCat objects should overwrite any existing per-thread global deploy variables.
54
+ # @param use_cache [Boolean]: If we have an existing object for this deploy, use that
55
+ # @return [MU::MommaCat]
56
+ def self.getLitter(deploy_id, set_context_to_me: false, use_cache: true)
57
+ if deploy_id.nil? or deploy_id.empty?
58
+ raise MuError, "Cannot fetch a deployment without a deploy_id"
59
+ end
60
+ # XXX this caching may be harmful, causing stale resource objects to stick
61
+ # around. Have we fixed this? Sort of. Bad entries seem to have no kittens,
62
+ # so force a reload if we see that. That's probably not the root problem.
63
+ @@litter_semaphore.synchronize {
64
+
65
+ if !use_cache or !@@litters.has_key?(deploy_id) or @@litters[deploy_id].kittens.nil? or @@litters[deploy_id].kittens.size == 0
66
+ @@litters[deploy_id] = MU::MommaCat.new(deploy_id, set_context_to_me: set_context_to_me)
67
+ elsif set_context_to_me
68
+ MU::MommaCat.setThreadContext(@@litters[deploy_id])
69
+ end
70
+ return @@litters[deploy_id]
71
+ }
72
+ # MU::MommaCat.new(deploy_id, set_context_to_me: set_context_to_me)
73
+ end
74
+
75
+ attr_reader :public_key
76
+ attr_reader :deploy_secret
77
+ attr_reader :deployment
78
+ attr_reader :original_config
79
+ attr_reader :environment
80
+ attr_reader :ssh_key_name
81
+ attr_reader :ssh_public_key
82
+ attr_reader :nocleanup
83
+ attr_reader :deploy_id
84
+ attr_reader :timestamp
85
+ attr_reader :appname
86
+ attr_reader :handle
87
+ attr_reader :seed
88
+ attr_reader :mu_user
89
+ attr_reader :clouds
90
+ attr_reader :chef_user
91
+ attr_reader :no_artifacts
92
+ attr_accessor :kittens # really want a method only available to :Deploy
93
+ @myhome = Etc.getpwuid(Process.uid).dir
94
+ @nagios_home = "/opt/mu/var/nagios_user_home"
95
+ @locks = Hash.new
96
+ @deploy_cache = Hash.new
97
+ @nocleanup = false
98
+ # List the currently held flock() locks.
99
+ def self.trapSafeLocks;
100
+ @locks
101
+ end
102
+ # List the currently held flock() locks.
103
+ def self.locks;
104
+ @lock_semaphore.synchronize {
105
+ @locks
106
+ }
107
+ end
108
+
109
+ @@deploy_struct_semaphore = Mutex.new
110
+ # Don't let things that modify the deploy struct Hash step on each other.
111
+ # @return [Mutex]
112
+ def self.deploy_struct_semaphore;
113
+ @@deploy_struct_semaphore
114
+ end
115
+
116
+ # Set the current threads' context (some knucklehead global variables) to
117
+ # values pertinent to the given deployment object.
118
+ # @param deploy [MU::MommaCat]: A deployment object
119
+ def self.setThreadContext(deploy)
120
+ raise MuError, "Didn't get a MU::MommaCat object in setThreadContext" if !deploy.is_a?(MU::MommaCat)
121
+ if !deploy.mu_user.nil?
122
+ MU.setVar("chef_user", deploy.chef_user)
123
+ if deploy.mu_user != "mu" and deploy.mu_user != "root"
124
+ MU.setVar("dataDir", Etc.getpwnam(deploy.mu_user).dir+"/.mu/var")
125
+ MU.setVar("mu_user", deploy.mu_user)
126
+ else
127
+ MU.setVar("dataDir", MU.mainDataDir)
128
+ MU.setVar("mu_user", "root")
129
+ end
130
+ end
131
+ MU.setVar("mommacat", deploy)
132
+ MU.setVar("deploy_id", deploy.deploy_id)
133
+ MU.setVar("appname", deploy.appname)
134
+ MU.setVar("environment", deploy.environment)
135
+ MU.setVar("timestamp", deploy.timestamp)
136
+ MU.setVar("seed", deploy.seed)
137
+ MU.setVar("handle", deploy.handle)
138
+ end
139
+
140
+ # @param deploy_id [String]: The MU identifier of the deployment to load or create.
141
+ # @param create [Boolean]: Create a new deployment instead of searching for an existing one.
142
+ # @param deploy_secret [String]: A secret encrypted by the private key of a deployment we're loading. Used to validate remote requests to bootstrap into this deployment.
143
+ # @param config [Hash]: The full configuration, parsed by {MU::Config}, of this deployment. Required when creating a new deployment.
144
+ # @param environment [String]: The environment of a deployment to create.
145
+ # @param ssh_key_name [String]: Required when creating a new deployment.
146
+ # @param ssh_private_key [String]: Required when creating a new deployment.
147
+ # @param ssh_public_key [String]: SSH public key for authorized_hosts on clients.
148
+ # @param skip_resource_objects [Boolean]: Whether preload the cloud resource objects from this deploy. Can save load time for simple MommaCat tasks.
149
+ # @param nocleanup [Boolean]: Skip automatic cleanup of failed resources
150
+ # @param no_artifacts [Boolean]: Do not save deploy metadata
151
+ # @param deployment_data [Hash]: Known deployment data.
152
+ # @return [void]
153
+ def initialize(deploy_id,
154
+ create: false,
155
+ deploy_secret: nil,
156
+ config: nil,
157
+ environment: "dev",
158
+ ssh_key_name: nil,
159
+ ssh_private_key: nil,
160
+ ssh_public_key: nil,
161
+ nocleanup: false,
162
+ set_context_to_me: true,
163
+ skip_resource_objects: false,
164
+ no_artifacts: false,
165
+ deployment_data: {},
166
+ mu_user: Etc.getpwuid(Process.uid).name
167
+ )
168
+ if deploy_id.nil? or deploy_id.empty?
169
+ raise DeployInitializeError, "MommaCat objects must specify a deploy_id"
170
+ end
171
+ set_context_to_me = true if create
172
+
173
+ @deploy_id = deploy_id
174
+ @mu_user = mu_user.dup
175
+ @no_artifacts = no_artifacts
176
+
177
+ # Make sure mu_user and chef_user are sane.
178
+ if @mu_user == "root"
179
+ @chef_user = "mu"
180
+ else
181
+ @chef_user = @mu_user.dup.gsub(/\./, "")
182
+ @mu_user = "root" if @mu_user == "mu"
183
+ end
184
+ @kitten_semaphore = Mutex.new
185
+ @kittens = {}
186
+ @original_config = config
187
+ @nocleanup = nocleanup
188
+ @secret_semaphore = Mutex.new
189
+ @notify_semaphore = Mutex.new
190
+ @node_cert_semaphore = Mutex.new
191
+ @deployment = deployment_data
192
+ @deployment['mu_public_ip'] = MU.mu_public_ip
193
+ @private_key = nil
194
+ @public_key = nil
195
+ @secrets = Hash.new
196
+ @secrets['instance_secret'] = Hash.new
197
+ @environment = environment
198
+ @ssh_key_name = ssh_key_name
199
+ @ssh_private_key = ssh_private_key
200
+ @ssh_public_key = ssh_public_key
201
+ @clouds = {}
202
+ @seed = MU.seed # pass this in
203
+ @handle = MU.handle # pass this in
204
+ if set_context_to_me
205
+ MU::MommaCat.setThreadContext(self)
206
+ end
207
+ if create and !@no_artifacts
208
+ if !Dir.exist?(MU.dataDir+"/deployments")
209
+ MU.log "Creating #{MU.dataDir}/deployments", MU::DEBUG
210
+ Dir.mkdir(MU.dataDir+"/deployments", 0700)
211
+ end
212
+ path = File.expand_path(MU.dataDir+"/deployments")+"/"+@deploy_id
213
+ if !Dir.exist?(path)
214
+ MU.log "Creating #{path}", MU::DEBUG
215
+ Dir.mkdir(path, 0700)
216
+ end
217
+ if @original_config.nil? or !@original_config.is_a?(Hash)
218
+ raise DeployInitializeError, "New MommaCat repository requires config hash"
219
+ end
220
+ @appname = @original_config['name']
221
+ MU::Cloud.resource_types.each { |cloudclass, data|
222
+ if !@original_config[data[:cfg_plural]].nil? and @original_config[data[:cfg_plural]].size > 0
223
+ @original_config[data[:cfg_plural]].each { |resource|
224
+ @clouds[resource['cloud']] = 0 if !@clouds.has_key?(resource['cloud'])
225
+ @clouds[resource['cloud']] = @clouds[resource['cloud']] + 1
226
+ }
227
+ end
228
+ }
229
+ @ssh_key_name, @ssh_private_key, @ssh_public_key = self.SSHKey
230
+ if !File.exist?(deploy_dir+"/private_key")
231
+ @private_key, @public_key = createDeployKey
232
+ end
233
+ MU.log "Creating deploy secret for #{MU.deploy_id}"
234
+ @deploy_secret = Password.random(256)
235
+ if !@original_config['scrub_mu_isms']
236
+ # TODO there's a nicer way to do this than hardcoding strings
237
+ if @clouds["AWS"] and @clouds["AWS"] > 0
238
+ MU::Cloud::AWS.writeDeploySecret(@deploy_id, @deploy_secret)
239
+ end
240
+ if @clouds["Google"] and @clouds["Google"] > 0
241
+ MU::Cloud::Google.writeDeploySecret(@deploy_id, @deploy_secret)
242
+ end
243
+ end
244
+ if set_context_to_me
245
+ MU::MommaCat.setThreadContext(self)
246
+ end
247
+ save!
248
+ end
249
+
250
+
251
+ loadDeploy(set_context_to_me: set_context_to_me)
252
+ if !deploy_secret.nil?
253
+ if !authKey(deploy_secret)
254
+ raise DeployInitializeError, "Invalid or incorrect deploy key."
255
+ end
256
+ end
257
+
258
+
259
+ # Initialize a MU::Cloud object for each resource belonging to this
260
+ # deploy, IF it already exists, which is to say if we're loading an
261
+ # existing deploy instead of creating a new one.
262
+ if !create and @deployment and @original_config and !skip_resource_objects
263
+ MU::Cloud.resource_types.each_pair { |res_type, attrs|
264
+ type = attrs[:cfg_plural]
265
+ if @deployment.has_key?(type)
266
+ @deployment[type].each_pair { |res_name, data|
267
+ orig_cfg = nil
268
+ if @original_config.has_key?(type)
269
+ @original_config[type].each { |resource|
270
+ if resource["name"] == res_name
271
+ orig_cfg = resource
272
+ break
273
+ end
274
+ }
275
+ end
276
+
277
+ # Some Server objects originated from ServerPools, get their
278
+ # configs from there
279
+ if type == "servers" and orig_cfg.nil? and
280
+ @original_config.has_key?("server_pools")
281
+ @original_config["server_pools"].each { |resource|
282
+ if resource["name"] == res_name
283
+ orig_cfg = resource
284
+ break
285
+ end
286
+ }
287
+ end
288
+ if orig_cfg.nil?
289
+ MU.log "Failed to locate original config for #{attrs[:cfg_name]} #{res_name} in #{@deploy_id}", MU::WARN if !["firewall_rules", "databases", "storage_pools", "cache_clusters", "alarms"].include?(type) # XXX shaddap
290
+ next
291
+ end
292
+ begin
293
+ # Load up MU::Cloud objects for all our kittens in this deploy
294
+ orig_cfg['environment'] = @environment # not always set in old deploys
295
+ if attrs[:has_multiples]
296
+ data.each_pair { |mu_name, actual_data|
297
+ attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: mu_name)
298
+ }
299
+ else
300
+ # XXX hack for old deployments, this can go away some day
301
+ if data['mu_name'].nil? or data['mu_name'].empty?
302
+ if res_type.to_s == "LoadBalancer" and !data['awsname'].nil?
303
+ data['mu_name'] = data['awsname'].dup
304
+ elsif res_type.to_s == "FirewallRule" and !data['group_name'].nil?
305
+ data['mu_name'] = data['group_name'].dup
306
+ elsif res_type.to_s == "Database" and !data['identifier'].nil?
307
+ data['mu_name'] = data['identifier'].dup.upcase
308
+ elsif res_type.to_s == "VPC"
309
+ # VPC names are deterministic, just generate the things
310
+ data['mu_name'] = getResourceName(data['name'])
311
+ end
312
+ end
313
+ if data['mu_name'].nil?
314
+ raise MuError, "Unable to find or guess a Mu name for #{res_type}: #{res_name} in #{@deploy_id}"
315
+ end
316
+ attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: data['mu_name'], cloud_id: data['cloud_id'])
317
+ end
318
+ rescue Exception => e
319
+ MU.log "Failed to load an existing resource of type '#{type}' in #{@deploy_id}: #{e.inspect}", MU::WARN, details: e.backtrace
320
+ end
321
+ }
322
+ end
323
+ }
324
+ end
325
+
326
+ # XXX this .owned? method may get changed by the Ruby maintainers
327
+ # if !@@litter_semaphore.owned?
328
+ # @@litter_semaphore.synchronize {
329
+ # @@litters[@deploy_id] = self
330
+ # }
331
+ # end
332
+ end
333
+
334
+ # Tell us the number of first-class resources we've configured, optionally
335
+ # filtering results to only include a given type and/or in a given cloud
336
+ # environment.
337
+ # @param clouds [Array<String>]: The cloud environment(s) to check for. If unspecified, will match all environments in this deployment.
338
+ # @param types [Array<String>]: The type of resource(s) to check for. If unspecified, will match all resources in this deployment.
339
+ # @param negate [Boolean]: Invert logic of the other filters if they are specified, e.g. search for all cloud resources that are *not* AWS.
340
+ def numKittens(clouds: [], types: [], negate: false)
341
+ realtypes = []
342
+ return 0 if @original_config.nil?
343
+ if !types.nil? and types.size > 0
344
+ types.each { |type|
345
+ shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
346
+ realtypes << cfg_plural
347
+ }
348
+ end
349
+
350
+ count = 0
351
+ MU::Cloud.resource_types.each { |cloudclass, data|
352
+ next if @original_config[data[:cfg_plural]].nil?
353
+ next if realtypes.size > 0 and (!negate and !realtypes.include?(data[:cfg_plural]))
354
+ @original_config[data[:cfg_plural]].each { |resource|
355
+ if clouds.nil? or clouds.size == 0 or (!negate and clouds.include?(resource["cloud"])) or (negate and !clouds.include?(resource["cloud"]))
356
+ count = count + 1
357
+ end
358
+ }
359
+ }
360
+ count
361
+ end
362
+
363
+ # @param object [MU::Cloud]:
364
+ def removeKitten(object)
365
+ if !object
366
+ raise MuError, "Nil arguments to removeKitten are not allowed"
367
+ end
368
+ @kitten_semaphore.synchronize {
369
+ MU::Cloud.resource_types.each_pair { |name, attrs|
370
+ type = attrs[:cfg_plural]
371
+ next if !@kittens.has_key?(type)
372
+ tmplitter = @kittens[type].values.dup
373
+ tmplitter.each { |nodeclass, data|
374
+ if data.is_a?(Hash)
375
+ data.each_pair { |mu_name, obj|
376
+ if data == object
377
+ @kittens[type][nodeclass].delete(mu_name)
378
+ return
379
+ end
380
+ }
381
+ else
382
+ if data == object
383
+ @kittens[type].delete(nodeclass)
384
+ return
385
+ end
386
+ end
387
+ }
388
+ }
389
+ }
390
+ @kittens
391
+ end
392
+
393
+ # Overwrite this deployment's configuration with a new version. Save the
394
+ # previous version as well.
395
+ # @param new_conf [Hash]: A new configuration, fully resolved by {MU::Config}
396
+ def updateBasketofKittens(new_conf)
397
+ loadDeploy
398
+ if new_conf == @original_config
399
+ MU.log "#{@deploy_id}", MU::WARN
400
+ return
401
+ end
402
+
403
+ backup = "#{deploy_dir}/basket_of_kittens.json.#{Time.now.to_i.to_s}"
404
+ MU.log "Saving previous config of #{@deploy_id} to #{backup}"
405
+ config = File.new(backup, File::CREAT|File::TRUNC|File::RDWR, 0600)
406
+ config.flock(File::LOCK_EX)
407
+ config.puts JSON.pretty_generate(@original_config)
408
+ config.flock(File::LOCK_UN)
409
+ config.close
410
+
411
+ @original_config = new_conf
412
+ # save! # XXX this will happen later, more sensibly
413
+ MU.log "New config saved to #{deploy_dir}/basket_of_kittens.json"
414
+ end
415
+
416
+ # Keep tabs on a {MU::Cloud} object so that it can be found easily by
417
+ # #findLitterMate.
418
+ # @param type [String]:
419
+ # @param name [String]:
420
+ # @param object [MU::Cloud]:
421
+ def addKitten(type, name, object)
422
+ if !type or !name or !object or !object.mu_name
423
+ raise MuError, "Nil arguments to addKitten are not allowed (got type: #{type}, name: #{name}, and '#{object}' to add)"
424
+ end
425
+ shortclass, cfg_name, cfg_plural, classname, attrs = MU::Cloud.getResourceNames(type)
426
+ type = cfg_plural
427
+ has_multiples = attrs[:has_multiples]
428
+
429
+ @kitten_semaphore.synchronize {
430
+ @kittens[type] ||= {}
431
+ if has_multiples
432
+ @kittens[type][name] ||= {}
433
+ @kittens[type][name][object.mu_name] = object
434
+ else
435
+ @kittens[type][name] = object
436
+ end
437
+ }
438
+ end
439
+
440
+ # Check a provided deploy key against our stored version. The instance has
441
+ # in theory accessed a secret via S3 and encrypted it with the deploy's
442
+ # public key. If it decrypts correctly, we assume this instance is indeed
443
+ # one of ours.
444
+ # @param ciphertext [String]: The text to decrypt.
445
+ # return [Boolean]: Whether the provided text was encrypted with the correct key
446
+ def authKey(ciphertext)
447
+ if @private_key.nil? or @deploy_secret.nil?
448
+ MU.log "Missing auth metadata, can't authorize node in authKey", MU::ERR
449
+ return false
450
+ end
451
+ my_key = OpenSSL::PKey::RSA.new(@private_key)
452
+
453
+ begin
454
+ if my_key.private_decrypt(ciphertext).force_encoding("UTF-8") == @deploy_secret.force_encoding("UTF-8")
455
+ MU.log "Matched ciphertext for #{MU.deploy_id}", MU::INFO
456
+ return true
457
+ else
458
+ MU.log "Mis-matched ciphertext for #{MU.deploy_id}", MU::ERR
459
+ return false
460
+ end
461
+ rescue OpenSSL::PKey::RSAError => e
462
+ MU.log e.inspect, MU::ERR
463
+ return false
464
+ end
465
+ end
466
+
467
+ # Generate a three-character string which can be used to unique-ify the
468
+ # names of resources which might potentially collide, e.g. Windows local
469
+ # hostnames, Amazon Elastic Load Balancers, or server pool instances.
470
+ # @return [String]: A three-character string consisting of two alphnumeric
471
+ # characters (uppercase) and one number.
472
+ def self.genUniquenessString
473
+ begin
474
+ candidate = SecureRandom.base64(2).slice(0..1) + SecureRandom.random_number(9).to_s
475
+ candidate.upcase!
476
+ end while candidate.match(/[^A-Z0-9]/)
477
+ return candidate
478
+ end
479
+
480
+ @unique_map_semaphore = Mutex.new
481
+ @name_unique_str_map = {}
482
+ # Keep a map of the uniqueness strings we assign to various full names, in
483
+ # case we want to reuse them later.
484
+ # @return [Hash<String>]
485
+ def self.name_unique_str_map
486
+ @name_unique_str_map
487
+ end
488
+
489
+ # Keep a map of the uniqueness strings we assign to various full names, in
490
+ # case we want to reuse them later.
491
+ # @return [Mutex]
492
+ def self.unique_map_semaphore
493
+ @unique_map_semaphore
494
+ end
495
+
496
+ # Generate a name string for a resource, incorporate the MU identifier
497
+ # for this deployment. Will dynamically shorten the name to fit for
498
+ # restrictive uses (e.g. Windows local hostnames, Amazon Elastic Load
499
+ # Balancers).
500
+ # @param name [String]: The shorthand name of the resource, usually the value of the "name" field in an Mu resource declaration.
501
+ # @param max_length [Integer]: The maximum length of the resulting resource name.
502
+ # @param need_unique_string [Boolean]: Whether to forcibly append a random three-character string to the name to ensure it's unique. Note that this behavior will be automatically invoked if the name must be truncated.
503
+ # @param scrub_mu_isms [Boolean]: Don't bother with generating names specific to this deployment. Used to generate generic CloudFormation templates, amongst other purposes.
504
+ # @return [String]: A full name string for this resource
505
+ def getResourceName(name, max_length: 255, need_unique_string: false, use_unique_string: nil, reuse_unique_string: false, scrub_mu_isms: @original_config['scrub_mu_isms'])
506
+ if name.nil?
507
+ raise MuError, "Got no argument to MU::MommaCat.getResourceName"
508
+ end
509
+ if @appname.nil? or @environment.nil? or @timestamp.nil? or @seed.nil?
510
+ MU.log "Missing global deploy variables in thread #{Thread.current.object_id}, using bare name '#{name}' (appname: #{@appname}, environment: #{@environment}, timestamp: #{@timestamp}, seed: #{@seed}", MU::WARN, details: caller
511
+ return name
512
+ end
513
+ need_unique_string = false if scrub_mu_isms
514
+
515
+ muname = nil
516
+ if need_unique_string
517
+ reserved = 4
518
+ else
519
+ reserved = 0
520
+ end
521
+
522
+ # First, pare down the base name string until it will fit
523
+ basename = @appname.upcase + "-" + @environment.upcase + "-" + @timestamp + "-" + @seed.upcase + "-" + name.upcase
524
+ if scrub_mu_isms
525
+ basename = @appname.upcase + "-" + @environment.upcase + name.upcase
526
+ end
527
+
528
+ begin
529
+ if (basename.length + reserved) > max_length
530
+ MU.log "Stripping name down from #{basename}[#{basename.length.to_s}] (reserved: #{reserved.to_s}, max_length: #{max_length.to_s})", MU::DEBUG
531
+ if basename == @appname.upcase + "-" + @seed.upcase + "-" + name.upcase
532
+ # If we've run out of stuff to strip, truncate what's left and
533
+ # just leave room for the deploy seed and uniqueness string. This
534
+ # is the bare minimum, and probably what you'll see for most Windows
535
+ # hostnames.
536
+ basename = name.upcase + "-" + @appname.upcase
537
+ basename.slice!((max_length-(reserved+3))..basename.length)
538
+ basename.sub!(/-$/, "")
539
+ basename = basename + "-" + @seed.upcase
540
+ else
541
+ # If we have to strip anything, assume we've lost uniqueness and
542
+ # will have to compensate with #genUniquenessString.
543
+ need_unique_string = true
544
+ reserved = 4
545
+ basename.sub!(/-[^-]+-#{@seed.upcase}-#{Regexp.escape(name.upcase)}$/, "")
546
+ basename = basename + "-" + @seed.upcase + "-" + name.upcase
547
+ end
548
+ end
549
+ end while (basename.length + reserved) > max_length
550
+
551
+ # Finally, apply our short random differentiator, if it's needed.
552
+ if need_unique_string
553
+ # Preferentially use a requested one, if it's not already in use.
554
+ if !use_unique_string.nil?
555
+ muname = basename + "-" + use_unique_string
556
+ if !allocateUniqueResourceName(muname) and !reuse_unique_string
557
+ MU.log "Requested to use #{use_unique_string} as differentiator when naming #{name}, but the name #{muname} is unavailable.", MU::WARN
558
+ muname = nil
559
+ end
560
+ end
561
+ if !muname
562
+ begin
563
+ unique_string = MU::MommaCat.genUniquenessString
564
+ muname = basename + "-" + unique_string
565
+ end while !allocateUniqueResourceName(muname)
566
+ MU::MommaCat.unique_map_semaphore.synchronize {
567
+ MU::MommaCat.name_unique_str_map[muname] = unique_string
568
+ }
569
+ end
570
+ else
571
+ muname = basename
572
+ end
573
+
574
+ return muname
575
+ end
576
+
577
+
578
+ # Encrypt a string with the deployment's public key.
579
+ # @param ciphertext [String]: The string to encrypt
580
+ def encryptWithDeployKey(ciphertext)
581
+ my_public_key = OpenSSL::PKey::RSA.new(@public_key)
582
+ return my_public_key.public_encrypt(ciphertext)
583
+ end
584
+
585
+ # Decrypt a string with the deployment's private key.
586
+ # @param ciphertext [String]: The string to decrypt
587
+ def decryptWithDeployKey(ciphertext)
588
+ my_private_key = OpenSSL::PKey::RSA.new(@private_key)
589
+ return my_private_key.private_decrypt(ciphertext)
590
+ end
591
+
592
+
593
+ # Save a string into deployment metadata for the current deployment,
594
+ # encrypting it with our deploy key.
595
+ # @param instance_id [String]: The cloud instance identifier with which this secret is associated.
596
+ # @param raw_secret [String]: The unencrypted string to store.
597
+ # @param type [String]: The type of secret, used to identify for retrieval.
598
+ def saveNodeSecret(instance_id, raw_secret, type)
599
+ return if @no_artifacts
600
+ if instance_id.nil? or instance_id.empty? or raw_secret.nil? or raw_secret.empty? or type.nil? or type.empty?
601
+ raise SecretError, "saveNodeSecret requires instance_id, raw_secret, and type args"
602
+ end
603
+ MU::MommaCat.lock("deployment-notification")
604
+ loadDeploy(true) # make sure we're not trampling deployment data
605
+ @secret_semaphore.synchronize {
606
+ if @secrets[type].nil?
607
+ raise SecretError, "'#{type}' is not a valid secret type (valid types: #{@secrets.keys.to_s})"
608
+ end
609
+ @secrets[type][instance_id] = encryptWithDeployKey(raw_secret)
610
+ }
611
+ save!
612
+ MU::MommaCat.unlock("deployment-notification")
613
+ end
614
+
615
+ # Retrieve an encrypted secret from metadata for the current deployment.
616
+ # @param instance_id [String]: The cloud instance identifier with which this secret is associated.
617
+ # @param type [String]: The type of secret, used to identify for retrieval.
618
+ # @param quiet [Boolean]: Do not log errors for non-existent secrets
619
+ def fetchSecret(instance_id, type, quiet: false)
620
+ @secret_semaphore.synchronize {
621
+ if @secrets[type].nil?
622
+ return nil if quiet
623
+ raise SecretError, "'#{type}' is not a valid secret type (valid types: #{@secrets.keys.to_s})"
624
+ end
625
+ if @secrets[type][instance_id].nil?
626
+ return nil if quiet
627
+ raise SecretError, "No '#{type}' secret known for instance #{instance_id}"
628
+ end
629
+ }
630
+ return decryptWithDeployKey(@secrets[type][instance_id])
631
+ end
632
+
633
+
634
+ # Run {MU::Cloud::Server#postBoot} and {MU::Cloud::Server#groom} on a node.
635
+ # @param cloud_id [OpenStruct]: The cloud provider's identifier for this node.
636
+ # @param name [String]: The MU resource name of the node being created.
637
+ # @param mu_name [String]: The full #{MU::MommaCat.getResourceName} name of the server we're grooming, if it's been initialized already.
638
+ # @param type [String]: The type of resource that created this node (either *server* or *serverpool*).
639
+ def groomNode(cloud_id, name, type, mu_name: nil, reraise_fail: false, sync_wait: true)
640
+ if cloud_id.nil?
641
+ raise GroomError, "MU::MommaCat.groomNode requires a {MU::Cloud::Server} object"
642
+ end
643
+ if name.nil? or name.empty?
644
+ raise GroomError, "MU::MommaCat.groomNode requires a resource name"
645
+ end
646
+ if type.nil? or type.empty?
647
+ raise GroomError, "MU::MommaCat.groomNode requires a resource type"
648
+ end
649
+
650
+ if !MU::MommaCat.lock(cloud_id+"-mommagroom", true)
651
+ MU.log "Instance #{cloud_id} on #{MU.deploy_id} (#{type}: #{name}) is already being groomed, ignoring this extra request.", MU::NOTICE
652
+ MU::MommaCat.unlockAll
653
+ if !MU::MommaCat.locks.nil? and MU::MommaCat.locks.size > 0
654
+ puts "------------------------------"
655
+ puts "Open flock() locks:"
656
+ pp MU::MommaCat.locks
657
+ puts "------------------------------"
658
+ end
659
+ return
660
+ end
661
+ loadDeploy
662
+
663
+ # XXX this is to stop Net::SSH from killing our entire stack when it
664
+ # throws an exception. See ECAP-139 in JIRA. Far as we can tell, it's
665
+ # just not entirely thread safe.
666
+ Thread.handle_interrupt(Net::SSH::Disconnect => :never) {
667
+ begin
668
+ Thread.handle_interrupt(Net::SSH::Disconnect => :immediate) {
669
+ MU.log "(Probably harmless) Caught a Net::SSH::Disconnect in #{Thread.current.inspect}", MU::DEBUG, details: Thread.current.backtrace
670
+ }
671
+ ensure
672
+ end
673
+ }
674
+
675
+ if @original_config[type+"s"].nil?
676
+ raise GroomError, "I see no configured resources of type #{type} (bootstrap request for #{name} on #{@deploy_id})"
677
+ end
678
+ kitten = nil
679
+
680
+ kitten = findLitterMate(type: "server", name: name, mu_name: mu_name, cloud_id: cloud_id)
681
+ if !kitten.nil?
682
+ MU.log "Re-grooming #{mu_name}", details: kitten.deploydata
683
+ else
684
+ first_groom = true
685
+ @original_config[type+"s"].each { |svr|
686
+ if svr['name'] == name
687
+ svr["instance_id"] = cloud_id
688
+
689
+ # This will almost always be true in server pools, but lets be safe. Somewhat problematic because we are only
690
+ # looking at deploy_id, but we still know this is our DNS record and not a custom one.
691
+ if svr['dns_records'] && !svr['dns_records'].empty?
692
+ svr['dns_records'].each { |dnsrec|
693
+ if dnsrec.has_key?("name") && dnsrec['name'].start_with?(MU.deploy_id.downcase)
694
+ MU.log "DNS record for #{MU.deploy_id.downcase}, #{name} is probably wrong, deleting", MU::WARN, details: dnsrec
695
+ dnsrec.delete('name')
696
+ dnsrec.delete('target')
697
+ end
698
+ }
699
+ end
700
+
701
+ kitten = MU::Cloud::Server.new(mommacat: self, kitten_cfg: svr, cloud_id: cloud_id)
702
+ mu_name = kitten.mu_name if mu_name.nil?
703
+ MU.log "Grooming #{mu_name} for the first time", details: svr
704
+ break
705
+ end
706
+ }
707
+ end
708
+
709
+ begin
710
+ # This is a shared lock with MU::Cloud::AWS::Server.create, to keep from
711
+ # stomping on synchronous deploys that are still running. This
712
+ # means we're going to wait here if this instance is still being
713
+ # bootstrapped by "regular" means.
714
+ if !MU::MommaCat.lock(cloud_id+"-create", true)
715
+ MU.log "#{mu_name} is still in mid-creation, skipping", MU::NOTICE
716
+ MU::MommaCat.unlockAll
717
+ if !MU::MommaCat.locks.nil? and MU::MommaCat.locks.size > 0
718
+ puts "------------------------------"
719
+ puts "Open flock() locks:"
720
+ pp MU::MommaCat.locks
721
+ puts "------------------------------"
722
+ end
723
+ return
724
+ end
725
+ MU::MommaCat.unlock(cloud_id+"-create")
726
+
727
+ if !kitten.postBoot(cloud_id)
728
+ MU.log "#{mu_name} is already being groomed, skipping", MU::NOTICE
729
+ MU::MommaCat.unlockAll
730
+ if !MU::MommaCat.locks.nil? and MU::MommaCat.locks.size > 0
731
+ puts "------------------------------"
732
+ puts "Open flock() locks:"
733
+ pp MU::MommaCat.locks
734
+ puts "------------------------------"
735
+ end
736
+ return
737
+ end
738
+
739
+ # This is a shared lock with MU::Deploy.createResources, simulating the
740
+ # thread logic that tells MU::Cloud::AWS::Server.deploy to wait until
741
+ # its dependencies are ready. We don't, for example, want to start
742
+ # deploying if we rely on an RDS instance that isn't ready yet. We can
743
+ # release this immediately, once we successfully grab it.
744
+ MU::MommaCat.lock("#{kitten.cloudclass.name}_#{kitten.config["name"]}-dependencies")
745
+ MU::MommaCat.unlock("#{kitten.cloudclass.name}_#{kitten.config["name"]}-dependencies")
746
+
747
+ kitten.groom
748
+ rescue Exception => e
749
+ MU::MommaCat.unlockAll
750
+ if e.class.name != "MU::Cloud::AWS::Server::BootstrapTempFail" and !File.exists?(deploy_dir+"/.cleanup."+cloud_id) and !File.exists?(deploy_dir+"/.cleanup")
751
+ MU.log "Grooming FAILED for #{kitten.mu_name} (#{e.inspect})", MU::ERR, details: e.backtrace
752
+ # sendAdminMail("Grooming FAILED for #{kitten.mu_name} on #{MU.appname} \"#{MU.handle}\" (#{MU.deploy_id})",
753
+ # msg: e.inspect,
754
+ # kitten: kitten,
755
+ # data: e.backtrace,
756
+ # debug: true
757
+ # )
758
+ raise e if reraise_fail
759
+ else
760
+ MU.log "Grooming of #{kitten.mu_name} interrupted by cleanup or planned reboot"
761
+ end
762
+ return
763
+ end
764
+
765
+ if !@deployment['servers'].nil?
766
+ syncLitter(@deployment["servers"].keys, triggering_node: kitten)
767
+ end
768
+ MU::MommaCat.unlock(cloud_id+"-mommagroom")
769
+ if MU.myCloud == "AWS"
770
+ MU::Cloud::AWS.openFirewallForClients # XXX add the other clouds, or abstract
771
+ end
772
+ MU::MommaCat.getLitter(MU.deploy_id, use_cache: false)
773
+ MU::MommaCat.syncMonitoringConfig(false)
774
+ MU::MommaCat.createStandardTags(cloud_id, region: kitten.config["region"])
775
+ MU.log "Grooming complete for '#{name}' mu_name on \"#{MU.handle}\" (#{MU.deploy_id})"
776
+ FileUtils.touch("/opt/mu/var/deployments/#{MU.deploy_id}/#{name}_done.txt")
777
+ MU::MommaCat.unlockAll
778
+ if first_groom
779
+ sendAdminMail("Grooming complete for '#{name}' (#{mu_name}) on deploy \"#{MU.handle}\" (#{MU.deploy_id})", kitten: kitten)
780
+ end
781
+ return
782
+ end
783
+
784
+ # Return the parts and pieces of this deploy's node ssh key set. Generate
785
+ # or load if that hasn't been done already.
786
+ def SSHKey
787
+ return [@ssh_key_name, @ssh_private_key, @ssh_public_key] if !@ssh_key_name.nil?
788
+ if numKittens(types: ["Server", "ServerPool", "ContainerCluster"]) == 0
789
+ return []
790
+ end
791
+ @ssh_key_name="deploy-#{MU.deploy_id}"
792
+ ssh_dir = Etc.getpwnam(@mu_user).dir+"/.ssh"
793
+
794
+ if !File.directory?(ssh_dir) then
795
+ MU.log "Creating #{ssh_dir}", MU::DEBUG
796
+ Dir.mkdir(ssh_dir, 0700)
797
+ if Process.uid == 0 and @mu_user != "mu"
798
+ ssh_dir.chown(Etc.getpwnam(@mu_user).uid, Etc.getpwnam(@mu_user).gid)
799
+ end
800
+ end
801
+ if !File.exists?("#{ssh_dir}/#{@ssh_key_name}")
802
+ MU.log "Generating SSH key #{@ssh_key_name}"
803
+ %x{/usr/bin/ssh-keygen -N "" -f #{ssh_dir}/#{@ssh_key_name}}
804
+ end
805
+ @ssh_public_key = File.read("#{ssh_dir}/#{@ssh_key_name}.pub")
806
+ @ssh_public_key.chomp!
807
+ @ssh_private_key = File.read("#{ssh_dir}/#{@ssh_key_name}")
808
+ @ssh_private_key.chomp!
809
+
810
+ if numKittens(clouds: ["AWS"], types: ["Server", "ServerPool"]) > 0
811
+ MU::Cloud::AWS.createEc2SSHKey(@ssh_key_name, @ssh_public_key)
812
+ end
813
+
814
+ return [@ssh_key_name, @ssh_private_key, @ssh_public_key]
815
+ end
816
+
817
+ @lock_semaphore = Mutex.new
818
+ # Release all flock() locks held by the current thread.
819
+ def self.unlockAll
820
+ if !@locks.nil? and !@locks[Thread.current.object_id].nil?
821
+ # Work from a copy so we can iterate without worrying about contention
822
+ # in lock() or unlock(). We can't just wrap our iterator block in a
823
+ # semaphore here, because we're calling another method that uses the
824
+ # same semaphore.
825
+ lock_copy = nil
826
+ @lock_semaphore.synchronize {
827
+ delete_list = []
828
+ @locks[Thread.current.object_id].each_pair { |id, fh|
829
+ MU.log "Releasing lock on #{deploy_dir(MU.deploy_id)}/locks/#{id}.lock (thread #{Thread.current.object_id})", MU::DEBUG
830
+ begin
831
+ @locks[Thread.current.object_id][id].flock(File::LOCK_UN)
832
+ @locks[Thread.current.object_id][id].close
833
+ rescue IOError => e
834
+ MU.log "Got #{e.inspect} unlocking #{id} on #{Thread.current.object_id}", MU::WARN
835
+ end
836
+ delete_list << id
837
+ }
838
+ # We do this here because we can't mangle a Hash while we're iterating
839
+ # over it.
840
+ delete_list.each { |id|
841
+ @locks[Thread.current.object_id].delete(id)
842
+ }
843
+ if @locks[Thread.current.object_id].size == 0
844
+ @locks.delete(Thread.current.object_id)
845
+ end
846
+ }
847
+ end
848
+ end
849
+
850
+ # Create/hold a flock() lock.
851
+ # @param id [String]: The lock identifier to release.
852
+ # @param nonblock [Boolean]: Whether to block while waiting for the lock. In non-blocking mode, we simply return false if the lock is not available.
853
+ # return [false, nil]
854
+ def self.lock(id, nonblock = false, global = false)
855
+ raise MuError, "Can't pass a nil id to MU::MommaCat.lock" if id.nil?
856
+
857
+ if !global
858
+ lockdir = "#{deploy_dir(MU.deploy_id)}/locks"
859
+ else
860
+ lockdir = File.expand_path(MU.dataDir+"/locks")
861
+ end
862
+
863
+ if !Dir.exist?(lockdir)
864
+ MU.log "Creating #{lockdir}", MU::DEBUG
865
+ Dir.mkdir(lockdir, 0700)
866
+ end
867
+
868
+ @lock_semaphore.synchronize {
869
+ if @locks[Thread.current.object_id].nil?
870
+ @locks[Thread.current.object_id] = Hash.new
871
+ end
872
+
873
+ @locks[Thread.current.object_id][id] = File.open("#{lockdir}/#{id}.lock", File::CREAT|File::RDWR, 0600)
874
+ }
875
+ MU.log "Getting a lock on #{lockdir}/#{id}.lock (thread #{Thread.current.object_id})...", MU::DEBUG
876
+ begin
877
+ if nonblock
878
+ if !@locks[Thread.current.object_id][id].flock(File::LOCK_EX|File::LOCK_NB)
879
+ return false
880
+ end
881
+ else
882
+ @locks[Thread.current.object_id][id].flock(File::LOCK_EX)
883
+ end
884
+ rescue IOError => e
885
+ raise MU::BootstrapTempFail, "Interrupted waiting for lock on thread #{Thread.current.object_id}, probably just a node rebooting as part of a synchronous install"
886
+ end
887
+ MU.log "Lock on #{lockdir}/#{id}.lock on thread #{Thread.current.object_id} acquired", MU::DEBUG
888
+ return true
889
+ end
890
+
891
+ # Release a flock() lock.
892
+ # @param id [String]: The lock identifier to release.
893
+ def self.unlock(id, global = false)
894
+ raise MuError, "Can't pass a nil id to MU::MommaCat.unlock" if id.nil?
895
+ lockdir = nil
896
+ if !global
897
+ lockdir = "#{deploy_dir(MU.deploy_id)}/locks"
898
+ else
899
+ lockdir = File.expand_path(MU.dataDir+"/locks")
900
+ end
901
+ @lock_semaphore.synchronize {
902
+ return if @locks.nil? or @locks[Thread.current.object_id].nil? or @locks[Thread.current.object_id][id].nil?
903
+ }
904
+ MU.log "Releasing lock on #{lockdir}/#{id}.lock (thread #{Thread.current.object_id})", MU::DEBUG
905
+ begin
906
+ @locks[Thread.current.object_id][id].flock(File::LOCK_UN)
907
+ @locks[Thread.current.object_id][id].close
908
+ if !@locks[Thread.current.object_id].nil?
909
+ @locks[Thread.current.object_id].delete(id)
910
+ end
911
+ if @locks[Thread.current.object_id].size == 0
912
+ @locks.delete(Thread.current.object_id)
913
+ end
914
+ rescue IOError => e
915
+ MU.log "Got #{e.inspect} unlocking #{id} on #{Thread.current.object_id}", MU::WARN
916
+ end
917
+ end
918
+
919
+ # Remove a deployment's metadata.
920
+ # @param deploy_id [String]: The deployment identifier to remove.
921
+ def self.purge(deploy_id)
922
+ if deploy_id.nil? or deploy_id.empty?
923
+ raise MuError, "Got nil deploy_id in MU::MommaCat.purge"
924
+ end
925
+ # XXX archiving is better than annihilating
926
+ path = File.expand_path(MU.dataDir+"/deployments")
927
+ if Dir.exist?(path+"/"+deploy_id)
928
+ unlockAll
929
+ MU.log "Purging #{path}/#{deploy_id}" if File.exists?(path+"/"+deploy_id+"/deployment.json")
930
+
931
+ FileUtils.rm_rf(path+"/"+deploy_id, :secure => true)
932
+ end
933
+ if File.exists?(path+"/unique_ids")
934
+ File.open(path+"/unique_ids", File::CREAT|File::RDWR, 0600) { |f|
935
+ newlines = []
936
+ f.flock(File::LOCK_EX)
937
+ f.readlines.each { |line|
938
+ newlines << line if !line.match(/:#{deploy_id}$/)
939
+ }
940
+ f.rewind
941
+ f.truncate(0)
942
+ f.puts(newlines)
943
+ f.flush
944
+ f.flock(File::LOCK_UN)
945
+ }
946
+ end
947
+ end
948
+
949
+ # Remove the metadata of the currently loaded deployment.
950
+ def purge!
951
+ MU::MommaCat.purge(MU.deploy_id)
952
+ end
953
+
954
+ @cleanup_threads = []
955
+
956
+ # Iterate over all known deployments and look for instances that have been
957
+ # terminated, but not yet cleaned up, then clean them up.
958
+ def self.cleanTerminatedInstances
959
+ MU::MommaCat.lock("clean-terminated-instances", false, true)
960
+ MU.log "Checking for harvested instances in need of cleanup", MU::DEBUG
961
+ parent_thread_id = Thread.current.object_id
962
+ cleanup_threads = []
963
+ purged = 0
964
+ MU::MommaCat.listDeploys.each { |deploy_id|
965
+ next if File.exists?(deploy_dir(deploy_id)+"/.cleanup")
966
+ MU.log "Checking for dead wood in #{deploy_id}", MU::DEBUG
967
+ @cleanup_threads << Thread.new {
968
+ MU.dupGlobals(parent_thread_id)
969
+ # We can't use cached litter information because we will then try to delete the same node over and over again until we restart the service
970
+ deploy = MU::MommaCat.getLitter(deploy_id, set_context_to_me: true, use_cache: false)
971
+ purged_this_deploy = 0
972
+ if deploy.kittens.has_key?("servers")
973
+ deploy.kittens["servers"].each_pair { |nodeclass, servers|
974
+ deletia = []
975
+ servers.each_pair { |mu_name, server|
976
+ server.describe
977
+ if !server.cloud_id
978
+ MU.log "Checking for deletion of #{mu_name}, but unable to fetch its cloud_id", MU::WARN, details: server
979
+ elsif !server.active?
980
+ next if File.exists?(deploy_dir(deploy_id)+"/.cleanup-"+server.cloud_id)
981
+ deletia << mu_name
982
+ MU.log "Deleting #{server} (#{nodeclass}), formerly #{server.cloud_id}", MU::NOTICE
983
+ begin
984
+ server.destroy
985
+ deploy.sendAdminMail("Retired terminated node #{mu_name}", kitten: server)
986
+ rescue Exception => e
987
+ MU.log "Saw #{e.message} while retiring #{mu_name}", MU::ERR, details: e.backtrace
988
+ next
989
+ end
990
+ MU.log "Deletion of #{server} (#{nodeclass}), formerly #{server.cloud_id} complete", MU::NOTICE
991
+ purged = purged + 1
992
+ purged_this_deploy = purged_this_deploy + 1
993
+ end
994
+ }
995
+ if purged_this_deploy > 0
996
+ # XXX some kind of filter (obey sync_siblings on nodes' configs)
997
+ deploy.syncLitter(servers.keys)
998
+ end
999
+ }
1000
+ end
1001
+ MU.purgeGlobals
1002
+ }
1003
+ }
1004
+ @cleanup_threads.each { |t|
1005
+ t.join
1006
+ }
1007
+ @cleanup_threads = []
1008
+
1009
+ if purged > 0
1010
+ if MU.myCloud == "AWS"
1011
+ MU::Cloud::AWS.openFirewallForClients # XXX add the other clouds, or abstract
1012
+ end
1013
+ MU::MommaCat.syncMonitoringConfig
1014
+ end
1015
+ MU::MommaCat.unlock("clean-terminated-instances", true)
1016
+ end
1017
+
1018
+
1019
+ # Locate a resource that's either a member of another deployment, or of no
1020
+ # deployment at all, and return a {MU::Cloud} object for it.
1021
+ # @param cloud [String]: The Cloud provider to use.
1022
+ # @param type [String]: The resource type. Can be the full class name, symbolic name, or Basket of Kittens configuration shorthand for the resource type.
1023
+ # @param deploy_id [String]: The identifier of an outside deploy to search.
1024
+ # @param name [String]: The name of the resource as defined in its 'name' Basket of Kittens field, typically used in conjunction with deploy_id.
1025
+ # @param mu_name [String]: The fully-resolved and deployed name of the resource, typically used in conjunction with deploy_id.
1026
+ # @param cloud_id [String]: A cloud provider identifier for this resource.
1027
+ # @param region [String]: The cloud provider region
1028
+ # @param tag_key [String]: A cloud provider tag to help identify the resource, used in conjunction with tag_value.
1029
+ # @param tag_value [String]: A cloud provider tag to help identify the resource, used in conjunction with tag_key.
1030
+ # @param allow_multi [Boolean]: Permit an array of matching resources to be returned (if applicable) instead of just one.
1031
+ # @param dummy_ok [Boolean]: Permit return of a faked {MU::Cloud} object if we don't have enough information to identify a real live one.
1032
+ # @param flags [Hash]: Other cloud or resource type specific options to pass to that resource's find() method
1033
+ # @return [Array<MU::Cloud>]
1034
+ def self.findStray(cloud,
1035
+ type,
1036
+ deploy_id: nil,
1037
+ name: nil,
1038
+ mu_name: nil,
1039
+ cloud_id: nil,
1040
+ region: nil,
1041
+ tag_key: nil,
1042
+ tag_value: nil,
1043
+ allow_multi: false,
1044
+ calling_deploy: MU.mommacat,
1045
+ flags: {},
1046
+ dummy_ok: false
1047
+ )
1048
+ return nil if cloud == "CloudFormation" and !cloud_id.nil?
1049
+ begin
1050
+ deploy_id = deploy_id.to_s if deploy_id.class.to_s == "MU::Config::Tail"
1051
+ name = name.to_s if name.class.to_s == "MU::Config::Tail"
1052
+ cloud_id = cloud_id.to_s if !cloud_id.nil?
1053
+ mu_name = mu_name.to_s if mu_name.class.to_s == "MU::Config::Tail"
1054
+ tag_key = tag_key.to_s if tag_key.class.to_s == "MU::Config::Tail"
1055
+ tag_value = tag_value.to_s if tag_value.class.to_s == "MU::Config::Tail"
1056
+ shortclass, cfg_name, cfg_plural, classname, attrs = MU::Cloud.getResourceNames(type)
1057
+ resourceclass = MU::Cloud.loadCloudType(cloud, shortclass)
1058
+ cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
1059
+ if (tag_key and !tag_value) or (!tag_key and tag_value)
1060
+ raise MuError, "Can't call findStray with only one of tag_key and tag_value set, must be both or neither"
1061
+ end
1062
+ # Help ourselves by making more refined parameters out of mu_name, if
1063
+ # they weren't passed explicitly
1064
+ if mu_name
1065
+ if !tag_key and !tag_value
1066
+ # XXX "Name" is an AWS-ism, perhaps those plugins should do this bit?
1067
+ tag_key="Name"
1068
+ tag_value=mu_name
1069
+ end
1070
+ # We can extract a deploy_id from mu_name if we don't have one already
1071
+ if !deploy_id and mu_name
1072
+ deploy_id = mu_name.sub(/^(\w+-\w+-\d{10}-[A-Z]{2})-/, '\1')
1073
+ end
1074
+ end
1075
+ MU.log "Called findStray with cloud: #{cloud}, type: #{type}, deploy_id: #{deploy_id}, calling_deploy: #{calling_deploy.deploy_id if !calling_deploy.nil?}, name: #{name}, cloud_id: #{cloud_id}, tag_key: #{tag_key}, tag_value: #{tag_value}", MU::DEBUG, details: flags
1076
+
1077
+ # See if the thing we're looking for is a member of the deploy that's
1078
+ # asking after it.
1079
+ if !deploy_id.nil? and !calling_deploy.nil? and flags.empty? and
1080
+ calling_deploy.deploy_id == deploy_id and (!name.nil? or !mu_name.nil?)
1081
+ handle = calling_deploy.findLitterMate(type: type, name: name, mu_name: mu_name, cloud_id: cloud_id)
1082
+ return [handle] if !handle.nil?
1083
+ end
1084
+
1085
+ kittens = {}
1086
+ # Search our other deploys for matching resources
1087
+ if (deploy_id or name or mu_name or cloud_id)# and flags.empty?
1088
+ mu_descs = MU::MommaCat.getResourceMetadata(cfg_plural, name: name, deploy_id: deploy_id, mu_name: mu_name)
1089
+
1090
+ mu_descs.each_pair { |deploy_id, matches|
1091
+ next if matches.nil? or matches.size == 0
1092
+ momma = MU::MommaCat.getLitter(deploy_id)
1093
+ straykitten = nil
1094
+ # If we found exactly one match in this deploy, use its metadata to
1095
+ # guess at resource names we weren't told.
1096
+ if matches.size == 1 and name.nil? and mu_name.nil?
1097
+ if cloud_id.nil?
1098
+ straykitten = momma.findLitterMate(type: type, name: matches.first["name"], cloud_id: matches.first["cloud_id"])
1099
+ else
1100
+ straykitten = momma.findLitterMate(type: type, name: matches.first["name"], cloud_id: cloud_id)
1101
+ end
1102
+ # elsif !flags.nil? and !flags.empty? # XXX eh, maybe later
1103
+ # # see if we can narrow it down further with some flags
1104
+ # filtered = []
1105
+ # matches.each { |m|
1106
+ # f = resourceclass.find(cloud_id: m['mu_name'], flags: flags)
1107
+ # filtered << m if !f.nil? and f.size > 0
1108
+ # MU.log "RESULT FROM find(cloud_id: #{m['mu_name']}, flags: #{flags})", MU::WARN, details: f
1109
+ # }
1110
+ # if filtered.size == 1
1111
+ # straykitten = momma.findLitterMate(type: type, name: matches.first["name"], cloud_id: filtered.first['cloud_id'])
1112
+ # end
1113
+ else
1114
+ straykitten = momma.findLitterMate(type: type, name: name, mu_name: mu_name, cloud_id: cloud_id)
1115
+ end
1116
+
1117
+ next if straykitten.nil?
1118
+
1119
+ kittens[straykitten.cloud_id] = straykitten
1120
+ # Peace out if we found the exact resource we want
1121
+ if cloud_id and straykitten.cloud_id == cloud_id
1122
+ return [straykitten]
1123
+ elsif !cloud_id and mu_descs.size == 1 and matches.size == 1
1124
+ return [straykitten]
1125
+ end
1126
+ }
1127
+
1128
+ # if !mu_descs.nil? and mu_descs.size > 0 and !deploy_id.nil? and !deploy_id.empty? and !mu_descs.first.empty?
1129
+ # MU.log "I found descriptions that might match #{resourceclass.cfg_plural} name: #{name}, deploy_id: #{deploy_id}, mu_name: #{mu_name}, but couldn't isolate my target kitten", MU::WARN, details: caller
1130
+ # puts File.read(deploy_dir(deploy_id)+"/deployment.json")
1131
+ # end
1132
+
1133
+ # We can't refine any further by asking the cloud provider...
1134
+ if !cloud_id and !tag_key and !tag_value and kittens.size > 1
1135
+ if !allow_multi
1136
+ raise MuError, "Multiple matches in MU::MommaCat.findStray where none allowed from deploy_id: '#{deploy_id}', name: '#{name}', mu_name: '#{mu_name}' (#{caller[0]})"
1137
+ else
1138
+ return kittens.values
1139
+ end
1140
+ end
1141
+ end
1142
+
1143
+ matches = []
1144
+
1145
+ if cloud_id or (tag_key and tag_value) or !flags.empty?
1146
+ regions = []
1147
+ begin
1148
+ if region
1149
+ regions << region
1150
+ else
1151
+ regions = cloudclass.listRegions
1152
+ end
1153
+ rescue NoMethodError # Not all cloud providers have regions
1154
+ regions = [""]
1155
+ end
1156
+
1157
+ if cloud == "Google" and ["vpcs", "firewall_rules"].include?(cfg_plural)
1158
+ regions = [nil]
1159
+ end
1160
+
1161
+ cloud_descs = {}
1162
+ regions.each { |r|
1163
+ cloud_descs[r] = resourceclass.find(cloud_id: cloud_id, region: r, tag_key: tag_key, tag_value: tag_value, flags: flags)
1164
+ # Stop if you found the thing
1165
+ if cloud_id and cloud_descs[r] and !cloud_descs[r].empty?
1166
+ break
1167
+ end
1168
+ }
1169
+ regions.each { |r|
1170
+ next if cloud_descs[r].nil?
1171
+ cloud_descs[r].each_pair { |kitten_cloud_id, descriptor|
1172
+ # We already have a MU::Cloud object for this guy, use it
1173
+ if kittens.has_key?(kitten_cloud_id)
1174
+ matches << kittens[kitten_cloud_id]
1175
+ elsif kittens.size == 0
1176
+ if !dummy_ok
1177
+ next
1178
+ end
1179
+ # If we don't have a MU::Cloud object, manufacture a dummy one.
1180
+ # Give it a fake name if we have to and have decided that's ok.
1181
+ if (name.nil? or name.empty?)
1182
+ if !dummy_ok
1183
+ MU.log "Found cloud provider data for #{cloud} #{type} #{kitten_cloud_id}, but without a name I can't manufacture a proper #{type} object to return", MU::DEBUG, details: caller
1184
+ next
1185
+ else
1186
+ if !mu_name.nil?
1187
+ name = mu_name
1188
+ elsif !tag_value.nil?
1189
+ name = tag_value
1190
+ else
1191
+ name = kitten_cloud_id
1192
+ end
1193
+ end
1194
+ end
1195
+ cfg = {"name" => name, "cloud" => cloud, "region" => r}
1196
+ # If we can at least find the config from the deploy this will
1197
+ # belong with, use that, even if it's an ungroomed resource.
1198
+ if !calling_deploy.nil? and
1199
+ !calling_deploy.original_config.nil? and
1200
+ !calling_deploy.original_config[type+"s"].nil?
1201
+ calling_deploy.original_config[type+"s"].each { |s|
1202
+ if s["name"] == name
1203
+ cfg = s.dup
1204
+ break
1205
+ end
1206
+ }
1207
+
1208
+ matches << resourceclass.new(mommacat: calling_deploy, kitten_cfg: cfg, cloud_id: kitten_cloud_id)
1209
+ else
1210
+ matches << resourceclass.new(mu_name: name, kitten_cfg: cfg, cloud_id: kitten_cloud_id.to_s)
1211
+ end
1212
+ end
1213
+ }
1214
+ }
1215
+ end
1216
+ rescue Exception => e
1217
+ MU.log e.inspect, MU::ERR, details: e.backtrace
1218
+ end
1219
+ matches
1220
+ end
1221
+
1222
+ # Return the resource object of another member of this deployment
1223
+ # @param type [String,Symbol]: The type of resource
1224
+ # @param name [String]: The name of the resource as defined in its 'name' Basket of Kittens field
1225
+ # @param mu_name [String]: The fully-resolved and deployed name of the resource
1226
+ # @param cloud_id [String]: The cloud provider's unique identifier for this resource
1227
+ # @param created_only [Boolean]: Only return the littermate if its cloud_id method returns a value
1228
+ # @param return_all [Boolean]: Return a Hash of matching objects indexed by their mu_name, instead of a single match. Only valid for resource types where has_multiples is true.
1229
+ # @return [MU::Cloud]
1230
+ def findLitterMate(type: nil, name: nil, mu_name: nil, cloud_id: nil, created_only: false, return_all: false)
1231
+ shortclass, cfg_name, cfg_plural, classname, attrs = MU::Cloud.getResourceNames(type)
1232
+ type = cfg_plural
1233
+ has_multiples = attrs[:has_multiples]
1234
+
1235
+ @kitten_semaphore.synchronize {
1236
+ if !@kittens.has_key?(type)
1237
+ return nil
1238
+ end
1239
+ MU.log "findLitterMate(type: #{type}, name: #{name}, mu_name: #{mu_name}, cloud_id: #{cloud_id}, created_only: #{created_only}). Caller: #{caller[2]}", MU::DEBUG, details: @kittens.keys.map { |k| k.to_s+": "+@kittens[k].keys.join(", ") }
1240
+ @kittens[type].each { |sib_class, data|
1241
+ next if !name.nil? and name != sib_class
1242
+ if has_multiples
1243
+ if !name.nil?
1244
+ if return_all
1245
+ return data.dup
1246
+ end
1247
+ if data.size == 1 and (cloud_id.nil? or data.values.first.cloud_id == cloud_id)
1248
+ obj = data.values.first
1249
+ return obj
1250
+ elsif mu_name.nil? and cloud_id.nil?
1251
+ obj = data.values.first
1252
+ MU.log "#{@deploy_id}: Found multiple matches in findLitterMate based on #{type}: #{name}, and not enough info to narrow down further. Returning an arbitrary result. Caller: #{caller[2]}", MU::WARN, details: data.keys
1253
+ return data.values.first
1254
+ end
1255
+ end
1256
+ data.each_pair { |sib_mu_name, obj|
1257
+ if (!mu_name.nil? and mu_name == sib_mu_name) or
1258
+ (!cloud_id.nil? and cloud_id == obj.cloud_id)
1259
+ if !created_only or !obj.cloud_id.nil?
1260
+ if return_all
1261
+ return data.dup
1262
+ else
1263
+ return obj
1264
+ end
1265
+ end
1266
+ end
1267
+ }
1268
+ else
1269
+ if (name.nil? or sib_class == name) and
1270
+ (cloud_id.nil? or cloud_id == data.cloud_id)
1271
+ return data if !created_only or !data.cloud_id.nil?
1272
+ end
1273
+ end
1274
+ }
1275
+ }
1276
+ return nil
1277
+ end
1278
+
1279
+ # Add or remove a resource's metadata to this deployment's structure and
1280
+ # flush it to disk.
1281
+ # @param type [String]: The type of resource (e.g. *server*, *database*).
1282
+ # @param key [String]: The name field of this resource.
1283
+ # @param data [Hash]: The resource's metadata.
1284
+ # @param remove [Boolean]: Remove this resource from the deploy structure, instead of adding it.
1285
+ # @return [void]
1286
+ def notify(type, key, data, mu_name: nil, remove: false, triggering_node: nil, delayed_save: false)
1287
+ return if @no_artifacts
1288
+ MU::MommaCat.lock("deployment-notification")
1289
+ loadDeploy(true) # make sure we're saving the latest and greatest
1290
+ have_deploy = true
1291
+ shortclass, cfg_name, cfg_plural, classname, attrs = MU::Cloud.getResourceNames(type)
1292
+ type = cfg_plural
1293
+ has_multiples = attrs[:has_multiples]
1294
+
1295
+ if mu_name.nil?
1296
+ if !data.nil? and !data["mu_name"].nil?
1297
+ mu_name = data["mu_name"]
1298
+ elsif !triggering_node.nil? and !triggering_node.mu_name.nil?
1299
+ mu_name = triggering_node.mu_name
1300
+ end
1301
+ if mu_name.nil? and has_multiples
1302
+ MU.log "MU::MommaCat.notify called to modify deployment struct for a type (#{type}) with :has_multiples, but no mu_name available to look under #{key}. Call was #{caller[0]}", MU::WARN, details: data
1303
+ MU::MommaCat.unlock("deployment-notification")
1304
+ return
1305
+ end
1306
+ end
1307
+
1308
+ if !remove
1309
+ if data.nil?
1310
+ MU.log "MU::MommaCat.notify called to modify deployment struct, but no data provided", MU::WARN
1311
+ MU::MommaCat.unlock("deployment-notification")
1312
+ return
1313
+ end
1314
+ @deployment[type] = {} if @deployment[type].nil?
1315
+ if has_multiples
1316
+ @deployment[type][key] = {} if @deployment[type][key].nil?
1317
+ # fix has_multiples classes that weren't tiered correctly
1318
+ if @deployment[type][key].is_a?(Hash) and @deployment[type][key].has_key?("mu_name")
1319
+ olddata = @deployment[type][key].dup
1320
+ @deployment[type][key][olddata["mu_name"]] = olddata
1321
+ end
1322
+ @deployment[type][key][mu_name] = data
1323
+ MU.log "Adding to @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: data
1324
+ else
1325
+ @deployment[type][key] = data
1326
+ MU.log "Adding to @deployment[#{type}][#{key}]", MU::DEBUG, details: data
1327
+ end
1328
+ save!(key) if !delayed_save
1329
+ else
1330
+ have_deploy = true
1331
+ if @deployment[type].nil? or @deployment[type][key].nil?
1332
+
1333
+ if has_multiples
1334
+ MU.log "MU::MommaCat.notify called to remove #{type} #{key} #{mu_name} deployment struct, but no such data exist", MU::DEBUG
1335
+ else
1336
+ MU.log "MU::MommaCat.notify called to remove #{type} #{key} deployment struct, but no such data exist", MU::DEBUG
1337
+ end
1338
+ MU::MommaCat.unlock("deployment-notification")
1339
+
1340
+ return
1341
+ end
1342
+
1343
+ if have_deploy
1344
+ if has_multiples
1345
+ MU.log "Removing @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: @deployment[type][key][mu_name]
1346
+ @deployment[type][key].delete(mu_name)
1347
+ if @deployment[type][key].size == 0
1348
+ @deployment[type].delete(key)
1349
+ end
1350
+ else
1351
+ MU.log "Removing @deployment[#{type}][#{key}]", MU::DEBUG, details: @deployment[type][key]
1352
+ @deployment[type].delete(key)
1353
+ end
1354
+ if @deployment[type].size == 0
1355
+ @deployment.delete(type)
1356
+ end
1357
+ end
1358
+ save! if !delayed_save
1359
+
1360
+ end
1361
+ MU::MommaCat.unlock("deployment-notification")
1362
+ end
1363
+
1364
+ # Tag a resource. Defaults to applying our MU deployment identifier, if no
1365
+ # arguments other than the resource identifier are given.
1366
+ # XXX this belongs in the cloud layer(s)
1367
+ #
1368
+ # @param resource [String]: The cloud provider identifier of the resource to tag
1369
+ # @param tag_name [String]: The name of the tag to create
1370
+ # @param tag_value [String]: The value of the tag
1371
+ # @param region [String]: The cloud provider region
1372
+ # @return [void]
1373
+ def self.createTag(resource = nil,
1374
+ tag_name="MU-ID",
1375
+ tag_value=MU.deploy_id,
1376
+ region: MU.curRegion)
1377
+ attempts = 0
1378
+
1379
+ if !MU::Cloud::CloudFormation.emitCloudFormation
1380
+ begin
1381
+ MU::Cloud::AWS.ec2(region).create_tags(
1382
+ resources: [resource],
1383
+ tags: [
1384
+ {
1385
+ key: tag_name,
1386
+ value: tag_value
1387
+ }
1388
+ ]
1389
+ )
1390
+ rescue Aws::EC2::Errors::ServiceError => e
1391
+ MU.log "Got #{e.inspect} tagging #{resource} with #{tag_name}=#{tag_value}", MU::WARN if attempts > 1
1392
+ if attempts < 5
1393
+ attempts = attempts + 1
1394
+ sleep 15
1395
+ retry
1396
+ else
1397
+ raise e
1398
+ end
1399
+ end
1400
+ MU.log "Created tag #{tag_name} with value #{tag_value} for resource #{resource}", MU::DEBUG
1401
+ else
1402
+ return {
1403
+ "Key" => tag_name,
1404
+ "Value" => tag_value
1405
+ }
1406
+ end
1407
+ end
1408
+
1409
+ # XXX this belongs in MU::Cloud::AWS
1410
+ # Tag a resource with all of our standard identifying tags.
1411
+ #
1412
+ # @param resource [String]: The cloud provider identifier of the resource to tag
1413
+ # @param region [String]: The cloud provider region
1414
+ # @return [void]
1415
+ def self.createStandardTags(resource = nil, region: MU.curRegion)
1416
+ tags = []
1417
+ listStandardTags.each_pair { |name, value|
1418
+ if !value.nil?
1419
+ tags << {key: name, value: value}
1420
+ end
1421
+ }
1422
+ if MU::Cloud::CloudFormation.emitCloudFormation
1423
+ return tags
1424
+ end
1425
+
1426
+ attempts = 0
1427
+ begin
1428
+ MU::Cloud::AWS.ec2(region).create_tags(
1429
+ resources: [resource],
1430
+ tags: tags
1431
+ )
1432
+ rescue Aws::EC2::Errors::ServiceError => e
1433
+ MU.log "Got #{e.inspect} tagging #{resource} in #{region}, will retry", MU::WARN, details: caller.concat(tags) if attempts > 1
1434
+ if attempts < 5
1435
+ attempts = attempts + 1
1436
+ sleep 15
1437
+ retry
1438
+ else
1439
+ raise e
1440
+ end
1441
+ end
1442
+ MU.log "Created standard tags for resource #{resource}", MU::DEBUG, details: caller
1443
+ end
1444
+
1445
+ # List the name/value pairs for our mandatory standard set of resource tags, which
1446
+ # should be applied to all taggable cloud provider resources.
1447
+ # @return [Hash<String,String>]
1448
+ def self.listStandardTags
1449
+ return {
1450
+ "MU-ID" => MU.deploy_id,
1451
+ "MU-APP" => MU.appname,
1452
+ "MU-ENV" => MU.environment,
1453
+ "MU-MASTER-IP" => MU.mu_public_ip
1454
+ }
1455
+ end
1456
+
1457
+ # List the name/value pairs of our optional set of resource tags which
1458
+ # should be applied to all taggable cloud provider resources.
1459
+ # @return [Hash<String,String>]
1460
+ def self.listOptionalTags
1461
+ return {
1462
+ "MU-HANDLE" => MU.handle,
1463
+ "MU-MASTER-NAME" => Socket.gethostname,
1464
+ "MU-OWNER" => MU.mu_user
1465
+ }
1466
+ end
1467
+
1468
+ # Clean a node's entries out of ~/.ssh/config
1469
+ # @param node [String]: The node's name
1470
+ # @return [void]
1471
+ def self.removeHostFromSSHConfig(node)
1472
+ sshdir = "#{@myhome}/.ssh"
1473
+ sshconf = "#{sshdir}/config"
1474
+
1475
+ if File.exists?(sshconf) and File.open(sshconf).read.match(/ #{node} /)
1476
+ MU.log "Expunging old #{node} entry from #{sshconf}", MU::DEBUG
1477
+ if !@noop
1478
+ File.open(sshconf, File::CREAT|File::RDWR, 0600) { |f|
1479
+ f.flock(File::LOCK_EX)
1480
+ newlines = Array.new
1481
+ delete_block = false
1482
+ f.readlines.each { |line|
1483
+ if line.match(/^Host #{node}(\s|$)/)
1484
+ delete_block = true
1485
+ elsif line.match(/^Host /)
1486
+ delete_block = false
1487
+ end
1488
+ newlines << line if !delete_block
1489
+ }
1490
+ f.rewind
1491
+ f.truncate(0)
1492
+ f.puts(newlines)
1493
+ f.flush
1494
+ f.flock(File::LOCK_UN)
1495
+ }
1496
+ end
1497
+ end
1498
+
1499
+ end
1500
+
1501
+ # Make sure the given node has proper DNS entries, /etc/hosts entries,
1502
+ # SSH config entries, etc.
1503
+ # @param server [MU::Cloud::Server]: The {MU::Cloud::Server} we'll be setting up.
1504
+ # @param sync_wait [Boolean]: Whether to wait for DNS to fully synchronize before returning.
1505
+ def self.nameKitten(server, sync_wait: false)
1506
+ node, config, deploydata = server.describe
1507
+ nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_addr, ssh_user, ssh_key_name = server.getSSHConfig
1508
+
1509
+ mu_zone = nil
1510
+ # XXX GCP!
1511
+ if MU::Cloud::AWS.hosted and !MU::Cloud::AWS.isGovCloud?
1512
+ zones = MU::Cloud::DNSZone.find(cloud_id: "platform-mu")
1513
+ mu_zone = zones.values.first if !zones.nil?
1514
+ end
1515
+ if !mu_zone.nil?
1516
+ MU::Cloud::DNSZone.genericMuDNSEntry(name: node, target: server.canonicalIP, cloudclass: MU::Cloud::Server, sync_wait: sync_wait)
1517
+ else
1518
+ MU::MommaCat.addInstanceToEtcHosts(server.canonicalIP, node)
1519
+ end
1520
+
1521
+ ## TO DO: Do DNS registration of "real" records as the last stage after the groomer completes
1522
+ if config && config['dns_records'] && !config['dns_records'].empty?
1523
+ dnscfg = config['dns_records'].dup
1524
+ dnscfg.each { |dnsrec|
1525
+ if !dnsrec.has_key?('name')
1526
+ dnsrec['name'] = node.downcase
1527
+ dnsrec['name'] = "#{dnsrec['name']}.#{MU.environment.downcase}" if dnsrec["append_environment_name"] && !dnsrec['name'].match(/\.#{MU.environment.downcase}$/)
1528
+ end
1529
+
1530
+ if !dnsrec.has_key?("target")
1531
+ # Default to register public endpoint
1532
+ public = true
1533
+
1534
+ if dnsrec.has_key?("target_type")
1535
+ # See if we have a preference for pubic/private endpoint
1536
+ public = dnsrec["target_type"] == "private" ? false : true
1537
+ end
1538
+
1539
+ dnsrec["target"] =
1540
+ if dnsrec["type"] == "CNAME"
1541
+ if public
1542
+ # Make sure we have a public canonical name to register. Use the private one if we don't
1543
+ server.cloud_desc.public_dns_name.empty? ? server.cloud_desc.private_dns_name : server.cloud_desc.public_dns_name
1544
+ else
1545
+ # If we specifically requested to register the private canonical name lets use that
1546
+ server.cloud_desc.private_dns_name
1547
+ end
1548
+ elsif dnsrec["type"] == "A"
1549
+ if public
1550
+ # Make sure we have a public IP address to register. Use the private one if we don't
1551
+ server.cloud_desc.public_ip_address ? server.cloud_desc.public_ip_address : server.cloud_desc.private_ip_address
1552
+ else
1553
+ # If we specifically requested to register the private IP lets use that
1554
+ server.cloud_desc.private_ip_address
1555
+ end
1556
+ end
1557
+ end
1558
+ }
1559
+ if !MU::Cloud::AWS.isGovCloud?
1560
+ MU::Cloud::DNSZone.createRecordsFromConfig(dnscfg)
1561
+ end
1562
+ end
1563
+
1564
+ MU::MommaCat.removeHostFromSSHConfig(node)
1565
+ # XXX add names paramater with useful stuff
1566
+ MU::MommaCat.addHostToSSHConfig(
1567
+ server,
1568
+ ssh_owner: server.deploy.mu_user,
1569
+ ssh_dir: Etc.getpwnam(server.deploy.mu_user).dir+"/.ssh"
1570
+ )
1571
+ end
1572
+
1573
+ @ssh_semaphore = Mutex.new
1574
+ # Insert a definition for a node into our SSH config.
1575
+ # @param server [MU::Cloud::Server]: The name of the node.
1576
+ # @param names [Array<String>]: Other names that we'd like this host to be known by for SSH purposes
1577
+ # @param ssh_dir [String]: The configuration directory of the SSH config to emit.
1578
+ # @param ssh_conf [String]: A specific SSH configuration file to write entries into.
1579
+ # @param ssh_owner [String]: The preferred owner of the SSH configuration files.
1580
+ # @param timeout [Integer]: An alternate timeout value for connections to this server.
1581
+ # @return [void]
1582
+ def self.addHostToSSHConfig(server,
1583
+ ssh_dir: "#{@myhome}/.ssh",
1584
+ ssh_conf: "#{@myhome}/.ssh/config",
1585
+ ssh_owner: Etc.getpwuid(Process.uid).name,
1586
+ names: [],
1587
+ timeout: 0
1588
+ )
1589
+ if server.nil?
1590
+ MU.log "Called addHostToSSHConfig without a MU::Cloud::Server object", MU::ERR, details: caller
1591
+ return nil
1592
+ end
1593
+ begin
1594
+ nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = server.getSSHConfig
1595
+ rescue MU::MuError => e
1596
+ return
1597
+ end
1598
+
1599
+ if ssh_user.nil? or ssh_user.empty?
1600
+ MU.log "Failed to extract ssh_user for #{server.mu_name} addHostToSSHConfig", MU::ERR
1601
+ return
1602
+ end
1603
+ if canonical_ip.nil? or canonical_ip.empty?
1604
+ MU.log "Failed to extract canonical_ip for #{server.mu_name} addHostToSSHConfig", MU::ERR
1605
+ return
1606
+ end
1607
+ if ssh_key_name.nil? or ssh_key_name.empty?
1608
+ MU.log "Failed to extract canonical_ip for #{ssh_key_name.mu_name} in addHostToSSHConfig", MU::ERR
1609
+ return
1610
+ end
1611
+
1612
+ @ssh_semaphore.synchronize {
1613
+
1614
+ if File.exists?(ssh_conf)
1615
+ File.readlines(ssh_conf).each { |line|
1616
+ if line.match(/^Host #{server.mu_name} /)
1617
+ MU.log("Attempt to add duplicate #{ssh_conf} entry for #{server.mu_name}", MU::WARN)
1618
+ return
1619
+ end
1620
+ }
1621
+ end
1622
+
1623
+ File.open(ssh_conf, 'a', 0600) { |ssh_config|
1624
+ ssh_config.flock(File::LOCK_EX)
1625
+ host_str = "Host #{server.mu_name} #{server.canonicalIP}"
1626
+ if !names.nil? and names.size > 0
1627
+ host_str = host_str+" "+names.join(" ")
1628
+ end
1629
+ ssh_config.puts host_str
1630
+ ssh_config.puts " Hostname #{server.canonicalIP}"
1631
+ if !nat_ssh_host.nil? and server.canonicalIP != nat_ssh_host
1632
+ ssh_config.puts " ProxyCommand ssh -W %h:%p #{nat_ssh_user}@#{nat_ssh_host}"
1633
+ end
1634
+ if timeout > 0
1635
+ ssh_config.puts " ConnectTimeout #{timeout}"
1636
+ end
1637
+
1638
+ ssh_config.puts " User #{ssh_user}"
1639
+ # XXX I'd rather add the host key to known_hosts, but Net::SSH is a little dumb
1640
+ ssh_config.puts " StrictHostKeyChecking no"
1641
+ ssh_config.puts " ServerAliveInterval 60"
1642
+
1643
+ ssh_config.puts " IdentityFile #{ssh_dir}/#{ssh_key_name}"
1644
+ if !File.exist?("#{ssh_dir}/#{ssh_key_name}")
1645
+ MU.log "#{server.mu_name} - ssh private key #{ssh_dir}/#{ssh_key_name} does not exist", MU::WARN
1646
+ end
1647
+
1648
+ ssh_config.flock(File::LOCK_UN)
1649
+ ssh_config.chown(Etc.getpwnam(ssh_owner).uid, Etc.getpwnam(ssh_owner).gid)
1650
+ }
1651
+ MU.log "Wrote #{server.mu_name} ssh key to #{ssh_dir}/config", MU::DEBUG
1652
+ return "#{ssh_dir}/#{ssh_key_name}"
1653
+ }
1654
+ end
1655
+
1656
+ # Clean a node's entries out of /etc/hosts
1657
+ # @param node [String]: The node's name
1658
+ # @return [void]
1659
+ def self.removeInstanceFromEtcHosts(node)
1660
+ return if MU.mu_user != "mu"
1661
+ hostsfile = "/etc/hosts"
1662
+ FileUtils.copy(hostsfile, "#{hostsfile}.bak-#{MU.deploy_id}")
1663
+ File.open(hostsfile, File::CREAT|File::RDWR, 0644) { |f|
1664
+ f.flock(File::LOCK_EX)
1665
+ newlines = Array.new
1666
+ f.readlines.each { |line|
1667
+ newlines << line if !line.match(/ #{node}(\s|$)/)
1668
+ }
1669
+ f.rewind
1670
+ f.truncate(0)
1671
+ f.puts(newlines)
1672
+ f.flush
1673
+
1674
+ f.flock(File::LOCK_UN)
1675
+ }
1676
+ end
1677
+
1678
+
1679
+ # Insert node names associated with a new instance into /etc/hosts so we
1680
+ # can treat them as if they were real DNS entries. Especially helpful when
1681
+ # Chef/Ohai mistake the proper hostname, e.g. when bootstrapping Windows.
1682
+ # @param public_ip [String]: The node's IP address
1683
+ # @param chef_name [String]: The node's Chef node name
1684
+ # @param system_name [String]: The node's local system name
1685
+ # @return [void]
1686
+ def self.addInstanceToEtcHosts(public_ip, chef_name = nil, system_name = nil)
1687
+ return if !["mu", "root"].include?(MU.mu_user)
1688
+
1689
+ # XXX cover ipv6 case
1690
+ if public_ip.nil? or !public_ip.match(/^\d+\.\d+\.\d+\.\d+$/) or (chef_name.nil? and system_name.nil?)
1691
+ raise MuError, "addInstanceToEtcHosts requires public_ip and one or both of chef_name and system_name!"
1692
+ end
1693
+ if chef_name == "localhost" or system_name == "localhost"
1694
+ raise MuError, "Can't set localhost as a name in addInstanceToEtcHosts"
1695
+ end
1696
+ File.readlines("/etc/hosts").each { |line|
1697
+ if line.match(/^#{public_ip} /) or (chef_name != nil and line.match(/ #{chef_name}(\s|$)/)) or (system_name != nil and line.match(/ #{system_name}(\s|$)/))
1698
+ MU.log "Ignoring attempt to add duplicate /etc/hosts entry: #{public_ip} #{chef_name} #{system_name}", MU::DEBUG
1699
+ return
1700
+ end
1701
+ }
1702
+ File.open("/etc/hosts", 'a') { |etc_hosts|
1703
+ etc_hosts.flock(File::LOCK_EX)
1704
+ etc_hosts.puts("#{public_ip} #{chef_name} #{system_name}")
1705
+ etc_hosts.flock(File::LOCK_UN)
1706
+ }
1707
+ MU.log("Added to /etc/hosts: #{public_ip} #{chef_name} #{system_name}")
1708
+ end
1709
+
1710
+ # Send a notification to a deployment's administrators.
1711
+ # @param subject [String]: The subject line of the message.
1712
+ # @param msg [String]: The message body.
1713
+ # @param data [Array]: Supplemental data to add to the message body.
1714
+ # @param debug [Boolean]: If set, will include the full deployment structure and original {MU::Config}-parsed configuration.
1715
+ # @return [void]
1716
+ def sendAdminMail(subject, msg: msg = "", kitten: nil, data: nil, debug: debug = false)
1717
+ require 'net/smtp'
1718
+ if @deployment.nil?
1719
+ MU.log "Can't send admin mail without a loaded deployment", MU::ERR
1720
+ return
1721
+ end
1722
+ to = Array.new
1723
+ if !@original_config.nil?
1724
+ @original_config['admins'].each { |admin|
1725
+ to << "#{admin['name']} <#{admin['email']}>"
1726
+ }
1727
+ end
1728
+ message = <<MESSAGE_END
1729
+ From: #{MU.handle} <root@localhost>
1730
+ To: #{to.join(",")}
1731
+ Subject: #{subject}
1732
+
1733
+ #{msg}
1734
+ MESSAGE_END
1735
+ if !kitten.nil? and kitten.kind_of?(MU::Cloud)
1736
+ message = message + "\n\n**** #{kitten}:\n"
1737
+ if !kitten.report.nil?
1738
+ kitten.report.each { |line|
1739
+ message = message + line
1740
+ }
1741
+ end
1742
+ end
1743
+ if !data.nil?
1744
+ message = message + "\n\n" + PP.pp(data, "")
1745
+ end
1746
+ if debug
1747
+ message = message + "\n\n**** Stack configuration:\n" + PP.pp(@original_config, "")
1748
+ message = message + "\n\n**** Deployment structure:\n" + PP.pp(@deployment, "")
1749
+ end
1750
+ begin
1751
+ Net::SMTP.start('localhost') do |smtp|
1752
+ smtp.send_message message, "root@localhost", to
1753
+ end
1754
+ rescue Net::SMTPFatalError, Errno::ECONNREFUSED => e
1755
+ MU.log e.inspect, MU::WARN
1756
+ end
1757
+ end
1758
+
1759
+ # Manufactures a human-readable deployment name from the random
1760
+ # two-character seed in MU-ID. Cat-themed when possible.
1761
+ # @param seed [String]: A two-character seed from which we'll generate a name.
1762
+ # @return [String]: Two words
1763
+ def self.generateHandle(seed)
1764
+ word_one=word_two=nil
1765
+
1766
+ # Unless we've got two letters that don't have corresponding cat-themed
1767
+ # words, we'll insist that our generated handle have at least one cat
1768
+ # element to it.
1769
+ require_cat_words = true
1770
+ if @catwords.select { |word| word.match(/^#{seed[0]}/i) }.size == 0 and
1771
+ @catwords.select { |word| word.match(/^#{seed[1]}/i) }.size == 0
1772
+ require_cat_words = false
1773
+ MU.log "Got an annoying pair of letters #{seed}, not forcing cat-theming", MU::DEBUG
1774
+ end
1775
+ allnouns = @catnouns + @jaegernouns
1776
+ alladjs = @catadjs + @jaegeradjs
1777
+
1778
+ tries = 0
1779
+ begin
1780
+ # Try to avoid picking something "nouny" for the first word
1781
+ source = @catadjs + @catmixed + @jaegeradjs + @jaegermixed
1782
+ first_ltr = source.select { |word| word.match(/^#{seed[0]}/i) }
1783
+ if !first_ltr or first_ltr.size == 0
1784
+ first_ltr = @words.select { |word| word.match(/^#{seed[0]}/i) }
1785
+ end
1786
+ word_one = first_ltr.shuffle.first
1787
+
1788
+ # If we got a paired set that happen to match our letters, go with it
1789
+ if !word_one.nil? and word_one.match(/-#{seed[1]}/i)
1790
+ word_one, word_two = word_one.split(/-/)
1791
+ else
1792
+ source = @words
1793
+ if @catwords.include?(word_one)
1794
+ source = @jaegerwords
1795
+ elsif require_cat_words
1796
+ source = @catwords
1797
+ end
1798
+ second_ltr = source.select { |word| word.match(/^#{seed[1]}/i) and !word.match(/-/i) }
1799
+ word_two = second_ltr.shuffle.first
1800
+ end
1801
+ tries = tries + 1
1802
+ end while tries < 50 and (word_one.nil? or word_two.nil? or word_one.match(/-/) or word_one == word_two or (allnouns.include?(word_one) and allnouns.include?(word_two)) or (alladjs.include?(word_one) and alladjs.include?(word_two)) or (require_cat_words and !@catwords.include?(word_one) and !@catwords.include?(word_two)))
1803
+
1804
+ if tries >= 50 and (word_one.nil? or word_two.nil?)
1805
+ MU.log "I failed to generated a valid handle, faking it", MU::ERR
1806
+ return "#{seed[0].capitalize} #{seed[1].capitalize}"
1807
+ end
1808
+
1809
+ return "#{word_one.capitalize} #{word_two.capitalize}"
1810
+ end
1811
+
1812
+ # Ensure that the Nagios configuration local to the MU master has been
1813
+ # updated, and make sure Nagios has all of the ssh keys it needs to tunnel
1814
+ # to client nodes.
1815
+ # @return [void]
1816
+ def self.syncMonitoringConfig(blocking = true)
1817
+ return if Etc.getpwuid(Process.uid).name != "root" or (MU.mu_user != "mu" and MU.mu_user != "root")
1818
+ parent_thread_id = Thread.current.object_id
1819
+ nagios_threads = []
1820
+ nagios_threads << Thread.new {
1821
+ MU.dupGlobals(parent_thread_id)
1822
+ realhome = Etc.getpwnam("nagios").dir
1823
+ [@nagios_home, "#{@nagios_home}/.ssh"].each { |dir|
1824
+ Dir.mkdir(dir, 0711) if !Dir.exists?(dir)
1825
+ File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, dir)
1826
+ }
1827
+ if realhome != @nagios_home and Dir.exists?(realhome) and !File.symlink?("#{realhome}/.ssh")
1828
+ File.rename("#{realhome}/.ssh", "#{realhome}/.ssh.#{$$}") if Dir.exists?("#{realhome}/.ssh")
1829
+ File.symlink("#{@nagios_home}/.ssh", Etc.getpwnam("nagios").dir+"/.ssh")
1830
+ end
1831
+ MU.log "Updating #{@nagios_home}/.ssh/config..."
1832
+ ssh_lock = File.new("#{@nagios_home}/.ssh/config.mu.lock", File::CREAT|File::TRUNC|File::RDWR, 0600)
1833
+ ssh_lock.flock(File::LOCK_EX)
1834
+ ssh_conf = File.new("#{@nagios_home}/.ssh/config.tmp", File::CREAT|File::TRUNC|File::RDWR, 0600)
1835
+ ssh_conf.puts "Host MU-MASTER localhost"
1836
+ ssh_conf.puts " Hostname localhost"
1837
+ ssh_conf.puts " User root"
1838
+ ssh_conf.puts " IdentityFile #{@nagios_home}/.ssh/id_rsa"
1839
+ ssh_conf.puts " StrictHostKeyChecking no"
1840
+ ssh_conf.close
1841
+ FileUtils.cp("#{@myhome}/.ssh/id_rsa", "#{@nagios_home}/.ssh/id_rsa")
1842
+ File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, "#{@nagios_home}/.ssh/id_rsa")
1843
+ threads = []
1844
+ if !MU::Cloud::AWS.isGovCloud?
1845
+ mu_zone = MU::Cloud::DNSZone.find(cloud_id: "platform-mu").values.first
1846
+ end
1847
+ # XXX what if we're in GCP?
1848
+ # XXX need a MU::Cloud::DNSZone.lookup for bulk lookups
1849
+ # XXX also grab things like mu_windows_name out of deploy data if we can
1850
+
1851
+ parent_thread_id = Thread.current.object_id
1852
+ MU::MommaCat.listDeploys.sort.each { |deploy_id|
1853
+ begin
1854
+ # We don't want to use cached litter information here because this is also called by cleanTerminatedInstances.
1855
+ deploy = MU::MommaCat.getLitter(deploy_id, use_cache: false)
1856
+ if deploy.ssh_key_name.nil? or deploy.ssh_key_name.empty?
1857
+ MU.log "Failed to extract ssh key name from #{deploy_id} in syncMonitoringConfig", MU::ERR if deploy.kittens.has_key?("servers")
1858
+ next
1859
+ end
1860
+ FileUtils.cp("#{@myhome}/.ssh/#{deploy.ssh_key_name}", "#{@nagios_home}/.ssh/#{deploy.ssh_key_name}")
1861
+ File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, "#{@nagios_home}/.ssh/#{deploy.ssh_key_name}")
1862
+ if deploy.kittens.has_key?("servers")
1863
+ deploy.kittens["servers"].each_pair { |nodeclass, nodes|
1864
+ nodes.each_pair { |mu_name, server|
1865
+ MU.dupGlobals(parent_thread_id)
1866
+ threads << Thread.new {
1867
+ MU::MommaCat.setThreadContext(deploy)
1868
+ MU.log "Adding #{server.mu_name} to #{@nagios_home}/.ssh/config", MU::DEBUG
1869
+ MU::MommaCat.addHostToSSHConfig(
1870
+ server,
1871
+ ssh_dir: "#{@nagios_home}/.ssh",
1872
+ ssh_conf: "#{@nagios_home}/.ssh/config.tmp",
1873
+ ssh_owner: "nagios"
1874
+ )
1875
+ MU.purgeGlobals
1876
+ }
1877
+ }
1878
+ }
1879
+ end
1880
+ rescue Exception => e
1881
+ MU.log "#{e.inspect} while generating Nagios SSH config in #{deploy_id}", MU::ERR, details: e.backtrace
1882
+ end
1883
+ }
1884
+ threads.each { |t|
1885
+ t.join
1886
+ }
1887
+ ssh_lock.flock(File::LOCK_UN)
1888
+ ssh_lock.close
1889
+ File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, "#{@nagios_home}/.ssh/config.tmp")
1890
+ File.rename("#{@nagios_home}/.ssh/config.tmp", "#{@nagios_home}/.ssh/config")
1891
+
1892
+ MU.log "Updating Nagios monitoring config, this may take a while..."
1893
+ output = nil
1894
+ if $MU_CFG and !$MU_CFG['master_runlist_extras'].nil?
1895
+ output = %x{#{MU::Groomer::Chef.chefclient} -o 'role[mu-master-nagios-only],#{$MU_CFG['master_runlist_extras'].join(",")}' 2>&1}
1896
+ else
1897
+ output = %x{#{MU::Groomer::Chef.chefclient} -o 'role[mu-master-nagios-only]' 2>&1}
1898
+ end
1899
+
1900
+ if $?.exitstatus != 0
1901
+ MU.log "Nagios monitoring config update returned a non-zero exit code!", MU::ERR, details: output
1902
+ else
1903
+ MU.log "Nagios monitoring config update complete."
1904
+ end
1905
+ }
1906
+
1907
+ if blocking
1908
+ nagios_threads.each { |t|
1909
+ t.join
1910
+ }
1911
+ end
1912
+ end
1913
+
1914
+ # Return a list of all currently active deploy identifiers.
1915
+ # @return [Array<String>]
1916
+ def self.listDeploys
1917
+ return [] if !Dir.exists?("#{MU.dataDir}/deployments")
1918
+ deploys = []
1919
+ Dir.entries("#{MU.dataDir}/deployments").reverse_each { |muid|
1920
+ next if !Dir.exists?("#{MU.dataDir}/deployments/#{muid}") or muid == "." or muid == ".."
1921
+ deploys << muid
1922
+ }
1923
+ return deploys
1924
+ end
1925
+
1926
+ # Return a list of all nodes in all deployments. Does so without loading
1927
+ # deployments fully.
1928
+ # @return [Hash]
1929
+ def self.listAllNodes
1930
+ nodes = Hash.new
1931
+ MU::MommaCat.deploy_struct_semaphore.synchronize {
1932
+ MU::MommaCat.listDeploys.each { |deploy|
1933
+ if !Dir.exists?(MU::MommaCat.deploy_dir(deploy)) or
1934
+ !File.size?("#{MU::MommaCat.deploy_dir(deploy)}/deployment.json")
1935
+ MU.log "Didn't see deployment metadata for '#{deploy}'", MU::WARN
1936
+ next
1937
+ end
1938
+ data = File.open("#{MU::MommaCat.deploy_dir(deploy)}/deployment.json", File::RDONLY)
1939
+ MU.log "Getting lock to read #{MU::MommaCat.deploy_dir(deploy)}/deployment.json", MU::DEBUG
1940
+ data.flock(File::LOCK_EX)
1941
+ begin
1942
+ deployment = JSON.parse(File.read("#{MU::MommaCat.deploy_dir(deploy)}/deployment.json"))
1943
+ deployment["deploy_id"] = deploy
1944
+ if deployment.has_key?("servers")
1945
+ deployment["servers"].each_key { |nodeclass|
1946
+ deployment["servers"][nodeclass].each_pair { |mu_name, metadata|
1947
+ nodes[mu_name] = metadata
1948
+ }
1949
+ }
1950
+ end
1951
+ rescue JSON::ParserError => e
1952
+ MU.log "JSON parse failed on #{MU::MommaCat.deploy_dir(deploy)}/deployment.json", MU::ERR
1953
+ end
1954
+ data.flock(File::LOCK_UN)
1955
+ data.close
1956
+ }
1957
+ }
1958
+ return nodes
1959
+ end
1960
+
1961
+ # Return a list of all nodes associated with the current deployment.
1962
+ # @return [Hash]
1963
+ def listNodes
1964
+ nodes = Hash.new
1965
+ if !@deployment['servers'].nil?
1966
+ @deployment['servers'].each_pair { |nodetype, node|
1967
+ node.each_pair { |name, metadata|
1968
+ if name.nil? or metadata.nil? or !metadata.is_a?(Hash)
1969
+ MU.log "Original config of deploy #{MU.deploy_id} looks funny. It's probably very old.", MU::WARN
1970
+ next
1971
+ end
1972
+ metadata['deploy_id'] = MU.deploy_id
1973
+ nodes[name] = metadata
1974
+ ['servers', 'server_pools'].each { |res_type|
1975
+ if !@original_config[res_type].nil?
1976
+ @original_config[res_type].each { |srv_conf|
1977
+ if srv_conf['name'] == nodetype
1978
+ nodes[name]['conf'] = srv_conf.dup
1979
+ end
1980
+ }
1981
+ end
1982
+ }
1983
+ }
1984
+ }
1985
+ end
1986
+
1987
+ return nodes
1988
+ end
1989
+
1990
+ # For a given (Windows) server, return it's administrator user and password.
1991
+ # This is generally for requests made to MommaCat from said server, which
1992
+ # we can assume have been authenticated with the deploy secret.
1993
+ # @param server [MU::Cloud::Server]: The Server object whose credentials we're fetching.
1994
+ def retrieveWindowsAdminCreds(server)
1995
+ if server.nil?
1996
+ raise MuError, "retrieveWindowsAdminCreds must be called with a Server object"
1997
+ elsif !server.is_a?(MU::Cloud::Server)
1998
+ raise MuError, "retrieveWindowsAdminCreds must be called with a Server object (got #{server.class.name})"
1999
+ end
2000
+ if server.config['use_cloud_provider_windows_password']
2001
+ return [server.config["windows_admin_username"], server.getWindowsAdminPassword]
2002
+ elsif server.config['windows_auth_vault'] && !server.config['windows_auth_vault'].empty?
2003
+ if server.config["windows_auth_vault"].has_key?("password_field")
2004
+ return [server.config["windows_admin_username"],
2005
+ server.groomer.getSecret(
2006
+ vault: server.config['windows_auth_vault']['vault'],
2007
+ item: server.config['windows_auth_vault']['item'],
2008
+ field: server.config["windows_auth_vault"]["password_field"]
2009
+ )]
2010
+ else
2011
+ return [server.config["windows_admin_username"], server.getWindowsAdminPassword]
2012
+ end
2013
+ end
2014
+ []
2015
+ end
2016
+
2017
+ # Given a Certificate Signing Request, sign it with our internal CA and
2018
+ # writers the resulting signed certificate. Only works on local files.
2019
+ # @param csr_path [String]: The CSR to sign, as a file.
2020
+ def signSSLCert(csr_path, sans = [])
2021
+ # XXX more sanity here, this feels unsafe
2022
+ certdir = File.dirname(csr_path)
2023
+ certname = File.basename(csr_path, ".csr")
2024
+ if File.exists?("#{certdir}/#{certname}.crt")
2025
+ MU.log "Not re-signing SSL certificate request #{csr_path}, #{certdir}/#{certname}.crt already exists", MU::WARN
2026
+ return
2027
+ end
2028
+ MU.log "Signing SSL certificate request #{csr_path} with #{MU.mySSLDir}/Mu_CA.pem"
2029
+
2030
+ begin
2031
+ csr = OpenSSL::X509::Request.new File.read csr_path
2032
+ rescue Exception => e
2033
+ MU.log e.message, MU::ERR, details: File.read(csr_path)
2034
+ raise e
2035
+ end
2036
+ key = OpenSSL::PKey::RSA.new File.read "#{certdir}/#{certname}.key"
2037
+
2038
+ # Load up the Mu Certificate Authority
2039
+ cakey = OpenSSL::PKey::RSA.new File.read "#{MU.mySSLDir}/Mu_CA.key"
2040
+ cacert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/Mu_CA.pem"
2041
+ cur_serial = 0
2042
+ File.open("#{MU.mySSLDir}/serial", File::CREAT|File::RDWR, 0600) { |f|
2043
+ f.flock(File::LOCK_EX)
2044
+ cur_serial = f.read.chomp!.to_i
2045
+ cur_serial = cur_serial + 1
2046
+ f.rewind
2047
+ f.truncate(0)
2048
+ f.puts cur_serial
2049
+ f.flush
2050
+ f.flock(File::LOCK_UN)
2051
+ }
2052
+
2053
+ # Create a certificate from our CSR, signed by the Mu CA
2054
+ cert = OpenSSL::X509::Certificate.new
2055
+ cert.serial = cur_serial
2056
+ cert.version = 3
2057
+ cert.not_before = Time.now
2058
+ cert.not_after = Time.now + 180000000
2059
+ cert.subject = csr.subject
2060
+ cert.public_key = csr.public_key
2061
+ cert.issuer = cacert.subject
2062
+ if !sans.nil? and sans.size > 0
2063
+ MU.log "Incorporting Subject Alternative Names: #{sans.join(",")}"
2064
+ ef = OpenSSL::X509::ExtensionFactory.new
2065
+ ef.issuer_certificate = cacert
2066
+ #v3_req_client
2067
+ ef.subject_certificate = cert
2068
+ ef.subject_request = csr
2069
+ cert.add_extension(ef.create_extension("keyUsage","nonRepudiation,digitalSignature,keyEncipherment", false))
2070
+ cert.add_extension(ef.create_extension("subjectAltName",sans.join(","),false))
2071
+ # XXX only do this if we see the otherName thinger in the san list
2072
+ cert.add_extension(ef.create_extension("extendedKeyUsage","clientAuth,serverAuth,codeSigning,emailProtection",false))
2073
+ end
2074
+ cert.sign cakey, OpenSSL::Digest::SHA256.new
2075
+
2076
+ open("#{certdir}/#{certname}.crt", 'w', 0644) { |io|
2077
+ io.write cert.to_pem
2078
+ }
2079
+ if MU.mu_user != "mu"
2080
+ owner_uid = Etc.getpwnam(MU.mu_user).uid
2081
+ File.chown(owner_uid, nil, "#{certdir}/#{certname}.crt")
2082
+ end
2083
+
2084
+ end
2085
+
2086
+ # Make sure deployment data is synchronized to/from each node in the
2087
+ # currently-loaded deployment.
2088
+ def syncLitter(nodeclasses = [], triggering_node: nil, save_all_only: false)
2089
+ # XXX take some config logic to decide what nodeclasses to hit
2090
+ # XXX don't run on triggering node, duh
2091
+ return if MU.syncLitterThread
2092
+ return if !Dir.exists?(deploy_dir)
2093
+ svrs = MU::Cloud.resource_types[:Server][:cfg_plural] # legibility shorthand
2094
+
2095
+ @kitten_semaphore.synchronize {
2096
+ if @kittens.nil? or
2097
+ @kittens[svrs].nil?
2098
+ MU.log "No #{svrs} as yet available in #{@deploy_id}", MU::DEBUG, details: @kittens
2099
+ return
2100
+ end
2101
+
2102
+ MU.log "Updating these siblings in #{@deploy_id}: #{nodeclasses.join(', ')}", MU::DEBUG, details: @kittens[svrs].map { |nodeclass, instance| instance.keys }
2103
+ }
2104
+
2105
+ update_servers = []
2106
+ if nodeclasses.nil? or nodeclasses.size == 0
2107
+ litter = findLitterMate(type: "server", return_all: true)
2108
+ litter.each_pair { |mu_name, node|
2109
+ next if !triggering_node.nil? and mu_name == triggering_node.mu_name
2110
+ if !node.groomer.nil?
2111
+ update_servers << node
2112
+ end
2113
+ }
2114
+ else
2115
+ litter = {}
2116
+ nodeclasses.each { |nodeclass|
2117
+ mates = findLitterMate(type: "server", name: nodeclass, return_all: true)
2118
+ litter.merge!(mates) if mates
2119
+ }
2120
+ litter.each_pair { |mu_name, node|
2121
+ next if !triggering_node.nil? and mu_name == triggering_node.mu_name
2122
+ if !node.deploydata or !node.deploydata.keys.include?('nodename')
2123
+ details = node.deploydata ? node.deploydata.keys : nil
2124
+ MU.log "#{mu_name} deploy data is missing (possibly retired), not syncing it", MU::WARN, details: details
2125
+ else
2126
+ update_servers << node
2127
+ end
2128
+ }
2129
+ end
2130
+ return if update_servers.size == 0
2131
+
2132
+ update_servers.each { |node|
2133
+ # Not clear where this pollution comes from, but let's stick a temp
2134
+ # fix in here.
2135
+ if node.deploydata['nodename'] != node.mu_name
2136
+ MU.log "Node #{node.mu_name} had wrong or missing nodename (#{node.deploydata['nodename']}), correcting", MU::WARN
2137
+ node.deploydata['nodename'] = node.mu_name
2138
+ @deployment[svrs][node.config['name']][node.mu_name]['nodename'] = node.mu_name
2139
+ save!
2140
+ end
2141
+ }
2142
+
2143
+ # Merge everyone's deploydata together
2144
+ if !save_all_only
2145
+ skip = []
2146
+ update_servers.each { |node|
2147
+ if node.mu_name.nil? or node.deploydata.nil? or node.config.nil?
2148
+ MU.log "Missing mu_name #{node.mu_name}, deploydata, or config from #{node} in syncLitter", MU::ERR, details: node.deploydata
2149
+ next
2150
+ end
2151
+
2152
+ if !@deployment[svrs][node.config['name']].has_key?(node.mu_name) or @deployment[svrs][node.config['name']][node.mu_name] != node.deploydata
2153
+ @deployment[svrs][node.config['name']][node.mu_name] = node.deploydata
2154
+ else
2155
+ skip << node
2156
+ end
2157
+ }
2158
+ update_servers = update_servers - skip
2159
+ end
2160
+
2161
+ return if update_servers.size < 1
2162
+ threads = []
2163
+ parent_thread_id = Thread.current.object_id
2164
+ update_servers.each { |sibling|
2165
+ threads << Thread.new {
2166
+ Thread.abort_on_exception = true
2167
+ MU.dupGlobals(parent_thread_id)
2168
+ Thread.current.thread_variable_set("name", "sync-"+sibling.mu_name.downcase)
2169
+ MU.setVar("syncLitterThread", true)
2170
+ begin
2171
+ sibling.groomer.saveDeployData
2172
+ sibling.groomer.run(purpose: "Synchronizing sibling kittens") if !save_all_only
2173
+ rescue MU::Groomer::RunError => e
2174
+ MU.log "Sync of #{sibling.mu_name} failed: #{e.inspect}", MU::WARN
2175
+ end
2176
+ MU.purgeGlobals
2177
+ }
2178
+ }
2179
+
2180
+ threads.each { |t|
2181
+ t.join
2182
+ }
2183
+
2184
+ MU.log "Synchronization of #{@deploy_id} complete", MU::DEBUG, details: update_servers
2185
+ end
2186
+
2187
+ @node_cert_semaphore = nil
2188
+ # Given a MU::Cloud object, return the generic self-signed SSL
2189
+ # certficate we made for it. If one doesn't exist yet, generate it first.
2190
+ # If it's a Windows node, also generate a certificate for WinRM client auth.
2191
+ # @param resource [MU::Cloud]: The server or other MU::Cloud resource object for which to generate or return the cert
2192
+ # @param poolname [Boolean]: If true, generate certificates for the base name of the server pool of which this node is a member, rather than for the individual node
2193
+ # @param keysize [Integer]: The size of the private key to use when generating this certificate
2194
+ def nodeSSLCerts(resource, poolname = false, keysize = 4096)
2195
+ nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = resource.getSSHConfig
2196
+
2197
+ deploy_id = resource.deploy_id || resource.deploy.deploy_id
2198
+
2199
+ cert_cn = poolname ? deploy_id + "-" + resource.config['name'].upcase : resource.mu_name
2200
+
2201
+ certs = {}
2202
+ results = {}
2203
+
2204
+ @node_cert_semaphore.synchronize {
2205
+ if File.exists?("#{MU.mySSLDir}/#{cert_cn}.crt") and
2206
+ File.exists?("#{MU.mySSLDir}/#{cert_cn}.key")
2207
+ ext_cert = OpenSSL::X509::Certificate.new(File.read("#{MU.mySSLDir}/#{cert_cn}.crt"))
2208
+ if ext_cert.not_after < Time.now
2209
+ MU.log "Node certificate for #{cert_cn} is expired, regenerating", MU::WARN
2210
+ ["crt", "key", "csr"].each { |suffix|
2211
+ if File.exists?("#{MU.mySSLDir}/#{cert_cn}.#{suffix}")
2212
+ File.unlink("#{MU.mySSLDir}/#{cert_cn}.#{suffix}")
2213
+ end
2214
+ }
2215
+ else
2216
+ results[cert_cn] = [
2217
+ OpenSSL::X509::Certificate.new(File.read("#{MU.mySSLDir}/#{cert_cn}.crt")),
2218
+ OpenSSL::PKey::RSA.new(File.read("#{MU.mySSLDir}/#{cert_cn}.key"))
2219
+ ]
2220
+ end
2221
+ end
2222
+
2223
+ if results.size == 0
2224
+ certs[cert_cn] = {
2225
+ "sans" => ["IP:#{canonical_ip}"],
2226
+ "cn" => cert_cn
2227
+ }
2228
+ if canonical_ip
2229
+ certs[cert_cn]["sans"] = ["IP:#{canonical_ip}"]
2230
+ end
2231
+ end
2232
+
2233
+ if [MU::Cloud::Server, MU::Cloud::AWS::Server, MU::Cloud::Google::Server].include?(resource.class) and resource.windows?
2234
+ if File.exists?("#{MU.mySSLDir}/#{cert_cn}-winrm.crt") and
2235
+ File.exists?("#{MU.mySSLDir}/#{cert_cn}-winrm.key")
2236
+ results[cert_cn+"-winrm"] = [File.read("#{MU.mySSLDir}/#{cert_cn}-winrm.crt"), File.read("#{MU.mySSLDir}/#{cert_cn}-winrm.key")]
2237
+ else
2238
+ certs[cert_cn+"-winrm"] = {
2239
+ "sans" => ["otherName:1.3.6.1.4.1.311.20.2.3;UTF8:#{resource.config['windows_admin_username']}@localhost"],
2240
+ "cn" => resource.config['windows_admin_username']
2241
+ }
2242
+ end
2243
+ end
2244
+
2245
+ certs.each { |certname, data|
2246
+ MU.log "Generating SSL certificate #{certname} for #{resource}"
2247
+
2248
+ # Create and save a key
2249
+ key = OpenSSL::PKey::RSA.new keysize
2250
+ if !Dir.exist?(MU.mySSLDir)
2251
+ Dir.mkdir(MU.mySSLDir, 0700)
2252
+ end
2253
+
2254
+ open("#{MU.mySSLDir}/#{certname}.key", 'w', 0600) { |io|
2255
+ io.write key.to_pem
2256
+ }
2257
+ # Create a certificate request for this node
2258
+ csr = OpenSSL::X509::Request.new
2259
+ csr.version = 3
2260
+ csr.subject = OpenSSL::X509::Name.parse "CN=#{data['cn']}/O=Mu/C=US"
2261
+ csr.public_key = key.public_key
2262
+ csr.sign key, OpenSSL::Digest::SHA256.new
2263
+ open("#{MU.mySSLDir}/#{certname}.csr", 'w', 0644) { |io|
2264
+ io.write csr.to_pem
2265
+ }
2266
+ if MU.chef_user == "mu"
2267
+ signSSLCert("#{MU.mySSLDir}/#{certname}.csr", data['sans'])
2268
+ else
2269
+ deploykey = OpenSSL::PKey::RSA.new(public_key)
2270
+ deploysecret = Base64.urlsafe_encode64(deploykey.public_encrypt(deploy_secret))
2271
+ # XXX things that aren't servers
2272
+ res_type = "server"
2273
+ res_type = "server_pool" if !resource.config['basis'].nil?
2274
+ uri = URI("https://#{MU.mu_public_addr}:2260/")
2275
+ req = Net::HTTP::Post.new(uri)
2276
+ req.set_form_data(
2277
+ "mu_id" => MU.deploy_id,
2278
+ "mu_resource_name" => resource.config['name'],
2279
+ "mu_resource_type" => res_type,
2280
+ "mu_ssl_sign" => "#{MU.mySSLDir}/#{certname}.csr",
2281
+ "mu_ssl_sans" => data["sans"].join(","),
2282
+ "mu_user" => MU.mu_user,
2283
+ "mu_deploy_secret" => deploysecret
2284
+ )
2285
+ http = Net::HTTP.new(uri.hostname, uri.port)
2286
+ http.ca_file = "/etc/pki/Mu_CA.pem" # XXX why no worky?
2287
+ http.use_ssl = true
2288
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX this sucks
2289
+ response = http.request(req)
2290
+ MU.log "Got error back on signing request for #{MU.mySSLDir}/#{certname}.csr", MU::ERR if response.code != "200"
2291
+ end
2292
+
2293
+ pfx = nil
2294
+ cert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/#{certname}.crt"
2295
+ if [MU::Cloud::Server, MU::Cloud::AWS::Server, MU::Cloud::Google::Server].include?(resource.class) and resource.windows?
2296
+ cacert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/Mu_CA.pem"
2297
+ pfx = OpenSSL::PKCS12.create(nil, nil, key, cert, [cacert], nil, nil, nil, nil)
2298
+ open("#{MU.mySSLDir}/#{certname}.pfx", 'w', 0644) { |io|
2299
+ io.write pfx.to_der
2300
+ }
2301
+ end
2302
+
2303
+ results[certname] = [cert, key]
2304
+
2305
+ if resource.config['cloud'] == "AWS"
2306
+ MU::Cloud::AWS.writeDeploySecret(@deploy_id, cert.to_pem, certname+".crt")
2307
+ MU::Cloud::AWS.writeDeploySecret(@deploy_id, key.to_pem, certname+".key")
2308
+ if pfx
2309
+ MU::Cloud::AWS.writeDeploySecret(@deploy_id, pfx.to_der, certname+".pfx")
2310
+ end
2311
+ # XXX add google logic, or better yet abstract this method
2312
+ end
2313
+ }
2314
+ }
2315
+
2316
+ results[cert_cn]
2317
+ end
2318
+
2319
+ # @return [String]: The Mu Master filesystem directory holding metadata for the current deployment
2320
+ def deploy_dir
2321
+ MU::MommaCat.deploy_dir(@deploy_id)
2322
+ end
2323
+
2324
+ private
2325
+
2326
+ # Check to see whether a given resource name is unique across all
2327
+ # deployments on this Mu server. We only enforce this for certain classes
2328
+ # of names. If the name in question is available, add it to our cache of
2329
+ # said names. See #{MU::MommaCat.getResourceName}
2330
+ # @param name [String]: The name to attempt to allocate.
2331
+ # @return [Boolean]: True if allocation was successful.
2332
+ def allocateUniqueResourceName(name)
2333
+ raise MuError, "Cannot call allocateUniqueResourceName without an active deployment" if @deploy_id.nil?
2334
+ path = File.expand_path(MU.dataDir+"/deployments")
2335
+ File.open(path+"/unique_ids", File::CREAT|File::RDWR, 0600) { |f|
2336
+ existing = []
2337
+ f.flock(File::LOCK_EX)
2338
+ f.readlines.each { |line|
2339
+ existing << line.chomp
2340
+ }
2341
+ begin
2342
+ existing.each { |used|
2343
+ if used.match(/^#{name}:/)
2344
+ if !used.match(/^#{name}:#{@deploy_id}$/)
2345
+ MU.log "#{name} is already reserved by another resource on this Mu server.", MU::WARN, details: caller
2346
+ return false
2347
+ else
2348
+ return true
2349
+ end
2350
+ end
2351
+ }
2352
+ f.puts name+":"+@deploy_id
2353
+ return true
2354
+ ensure
2355
+ f.flock(File::LOCK_UN)
2356
+ end
2357
+ }
2358
+ end
2359
+
2360
+ ###########################################################################
2361
+ ###########################################################################
2362
+ def self.deploy_dir(deploy_id)
2363
+ raise MuError, "deploy_dir must get a deploy_id if called as class method (from #{caller[0]}; #{caller[1]})" if deploy_id.nil?
2364
+ # XXX this will blow up if someone sticks MU in /
2365
+ path = File.expand_path(MU.dataDir+"/deployments")
2366
+ if !Dir.exist?(path)
2367
+ MU.log "Creating #{path}", MU::DEBUG
2368
+ Dir.mkdir(path, 0700)
2369
+ end
2370
+ path = path+"/"+deploy_id
2371
+ return path
2372
+ end
2373
+
2374
+ def self.deploy_exists?(deploy_id)
2375
+ if deploy_id.nil? or deploy_id.empty?
2376
+ MU.log "Got nil deploy_id in MU::MommaCat.deploy_exists?", MU::WARN
2377
+ return
2378
+ end
2379
+ path = File.expand_path(MU.dataDir+"/deployments")
2380
+ if !Dir.exists?(path)
2381
+ Dir.mkdir(path, 0700)
2382
+ end
2383
+ deploy_path = File.expand_path(path+"/"+deploy_id)
2384
+ return Dir.exist?(deploy_path)
2385
+ end
2386
+
2387
+
2388
+ def createDeployKey
2389
+ key = OpenSSL::PKey::RSA.generate(4096)
2390
+ MU.log "Generated deploy key for #{MU.deploy_id}", MU::DEBUG, details: key.public_key.export
2391
+ return [key.export, key.public_key.export]
2392
+ end
2393
+
2394
+ # Synchronize all in-memory information related to this to deployment to
2395
+ # disk.
2396
+ def save!(triggering_node = nil)
2397
+ return if @no_artifacts
2398
+ MU::MommaCat.deploy_struct_semaphore.synchronize {
2399
+ MU.log "Saving deployment #{MU.deploy_id}", MU::DEBUG
2400
+
2401
+ if !Dir.exist?(deploy_dir)
2402
+ MU.log "Creating #{deploy_dir}", MU::DEBUG
2403
+ Dir.mkdir(deploy_dir, 0700)
2404
+ end
2405
+
2406
+ if !@private_key.nil?
2407
+ privkey = File.new("#{deploy_dir}/private_key", File::CREAT|File::TRUNC|File::RDWR, 0600)
2408
+ privkey.puts @private_key
2409
+ privkey.close
2410
+ end
2411
+
2412
+ if !@public_key.nil?
2413
+ pubkey = File.new("#{deploy_dir}/public_key", File::CREAT|File::TRUNC|File::RDWR, 0600)
2414
+ pubkey.puts @public_key
2415
+ pubkey.close
2416
+ end
2417
+
2418
+ if !@deployment.nil? and @deployment.size > 0
2419
+ @deployment['handle'] = MU.handle if @deployment['handle'].nil? and !MU.handle.nil?
2420
+ @deployment['public_key'] = @public_key
2421
+ begin
2422
+ # XXX doing this to trigger JSON errors before stomping the stored
2423
+ # file...
2424
+ JSON.pretty_generate(@deployment, max_nesting: false)
2425
+ deploy = File.new("#{deploy_dir}/deployment.json", File::CREAT|File::TRUNC|File::RDWR, 0600)
2426
+ MU.log "Getting lock to write #{deploy_dir}/deployment.json", MU::DEBUG
2427
+ deploy.flock(File::LOCK_EX)
2428
+ deploy.puts JSON.pretty_generate(@deployment, max_nesting: false)
2429
+ rescue JSON::NestingError => e
2430
+ raise MuError, e.inspect+"\n\n"+@deployment.to_s
2431
+ end
2432
+ deploy.flock(File::LOCK_UN)
2433
+ deploy.close
2434
+ end
2435
+
2436
+ if !@original_config.nil? and @original_config.is_a?(Hash)
2437
+ config = File.new("#{deploy_dir}/basket_of_kittens.json", File::CREAT|File::TRUNC|File::RDWR, 0600)
2438
+ config.puts JSON.pretty_generate(@original_config)
2439
+ config.close
2440
+ end
2441
+
2442
+ if !@ssh_private_key.nil?
2443
+ key = File.new("#{deploy_dir}/node_ssh.key", File::CREAT|File::TRUNC|File::RDWR, 0600)
2444
+ key.puts @ssh_private_key
2445
+ key.close
2446
+ end
2447
+ if !@ssh_public_key.nil?
2448
+ key = File.new("#{deploy_dir}/node_ssh.pub", File::CREAT|File::TRUNC|File::RDWR, 0600)
2449
+ key.puts @ssh_public_key
2450
+ key.close
2451
+ end
2452
+ if !@ssh_key_name.nil?
2453
+ key = File.new("#{deploy_dir}/ssh_key_name", File::CREAT|File::TRUNC|File::RDWR, 0600)
2454
+ key.puts @ssh_key_name
2455
+ key.close
2456
+ end
2457
+ if !@environment.nil?
2458
+ env = File.new("#{deploy_dir}/environment_name", File::CREAT|File::TRUNC|File::RDWR, 0600)
2459
+ env.puts @environment
2460
+ env.close
2461
+ end
2462
+ if !@deploy_secret.nil?
2463
+ secret = File.new("#{deploy_dir}/deploy_secret", File::CREAT|File::TRUNC|File::RDWR, 0600)
2464
+ secret.print @deploy_secret
2465
+ secret.close
2466
+ end
2467
+ if !@secrets.nil?
2468
+ secretdir = "#{deploy_dir}/secrets"
2469
+ if !Dir.exist?(secretdir)
2470
+ MU.log "Creating #{secretdir}", MU::DEBUG
2471
+ Dir.mkdir(secretdir, 0700)
2472
+ end
2473
+ @secrets.each_pair { |type, server|
2474
+ server.each_pair { |server, secret|
2475
+ key = File.new("#{secretdir}/#{type}.#{server}", File::CREAT|File::TRUNC|File::RDWR, 0600)
2476
+ key.puts secret
2477
+ key.close
2478
+ }
2479
+ }
2480
+ end
2481
+ }
2482
+
2483
+ # Update groomer copies of this metadata
2484
+ syncLitter(@deployment['servers'].keys, save_all_only: true) if @deployment.has_key?("servers")
2485
+ end
2486
+
2487
+ # Find one or more resources by their Mu resource name, and return
2488
+ # MommaCat objects for their containing deploys, their BoK config data,
2489
+ # and their deployment data.
2490
+ #
2491
+ # @param type [String]: The type of resource, e.g. "vpc" or "server."
2492
+ # @param name [String]: The Mu resource class, typically the name field of a Basket of Kittens resource declaration.
2493
+ # @param mu_name [String]: The fully-expanded Mu resource name, e.g. MGMT-PROD-2015040115-FR-ADMGMT2
2494
+ # @param deploy_id [String]: The deployment to search. Will search all deployments if not specified.
2495
+ # @return [Hash,Array<Hash>]
2496
+ def self.getResourceMetadata(type, name: nil, deploy_id: nil, use_cache: true, mu_name: nil)
2497
+ if type.nil?
2498
+ raise MuError, "Can't call getResourceMetadata without a type argument"
2499
+ end
2500
+ shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
2501
+ type = cfg_plural
2502
+
2503
+ deploy_root = File.expand_path(MU.dataDir+"/deployments")
2504
+ MU::MommaCat.deploy_struct_semaphore.synchronize {
2505
+ if Dir.exists?(deploy_root)
2506
+ Dir.entries(deploy_root).each { |deploy|
2507
+ this_deploy_dir = deploy_root+"/"+deploy
2508
+ next if deploy == "." or deploy == ".." or !Dir.exists?(this_deploy_dir)
2509
+ next if deploy_id and deploy_id != deploy
2510
+
2511
+ if !File.size?(this_deploy_dir+"/deployment.json")
2512
+ MU.log "#{this_deploy_dir}/deployment.json doesn't exist, skipping when loading cache", MU::DEBUG
2513
+ next
2514
+ end
2515
+ if @deploy_cache[deploy].nil? or !use_cache
2516
+ @deploy_cache[deploy] = Hash.new
2517
+ elsif @deploy_cache[deploy]['mtime'] == File.mtime("#{this_deploy_dir}/deployment.json")
2518
+ MU.log "Using cached copy of deploy #{deploy} from #{@deploy_cache[deploy]['mtime']}", MU::DEBUG
2519
+
2520
+ next
2521
+ end
2522
+
2523
+ @deploy_cache[deploy] = Hash.new if !@deploy_cache.has_key?(deploy)
2524
+ MU.log "Caching deploy #{deploy}", MU::DEBUG
2525
+ lock = File.open("#{this_deploy_dir}/deployment.json", File::RDONLY)
2526
+ lock.flock(File::LOCK_EX)
2527
+ @deploy_cache[deploy]['mtime'] = File.mtime("#{this_deploy_dir}/deployment.json")
2528
+
2529
+ begin
2530
+ @deploy_cache[deploy]['data'] = JSON.parse(File.read("#{this_deploy_dir}/deployment.json"))
2531
+ lock.flock(File::LOCK_UN)
2532
+
2533
+ next if @deploy_cache[deploy].nil? or @deploy_cache[deploy]['data'].nil?
2534
+ # Populate some generable entries that should be in the deploy
2535
+ # data. Also, bounce out if we realize we've found exactly what
2536
+ # we needed already.
2537
+ MU::Cloud.resource_types.each_pair { |res_type, attrs|
2538
+
2539
+ next if @deploy_cache[deploy]['data'][attrs[:cfg_plural]].nil?
2540
+ if !attrs[:has_multiples]
2541
+ @deploy_cache[deploy]['data'][attrs[:cfg_plural]].each_pair { |nodename, data|
2542
+ # XXX we don't actually store node names for some resources, need to farm them
2543
+ # and fix metadata
2544
+ # if !mu_name.nil? and nodename == mu_name
2545
+ # return { deploy => [data] }
2546
+ # end
2547
+ }
2548
+ else
2549
+ @deploy_cache[deploy]['data'][attrs[:cfg_plural]].each_pair { |node_class, nodes|
2550
+ next if nodes.nil? or !nodes.is_a?(Hash)
2551
+ nodes.each_pair { |nodename, data|
2552
+ next if !data.is_a?(Hash)
2553
+ data['#MU_NODE_CLASS'] = node_class
2554
+ if !data.has_key?("cloud") # XXX kludge until old metadata gets fixed
2555
+ data["cloud"] = MU::Config.defaultCloud
2556
+ end
2557
+ data['#MU_NAME'] = nodename
2558
+ if !mu_name.nil? and nodename == mu_name
2559
+ return {deploy => [data]} if deploy_id && deploy == deploy_id
2560
+ end
2561
+ }
2562
+ }
2563
+ end
2564
+ }
2565
+ rescue JSON::ParserError => e
2566
+ raise MuError, "JSON parse failed on #{this_deploy_dir}/deployment.json\n\n"+File.read("#{this_deploy_dir}/deployment.json")
2567
+ end
2568
+ lock.flock(File::LOCK_UN)
2569
+ lock.close
2570
+ }
2571
+ end
2572
+ }
2573
+
2574
+ matches = {}
2575
+
2576
+ if deploy_id.nil?
2577
+ @deploy_cache.each_key { |deploy|
2578
+ next if !@deploy_cache[deploy].has_key?('data')
2579
+ next if !@deploy_cache[deploy]['data'].has_key?(type)
2580
+ if !name.nil?
2581
+ next if @deploy_cache[deploy]['data'][type][name].nil?
2582
+ matches[deploy] = [] if !matches.has_key?(deploy)
2583
+ matches[deploy] << @deploy_cache[deploy]['data'][type][name].dup
2584
+ else
2585
+ matches[deploy] = [] if !matches.has_key?(deploy)
2586
+ matches[deploy].concat(@deploy_cache[deploy]['data'][type].values)
2587
+ end
2588
+ }
2589
+ return matches
2590
+ elsif !@deploy_cache[deploy_id].nil?
2591
+ if !@deploy_cache[deploy_id]['data'].nil? and
2592
+ !@deploy_cache[deploy_id]['data'][type].nil?
2593
+ if !name.nil?
2594
+ if !@deploy_cache[deploy_id]['data'][type][name].nil?
2595
+ matches[deploy_id] = [] if !matches.has_key?(deploy_id)
2596
+ matches[deploy_id] << @deploy_cache[deploy_id]['data'][type][name].dup
2597
+ else
2598
+ return matches # nothing, actually
2599
+ end
2600
+ else
2601
+ matches[deploy_id] = @deploy_cache[deploy_id]['data'][type].values
2602
+ end
2603
+ end
2604
+ end
2605
+
2606
+ return matches
2607
+ end
2608
+
2609
+ ###########################################################################
2610
+ ###########################################################################
2611
+ def loadDeploy(deployment_json_only = false, set_context_to_me: true)
2612
+ MU::MommaCat.deploy_struct_semaphore.synchronize {
2613
+ if File.size?(deploy_dir+"/deployment.json")
2614
+ deploy = File.open("#{deploy_dir}/deployment.json", File::RDONLY)
2615
+ MU.log "Getting lock to read #{deploy_dir}/deployment.json", MU::DEBUG
2616
+ # deploy.flock(File::LOCK_EX)
2617
+ begin
2618
+ Timeout::timeout(90) {deploy.flock(File::LOCK_EX)}
2619
+ rescue Timeout::Error
2620
+ raise MuError, "Timed out trying to get an exclusive lock on #{deploy_dir}/deployment.json"
2621
+ end
2622
+
2623
+ begin
2624
+ @deployment = JSON.parse(File.read("#{deploy_dir}/deployment.json"))
2625
+ rescue JSON::ParserError => e
2626
+ MU.log "JSON parse failed on #{deploy_dir}/deployment.json", MU::ERR
2627
+ end
2628
+
2629
+ deploy.flock(File::LOCK_UN)
2630
+ deploy.close
2631
+ if set_context_to_me
2632
+ ["appname", "environment", "timestamp", "seed", "handle"].each { |var|
2633
+ if @deployment[var]
2634
+ if var != "handle"
2635
+ MU.setVar(var, @deployment[var].upcase)
2636
+ else
2637
+ MU.setVar(var, @deployment[var])
2638
+ end
2639
+ else
2640
+ MU.log "Missing global variable #{var} for #{MU.deploy_id}", MU::ERR
2641
+ end
2642
+ }
2643
+ end
2644
+ @timestamp = @deployment['timestamp']
2645
+ @seed = @deployment['seed']
2646
+ @appname = @deployment['appname']
2647
+ @handle = @deployment['handle']
2648
+
2649
+ return if deployment_json_only
2650
+ end
2651
+ if File.exist?(deploy_dir+"/private_key")
2652
+ @private_key = File.read("#{deploy_dir}/private_key")
2653
+ @public_key = File.read("#{deploy_dir}/public_key")
2654
+ end
2655
+ if File.exist?(deploy_dir+"/basket_of_kittens.json")
2656
+ begin
2657
+ @original_config = JSON.parse(File.read("#{deploy_dir}/basket_of_kittens.json"))
2658
+ rescue JSON::ParserError => e
2659
+ MU.log "JSON parse failed on #{deploy_dir}/basket_of_kittens.json", MU::ERR
2660
+ end
2661
+ end
2662
+ if File.exist?(deploy_dir+"/ssh_key_name")
2663
+ @ssh_key_name = File.read("#{deploy_dir}/ssh_key_name").chomp!
2664
+ end
2665
+ if File.exist?(deploy_dir+"/node_ssh.key")
2666
+ @ssh_private_key = File.read("#{deploy_dir}/node_ssh.key")
2667
+ end
2668
+ if File.exist?(deploy_dir+"/node_ssh.pub")
2669
+ @ssh_public_key = File.read("#{deploy_dir}/node_ssh.pub")
2670
+ end
2671
+ if File.exist?(deploy_dir+"/environment_name")
2672
+ @environment = File.read("#{deploy_dir}/environment_name").chomp!
2673
+ end
2674
+ if File.exist?(deploy_dir+"/deploy_secret")
2675
+ @deploy_secret = File.read("#{deploy_dir}/deploy_secret")
2676
+ end
2677
+ if Dir.exist?("#{deploy_dir}/secrets")
2678
+ @secrets.each_key { |type|
2679
+ Dir.glob("#{deploy_dir}/secrets/#{type}.*") { |filename|
2680
+ base, server = File.basename(filename).split(/\./)
2681
+
2682
+ @secrets[type][server] = File.read(filename).chomp!
2683
+ }
2684
+ }
2685
+ end
2686
+ }
2687
+ end
2688
+
2689
+ @catadjs = %w{fuzzy ginger lilac chocolate xanthic wiggly itty}
2690
+ @catnouns = %w{bastet biscuits bobcat catnip cheetah chonk dot felix jaguar kitty leopard lion lynx maru mittens moggy neko nip ocelot panther patches paws phoebe purr queen roar saber sekhmet skogkatt socks sphinx spot tail tiger tom whiskers wildcat yowl floof beans ailurophile dander dewclaw grimalkin kibble quick tuft misty simba mew quat eek ziggy}
2691
+ @catmixed = %w{abyssinian angora bengal birman bobtail bombay burmese calico chartreux cheshire cornish-rex curl devon egyptian-mau feline furever fumbs havana himilayan japanese-bobtail javanese khao-manee maine-coon manx marmalade mau munchkin norwegian pallas persian peterbald polydactyl ragdoll russian-blue savannah scottish-fold serengeti shorthair siamese siberian singapura snowshoe stray tabby tonkinese tortoiseshell turkish-van tuxedo uncia caterwaul lilac-point chocolate-point mackerel maltese knead whitenose vorpal}
2692
+ @catwords = @catadjs + @catnouns + @catmixed
2693
+
2694
+ @jaegeradjs = %w{azure fearless lucky olive vivid electric grey yarely violet ivory jade cinnamon crimson tacit umber mammoth ultra iron zodiac}
2695
+ @jaegernouns = %w{horizon hulk ultimatum yardarm watchman whilrwind wright rhythm ocean enigma eruption typhoon jaeger brawler blaze vandal excalibur paladin juliet kaleidoscope romeo}
2696
+ @jaegermixed = %w{alpha ajax amber avenger brave bravo charlie chocolate chrome corinthian dancer danger dash delta duet echo edge elite eureka foxtrot guardian gold hyperion illusion imperative india intercept kilo lancer night nova november oscar omega pacer quickstrike rogue ronin striker tango titan valor victor vulcan warder xenomorph xenon xray xylem yankee yell yukon zeal zero zoner zodiac}
2697
+ @jaegerwords = @jaegeradjs + @jaegernouns + @jaegermixed
2698
+
2699
+ @words = @catwords + @jaegerwords
2700
+
2701
+ end #class
2702
+ end #module
2703
+