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,2000 @@
1
+ # Copyright:: Copyright (c) 2014 eGlobalTech, Inc., all rights reserved
2
+ #
3
+ # Licensed under the BSD-3 license (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License in the root of the project or at
6
+ #
7
+ # http://egt-labs.com/mu/LICENSE.html
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'rubygems'
16
+ require 'json'
17
+ require 'erb'
18
+ require 'pp'
19
+ require 'json-schema'
20
+ require 'net/http'
21
+ autoload :GraphViz, 'graphviz'
22
+ autoload :ChronicDuration, 'chronic_duration'
23
+
24
+ module MU
25
+
26
+ # Methods and structures for parsing Mu's configuration files. See also {MU::Config::BasketofKittens}.
27
+ class Config
28
+ # Exception class for BoK parse or validation errors
29
+ class ValidationError < MU::MuError
30
+ end
31
+ # Exception class for deploy parameter (mu-deploy -p foo=bar) errors
32
+ class DeployParamError < MuError
33
+ end
34
+
35
+ # The default cloud provider for new resources. Must exist in MU.supportedClouds
36
+ def self.defaultCloud
37
+ begin
38
+ MU.myCloud
39
+ rescue NoMethodError
40
+ "AWS"
41
+ end
42
+ if MU::Cloud::Google.hosted
43
+ "Google"
44
+ elsif MU::Cloud::AWS.hosted
45
+ "AWS"
46
+ end
47
+ end
48
+
49
+ # The default grooming agent for new resources. Must exist in MU.supportedGroomers.
50
+ def self.defaultGroomer
51
+ "Chef"
52
+ end
53
+
54
+ attr_accessor :nat_routes
55
+ attr_reader :skipinitialupdates
56
+
57
+ attr_reader :google_images
58
+ @@google_images = YAML.load(File.read("#{MU.myRoot}/modules/mu/defaults/google_images.yaml"))
59
+ if File.exists?("#{MU.etcDir}/google_images.yaml")
60
+ custom = YAML.load(File.read("#{MU.etcDir}/google_images.yaml"))
61
+ @@google_images.merge!(custom) { |key, oldval, newval|
62
+ if !oldval.is_a?(Hash) and !newval.nil?
63
+ if !newval.nil?
64
+ newval
65
+ else
66
+ oldval
67
+ end
68
+ else
69
+ oldval.merge(newval)
70
+ end
71
+ }
72
+ end
73
+ # The list of known Google Images which we can use for a given platform
74
+ def self.google_images
75
+ @@google_images
76
+ end
77
+
78
+ attr_reader :amazon_images
79
+ @@amazon_images = YAML.load(File.read("#{MU.myRoot}/modules/mu/defaults/amazon_images.yaml"))
80
+ if File.exists?("#{MU.etcDir}/amazon_images.yaml")
81
+ custom = YAML.load(File.read("#{MU.etcDir}/amazon_images.yaml"))
82
+ @@amazon_images.merge!(custom) { |key, oldval, newval|
83
+ if !oldval.is_a?(Hash) and !newval.nil?
84
+ if !newval.nil?
85
+ newval
86
+ else
87
+ oldval
88
+ end
89
+ else
90
+ oldval.merge(newval)
91
+ end
92
+ }
93
+ end
94
+ # The list of known Amazon AMIs, by region, which we can use for a given
95
+ # platform.
96
+ def self.amazon_images
97
+ @@amazon_images
98
+ end
99
+
100
+ @@config_path = nil
101
+ # The path to the most recently loaded configuration file
102
+ attr_reader :config_path
103
+ # The path to the most recently loaded configuration file
104
+ def self.config_path
105
+ @@config_path
106
+ end
107
+
108
+ # Accessor for our Basket of Kittens schema definition
109
+ def self.schema
110
+ @@schema
111
+ end
112
+
113
+ # Deep merge a configuration hash so we can meld different cloud providers'
114
+ # schemas together, while preserving documentation differences
115
+ def self.schemaMerge(orig, new, cloud)
116
+ if new.is_a?(Hash)
117
+ new.each_pair { |k, v|
118
+ if orig and orig.has_key?(k)
119
+ schemaMerge(orig[k], new[k], cloud)
120
+ elsif orig
121
+ orig[k] = new[k]
122
+ else
123
+ orig = new
124
+ end
125
+ }
126
+ elsif orig.is_a?(Array) and new
127
+ orig.concat(new)
128
+ orig.uniq!
129
+ elsif new.is_a?(String)
130
+ orig ||= ""
131
+ orig += "\n#{cloud.upcase}: "+new
132
+ else
133
+ # XXX I think this is a NOOP?
134
+ end
135
+ end
136
+
137
+ # Accessor for our Basket of Kittens schema definition, with various
138
+ # cloud-specific details merged so we can generate documentation for them.
139
+ def self.docSchema
140
+ docschema = Marshal.load(Marshal.dump(@@schema))
141
+ only_children = {}
142
+ MU::Cloud.resource_types.each_pair { |classname, attrs|
143
+ MU::Cloud.supportedClouds.each { |cloud|
144
+ begin
145
+ require "mu/clouds/#{cloud.downcase}/#{attrs[:cfg_name]}"
146
+ rescue LoadError => e
147
+ next
148
+ end
149
+ res_class = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(classname)
150
+ required, res_schema = res_class.schema(self)
151
+ res_schema.each { |key, cfg|
152
+ if !docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]
153
+ only_children[attrs[:cfg_plural]] ||= {}
154
+ only_children[attrs[:cfg_plural]][key] ||= {}
155
+ only_children[attrs[:cfg_plural]][key][cloud] = cfg
156
+ end
157
+ }
158
+ }
159
+ }
160
+
161
+ # recursively chase down description fields in arrays and objects of our
162
+ # schema and prepend stuff to them for documentation
163
+ def self.prepend_descriptions(prefix, cfg)
164
+ # cfg["description"] ||= ""
165
+ # cfg["description"] = prefix+cfg["description"]
166
+ cfg["prefix"] = prefix
167
+ if cfg["type"] == "array" and cfg["items"]
168
+ cfg["items"] = prepend_descriptions(prefix, cfg["items"])
169
+ elsif cfg["type"] == "object" and cfg["properties"]
170
+ cfg["properties"].each_pair { |key, subcfg|
171
+ cfg["properties"][key] = prepend_descriptions(prefix, cfg["properties"][key])
172
+ }
173
+ end
174
+ cfg
175
+ end
176
+
177
+ MU::Cloud.resource_types.each_pair { |classname, attrs|
178
+ MU::Cloud.supportedClouds.each { |cloud|
179
+ res_class = nil
180
+ begin
181
+ res_class = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(classname)
182
+ rescue MU::Cloud::MuCloudResourceNotImplemented
183
+ next
184
+ end
185
+ required, res_schema = res_class.schema(self)
186
+ next if required.size == 0 and res_schema.size == 0
187
+ res_schema.each { |key, cfg|
188
+ cfg["description"] ||= ""
189
+ cfg["description"] = "+"+cloud.upcase+"+: "+cfg["description"]
190
+ if docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]
191
+ schemaMerge(docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key], cfg, cloud)
192
+ docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]["description"] ||= ""
193
+ docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]["description"] += "\n"+cfg["description"]
194
+ MU.log "Munging #{cloud}-specific #{classname.to_s} schema into BasketofKittens => #{attrs[:cfg_plural]} => #{key}", MU::DEBUG, details: docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]
195
+ else
196
+ if only_children[attrs[:cfg_plural]][key]
197
+ prefix = only_children[attrs[:cfg_plural]][key].keys.map{ |x| x.upcase }.join(" & ")+" ONLY"
198
+ cfg = prepend_descriptions(prefix, cfg)
199
+ end
200
+
201
+ docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key] = cfg
202
+ end
203
+ docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]["clouds"] = {}
204
+ docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]["clouds"][cloud] = cfg
205
+ }
206
+
207
+ docschema['required'].concat(required)
208
+ docschema['required'].uniq!
209
+ }
210
+ }
211
+
212
+ docschema
213
+ end
214
+
215
+ attr_reader :config
216
+
217
+ @@parameters = {}
218
+ @@user_supplied_parameters = {}
219
+ attr_reader :parameters
220
+ # Accessor for parameters to our Basket of Kittens
221
+ def self.parameters
222
+ @@parameters
223
+ end
224
+ @@tails = {}
225
+ attr_reader :tails
226
+ # Accessor for tails in our Basket of Kittens. This should be a superset of
227
+ # user-supplied parameters. It also has machine-generated parameterized
228
+ # behaviors.
229
+ def self.tails
230
+ @@tails
231
+ end
232
+
233
+ # Run through a config hash and return a version with all
234
+ # {MU::Config::Tail} endpoints converted to plain strings. Useful for cloud
235
+ # layers that don't care about the metadata in Tails.
236
+ # @param config [Hash]: The configuration tree to convert
237
+ # @return [Hash]: The modified configuration
238
+ def self.manxify(config)
239
+ if config.is_a?(Hash)
240
+ config.each_pair { |key, val|
241
+ config[key] = self.manxify(val)
242
+ }
243
+ elsif config.is_a?(Array)
244
+ config.each { |val|
245
+ val = self.manxify(val)
246
+ }
247
+ elsif config.is_a?(MU::Config::Tail)
248
+ return config.to_s
249
+ end
250
+ return config
251
+ end
252
+
253
+ # A wrapper for config leaves that came from ERB parameters instead of raw
254
+ # YAML or JSON. Will behave like a string for things that expect that
255
+ # sort of thing. Code that needs to know that this leaf was the result of
256
+ # a parameter will be able to tell by the object class being something
257
+ # other than a plain string, array, or hash.
258
+ class Tail
259
+ @value = nil
260
+ @name = nil
261
+ @prettyname = nil
262
+ @description = nil
263
+ @prefix = ""
264
+ @suffix = ""
265
+ @is_list_element = false
266
+ @pseudo = false
267
+ @runtimecode = nil
268
+ @valid_values = []
269
+ @index = 0
270
+ attr_reader :description
271
+ attr_reader :pseudo
272
+ attr_reader :index
273
+ attr_reader :value
274
+ attr_reader :runtimecode
275
+ attr_reader :valid_values
276
+ attr_reader :is_list_element
277
+
278
+ def initialize(name, value, prettyname = nil, cloudtype = "String", valid_values = [], description = "", is_list_element = false, prefix: "", suffix: "", pseudo: false, runtimecode: nil, index: 0)
279
+ @name = name
280
+ @value = value
281
+ @valid_values = valid_values
282
+ @pseudo = pseudo
283
+ @index = index
284
+ @runtimecode = runtimecode
285
+ @cloudtype = cloudtype
286
+ @is_list_element = is_list_element
287
+ @description ||=
288
+ if !description.nil?
289
+ description
290
+ else
291
+ ""
292
+ end
293
+ @prettyname ||=
294
+ if !prettyname.nil?
295
+ prettyname
296
+ else
297
+ @name.capitalize.gsub(/[^a-z0-9]/i, "")
298
+ end
299
+ @prefix = prefix if !prefix.nil?
300
+ @suffix = suffix if !suffix.nil?
301
+ end
302
+
303
+ # Return the parameter name of this Tail
304
+ def getName
305
+ @name
306
+ end
307
+ # Return the platform-specific cloud type of this Tail
308
+ def getCloudType
309
+ @cloudtype
310
+ end
311
+ # Return the human-friendly name of this Tail
312
+ def getPrettyName
313
+ @prettyname
314
+ end
315
+ # Walk like a String
316
+ def to_s
317
+ @prefix+@value+@suffix
318
+ end
319
+ # Quack like a String
320
+ def to_str
321
+ to_s
322
+ end
323
+ # Upcase like a String
324
+ def upcase
325
+ to_s.upcase
326
+ end
327
+ # Downcase like a String
328
+ def downcase
329
+ to_s.downcase
330
+ end
331
+ # Check for emptiness like a String
332
+ def empty?
333
+ to_s.empty?
334
+ end
335
+ # Match like a String
336
+ def match(*args)
337
+ to_s.match(*args)
338
+ end
339
+ # Check for equality like a String
340
+ def ==(o)
341
+ (o.class == self.class or o.class == "String") && o.to_s == to_s
342
+ end
343
+ # Perform global substitutions like a String
344
+ def gsub(*args)
345
+ to_s.gsub(*args)
346
+ end
347
+ end
348
+
349
+ # Wrapper method for creating a {MU::Config::Tail} object as a reference to
350
+ # a parameter that's valid in the loaded configuration.
351
+ # @param param [<String>]: The name of the parameter to which this should be tied.
352
+ # @param value [<String>]: The value of the parameter to return when asked
353
+ # @param prettyname [<String>]: A human-friendly parameter name to be used when generating CloudFormation templates and the like
354
+ # @param cloudtype [<String>]: A platform-specific identifier used by cloud layers to identify a parameter's type, e.g. AWS::EC2::VPC::Id
355
+ # @param valid_values [Array<String>]: A list of acceptable String values for the given parameter.
356
+ # @param description [<String>]: A long-form description of what the parameter does.
357
+ # @param list_of [<String>]: Indicates that the value should be treated as a member of a list (array) by the cloud layer.
358
+ # @param prefix [<String>]: A static String that should be prefixed to the stored value when queried
359
+ # @param suffix [<String>]: A static String that should be appended to the stored value when queried
360
+ # @param pseudo [<Boolean>]: This is a pseudo-parameter, automatically provided, and not available as user input.
361
+ # @param runtimecode [<String>]: Actual code to allow the cloud layer to interpret literally in its own idiom, e.g. '"Ref" : "AWS::StackName"' for CloudFormation
362
+ def getTail(param, value: nil, prettyname: nil, cloudtype: "String", valid_values: [], description: nil, list_of: nil, prefix: "", suffix: "", pseudo: false, runtimecode: nil)
363
+ if value.nil?
364
+ if @@parameters.nil? or !@@parameters.has_key?(param)
365
+ MU.log "Parameter '#{param}' (#{param.class.name}) referenced in config but not provided (#{caller[0]})", MU::DEBUG, details: @@parameters
366
+ return nil
367
+ # raise DeployParamError
368
+ else
369
+ value = @@parameters[param]
370
+ end
371
+ end
372
+ if !prettyname.nil?
373
+ prettyname.gsub!(/[^a-z0-9]/i, "") # comply with CloudFormation restrictions
374
+ end
375
+ if value.is_a?(MU::Config::Tail)
376
+ MU.log "Parameter #{param} is using a nested parameter as a value. This rarely works, depending on the target cloud. YMMV.", MU::WARN
377
+ tail = MU::Config::Tail.new(param, value, prettyname, cloudtype, valid_values, description, prefix: prefix, suffix: suffix, pseudo: pseudo, runtimecode: runtimecode)
378
+ elsif !list_of.nil? or (@@tails.has_key?(param) and @@tails[param].is_a?(Array))
379
+ tail = []
380
+ count = 0
381
+ value.split(/\s*,\s*/).each { |subval|
382
+ if @@tails.has_key?(param) and !@@tails[param][count].nil?
383
+ subval = @@tails[param][count].values.first.to_s if subval.nil?
384
+ list_of = @@tails[param][count].values.first.getName if list_of.nil?
385
+ prettyname = @@tails[param][count].values.first.getPrettyName if prettyname.nil?
386
+ description = @@tails[param][count].values.first.description if description.nil?
387
+ valid_values = @@tails[param][count].values.first.valid_values if valid_values.nil? or valid_values.empty?
388
+ cloudtype = @@tails[param][count].values.first.getCloudType if @@tails[param][count].values.first.getCloudType != "String"
389
+ end
390
+ prettyname = param.capitalize if prettyname.nil?
391
+ tail << { list_of => MU::Config::Tail.new(list_of, subval, prettyname, cloudtype, valid_values, description, true, pseudo: pseudo, index: count) }
392
+ count = count + 1
393
+ }
394
+ else
395
+ if @@tails.has_key?(param)
396
+ pseudo = @@tails[param].pseudo
397
+ value = @@tails[param].to_s if value.nil?
398
+ prettyname = @@tails[param].getPrettyName if prettyname.nil?
399
+ description = @@tails[param].description if description.nil?
400
+ valid_values = @@tails[param].valid_values if valid_values.nil? or valid_values.empty?
401
+ cloudtype = @@tails[param].getCloudType if @@tails[param].getCloudType != "String"
402
+ end
403
+ tail = MU::Config::Tail.new(param, value, prettyname, cloudtype, valid_values, description, prefix: prefix, suffix: suffix, pseudo: pseudo, runtimecode: runtimecode)
404
+ end
405
+
406
+ if valid_values and valid_values.size > 0 and value
407
+ if !valid_values.include?(value)
408
+ raise DeployParamError, "Invalid parameter value '#{value}' supplied for '#{param}'"
409
+ end
410
+ end
411
+ @@tails[param] = tail
412
+
413
+ tail
414
+ end
415
+
416
+ # Load up our YAML or JSON and parse it through ERB, optionally substituting
417
+ # externally-supplied parameters.
418
+ def resolveConfig(path: @@config_path, param_pass: false)
419
+ config = nil
420
+ @param_pass = param_pass
421
+
422
+ # Catch calls to missing variables in Basket of Kittens files when being
423
+ # parsed by ERB, and replace with placeholders for parameters. This
424
+ # method_missing is only defined innside {MU::Config.resolveConfig}
425
+ def method_missing(var_name)
426
+ if @param_pass
427
+ "MU::Config.getTail PLACEHOLDER #{var_name} REDLOHECALP"
428
+ else
429
+ tail = getTail(var_name.to_s)
430
+ if tail.is_a?(Array)
431
+ if @param_pass
432
+ return tail.map {|f| f.values.first.to_s }.join(",")
433
+ else
434
+ # Don't try to jam complex types into a string file format, just
435
+ # sub them back in later from a placeholder.
436
+ return "MU::Config.getTail PLACEHOLDER #{var_name} REDLOHECALP"
437
+ end
438
+ else
439
+ return "MU::Config.getTail PLACEHOLDER #{var_name} REDLOHECALP"
440
+ end
441
+ end
442
+ end
443
+
444
+ # A check for the existence of a user-supplied parameter value that can
445
+ # be easily run in an ERB block in a Basket of Kittens.
446
+ def parameter?(var_name)
447
+ @@user_supplied_parameters.has_key?(var_name)
448
+ end
449
+
450
+ # Instead of resolving a parameter, leave a placeholder for a
451
+ # cloud-specific variable that will be generated at runtime. Canonical
452
+ # use case: referring to a CloudFormation variable by reference, like
453
+ # "AWS::StackName" or "SomeChildTemplate.OutputVariableName."
454
+ # @param code [String]: A string consistent of code which will be understood by the Cloud layer, e.g. '"Ref" : "AWS::StackName"' (CloudFormation)
455
+ # @param placeholder [Object]: A placeholder value to use at the config parser stage, if the default string will not pass validation.
456
+ def cloudCode(code, placeholder = "CLOUDCODEPLACEHOLDER")
457
+ var_name = code.gsub(/[^a-z0-9]/i, "_")
458
+ placeholder = code if placeholder.nil?
459
+ getTail(var_name, value: placeholder, runtimecode: code)
460
+ "MU::Config.getTail PLACEHOLDER #{var_name} REDLOHECALP"
461
+ end
462
+
463
+ # Figure out what kind of file we're loading. We handle includes
464
+ # differently if YAML is involved. These globals get used inside
465
+ # templates. They're globals on purpose. Stop whining.
466
+ $file_format = MU::Config.guessFormat(path)
467
+ $yaml_refs = {}
468
+ erb = ERB.new(File.read(path), nil, "<>")
469
+ raw_text = erb.result(get_binding)
470
+ raw_json = nil
471
+
472
+ # If we're working in YAML, do some magic to make includes work better.
473
+ yaml_parse_error = nil
474
+ if $file_format == :yaml
475
+ begin
476
+ raw_json = JSON.generate(YAML.load(MU::Config.resolveYAMLAnchors(raw_text)))
477
+ rescue Psych::SyntaxError => e
478
+ raw_json = raw_text
479
+ yaml_parse_error = e.message
480
+ end
481
+ else
482
+ raw_json = raw_text
483
+ end
484
+
485
+ begin
486
+ config = JSON.parse(raw_json)
487
+ if param_pass
488
+ config.keys.each { |key|
489
+ if key != "parameters"
490
+ if key == "appname" and @@parameters["myAppName"].nil?
491
+ $myAppName = config["appname"].upcase.dup
492
+ $myAppName.freeze
493
+ @@parameters["myAppName"] = getTail("myAppName", value: config["appname"].upcase, pseudo: true).to_s
494
+ end
495
+ config.delete(key)
496
+ end
497
+ }
498
+ else
499
+ config.delete("parameters")
500
+ end
501
+ rescue JSON::ParserError => e
502
+ badconf = File.new("/tmp/badconf.#{$$}", File::CREAT|File::TRUNC|File::RDWR, 0400)
503
+ badconf.puts raw_text
504
+ badconf.close
505
+ if !yaml_parse_error.nil? and !path.match(/\.json/)
506
+ MU.log "YAML Error parsing #{path}! Complete file dumped to /tmp/badconf.#{$$}", MU::ERR, details: yaml_parse_error
507
+ else
508
+ MU.log "JSON Error parsing #{path}! Complete file dumped to /tmp/badconf.#{$$}", MU::ERR, details: e.message
509
+ end
510
+ raise ValidationError
511
+ end
512
+
513
+ undef :method_missing
514
+ return [MU::Config.fixDashes(config), raw_text]
515
+ end
516
+
517
+ attr_reader :kittens
518
+ attr_reader :updating
519
+ attr_reader :kittencfg_semaphore
520
+
521
+ # Load, resolve, and validate a configuration file ("Basket of Kittens").
522
+ # @param path [String]: The path to the master config file to load. Note that this can include other configuration files via ERB.
523
+ # @param skipinitialupdates [Boolean]: Whether to forcibly apply the *skipinitialupdates* flag to nodes created by this configuration.
524
+ # @param params [Hash]: Optional name-value parameter pairs, which will be passed to our configuration files as ERB variables.
525
+ # @return [Hash]: The complete validated configuration for a deployment.
526
+ def initialize(path, skipinitialupdates = false, params: params = Hash.new, updating: nil)
527
+ $myPublicIp = MU::Cloud::AWS.getAWSMetaData("public-ipv4")
528
+ $myRoot = MU.myRoot
529
+ $myRoot.freeze
530
+
531
+ $myAZ = MU.myAZ.freeze
532
+ $myAZ.freeze
533
+ $myRegion = MU.curRegion.freeze
534
+ $myRegion.freeze
535
+
536
+ $myAppName = nil
537
+
538
+ @kittens = {}
539
+ @kittencfg_semaphore = Mutex.new
540
+ @@config_path = path
541
+ @admin_firewall_rules = []
542
+ @skipinitialupdates = skipinitialupdates
543
+ @updating = updating
544
+
545
+ ok = true
546
+ params.each_pair { |name, value|
547
+ begin
548
+ raise DeployParamError, "Parameter must be formatted as name=value" if value.nil? or value.empty?
549
+ raise DeployParamError, "Parameter name must be a legal Ruby variable name" if name.match(/[^A-Za-z0-9_]/)
550
+ raise DeployParamError, "Parameter values cannot contain quotes" if value.match(/["']/)
551
+ eval("defined? $#{name} and raise DeployParamError, 'Parameter name reserved'")
552
+ @@parameters[name] = value
553
+ @@user_supplied_parameters[name] = value
554
+ eval("$#{name}='#{value}'") # support old-style $global parameter refs
555
+ MU.log "Passing variable $#{name} into #{@@config_path} with value '#{value}'"
556
+ rescue RuntimeError, SyntaxError => e
557
+ ok = false
558
+ MU.log "Error setting $#{name}='#{value}': #{e.message}", MU::ERR
559
+ end
560
+ }
561
+ raise ValidationError if !ok
562
+
563
+ # Run our input through the ERB renderer, a first pass just to extract
564
+ # the parameters section so that we can resolve all of those to variables
565
+ # for the rest of the config to reference.
566
+ # XXX Figure out how to make include() add parameters for us. Right now
567
+ # you can't specify parameters in an included file, because ERB is what's
568
+ # doing the including, and parameters need to already be resolved so that
569
+ # ERB can use them.
570
+ param_cfg, raw_erb_params_only = resolveConfig(path: @@config_path, param_pass: true)
571
+ if param_cfg.has_key?("parameters")
572
+ param_cfg["parameters"].each { |param|
573
+ if param.has_key?("default") and param["default"].nil?
574
+ param["default"] = ""
575
+ end
576
+ }
577
+ end
578
+
579
+ # Set up special Tail objects for our automatic pseudo-parameters
580
+ getTail("myPublicIp", value: $myPublicIp, pseudo: true)
581
+ getTail("myRoot", value: $myRoot, pseudo: true)
582
+ getTail("myAZ", value: $myAZ, pseudo: true)
583
+ getTail("myRegion", value: $myRegion, pseudo: true)
584
+
585
+ if param_cfg.has_key?("parameters") and !param_cfg["parameters"].nil? and param_cfg["parameters"].size > 0
586
+ param_cfg["parameters"].each { |param|
587
+ param['valid_values'] ||= []
588
+ if !@@parameters.has_key?(param['name'])
589
+ if param.has_key?("default")
590
+ @@parameters[param['name']] = param['default'].nil? ? "" : param['default']
591
+ elsif param["required"] or !param.has_key?("required")
592
+ MU.log "Required parameter '#{param['name']}' not supplied", MU::ERR
593
+ ok = false
594
+ end
595
+ if param.has_key?("cloudtype")
596
+ getTail(param['name'], value: @@parameters[param['name']], cloudtype: param["cloudtype"], valid_values: param['valid_values'], description: param['description'], prettyname: param['prettyname'], list_of: param['list_of'])
597
+ else
598
+ getTail(param['name'], value: @@parameters[param['name']], valid_values: param['valid_values'], description: param['description'], prettyname: param['prettyname'], list_of: param['list_of'])
599
+ end
600
+ end
601
+ }
602
+ end
603
+ raise ValidationError if !ok
604
+ @@parameters.each_pair { |name, val|
605
+ next if @@tails.has_key?(name) and @@tails[name].is_a?(MU::Config::Tail) and @@tails[name].pseudo
606
+ # Parameters can have limited parameterization of their own
607
+ if @@tails[name].to_s.match(/^(.*?)MU::Config.getTail PLACEHOLDER (.+?) REDLOHECALP(.*)/)
608
+ @@tails[name] = getTail(name, value: @@tails[$2])
609
+ end
610
+
611
+ if respond_to?(name.to_sym)
612
+ MU.log "Parameter name '#{name}' reserved", MU::ERR
613
+ ok = false
614
+ next
615
+ end
616
+ MU.log "Passing variable '#{name}' into #{path} with value '#{val}'"
617
+ }
618
+ raise DeployParamError, "One or more invalid parameters specified" if !ok
619
+ $parameters = @@parameters
620
+ $parameters.freeze
621
+
622
+ tmp_cfg, raw_erb = resolveConfig(path: @@config_path)
623
+
624
+ # Convert parameter entries that constitute whole config keys into
625
+ # MU::Config::Tail objects.
626
+ def resolveTails(tree, indent= "")
627
+ if tree.is_a?(Hash)
628
+ tree.each_pair { |key, val|
629
+ tree[key] = resolveTails(val, indent+" ")
630
+ }
631
+ elsif tree.is_a?(Array)
632
+ newtree = []
633
+ tree.each { |item|
634
+ newtree << resolveTails(item, indent+" ")
635
+ }
636
+ tree = newtree
637
+ elsif tree.is_a?(String) and tree.match(/^(.*?)MU::Config.getTail PLACEHOLDER (.+?) REDLOHECALP(.*)/)
638
+ tree = getTail($2, prefix: $1, suffix: $3)
639
+ if tree.nil? and @@tails.has_key?($2) # XXX why necessary?
640
+ tree = @@tails[$2]
641
+ end
642
+ end
643
+ return tree
644
+ end
645
+ @config = resolveTails(tmp_cfg)
646
+ @config.merge!(param_cfg)
647
+
648
+ if !@config.has_key?('admins') or @config['admins'].size == 0
649
+ @config['admins'] = [
650
+ {
651
+ "name" => MU.chef_user == "mu" ? "Mu Administrator" : MU.userName,
652
+ "email" => MU.userEmail
653
+ }
654
+ ]
655
+ end
656
+ MU::Config.set_defaults(@config, MU::Config.schema)
657
+ validate # individual resources validate when added now, necessary because the schema can change depending on what cloud they're targeting
658
+ # XXX but now we're not validating top-level keys, argh
659
+ #pp @config
660
+ #raise "DERP"
661
+ return @config.freeze
662
+ end
663
+
664
+ # Output the dependencies of this BoK stack as a directed acyclic graph.
665
+ # Very useful for debugging.
666
+ def visualizeDependencies
667
+ # GraphViz won't like MU::Config::Tail, pare down to plain Strings
668
+ config = MU::Config.manxify(Marshal.load(Marshal.dump(@config)))
669
+ begin
670
+ g = GraphViz.new(:G, :type => :digraph)
671
+ # Generate a GraphViz node for each resource in this stack
672
+ nodes = {}
673
+ MU::Cloud.resource_types.each_pair { |classname, attrs|
674
+ nodes[attrs[:cfg_name]] = {}
675
+ if config.has_key?(attrs[:cfg_plural]) and config[attrs[:cfg_plural]]
676
+ config[attrs[:cfg_plural]].each { |resource|
677
+ nodes[attrs[:cfg_name]][resource['name']] = g.add_nodes("#{classname}: #{resource['name']}")
678
+ }
679
+ end
680
+ }
681
+ # Now add edges corresponding to the dependencies they list
682
+ MU::Cloud.resource_types.each_pair { |classname, attrs|
683
+ if config.has_key?(attrs[:cfg_plural]) and config[attrs[:cfg_plural]]
684
+ config[attrs[:cfg_plural]].each { |resource|
685
+ if resource.has_key?("dependencies")
686
+ me = nodes[attrs[:cfg_name]][resource['name']]
687
+ resource["dependencies"].each { |dep|
688
+ parent = nodes[dep['type']][dep['name']]
689
+ g.add_edges(me, parent)
690
+ }
691
+ end
692
+ }
693
+ end
694
+ }
695
+ # Spew some output?
696
+ MU.log "Emitting dependency graph as /tmp/#{config['appname']}.jpg", MU::NOTICE
697
+ g.output(:jpg => "/tmp/#{config['appname']}.jpg")
698
+ rescue Exception => e
699
+ MU.log "Failed to generate GraphViz dependency tree: #{e.inspect}. This should only matter to developers.", MU::WARN, details: e.backtrace
700
+ end
701
+ end
702
+
703
+ # Take the schema we've defined and create a dummy Ruby class tree out of
704
+ # it, basically so we can leverage Yard to document it.
705
+ def self.emitSchemaAsRuby
706
+ kittenpath = "#{MU.myRoot}/modules/mu/kittens.rb"
707
+ MU.log "Converting Basket of Kittens schema to Ruby objects in #{kittenpath}"
708
+ dummy_kitten_class = File.new(kittenpath, File::CREAT|File::TRUNC|File::RDWR, 0644)
709
+ dummy_kitten_class.puts "### THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT ###"
710
+ dummy_kitten_class.puts ""
711
+ dummy_kitten_class.puts "module MU"
712
+ dummy_kitten_class.puts "class Config"
713
+ dummy_kitten_class.puts "\t# The configuration file format for Mu application stacks."
714
+ self.printSchema(dummy_kitten_class, ["BasketofKittens"], MU::Config.docSchema)
715
+ dummy_kitten_class.puts "end"
716
+ dummy_kitten_class.puts "end"
717
+ dummy_kitten_class.close
718
+
719
+ end
720
+
721
+ # Take an IP block and split it into a more-or-less arbitrary number of
722
+ # subnets.
723
+ # @param ip_block [String]: CIDR of the network to subdivide
724
+ # @param subnets_desired [Integer]: Number of subnets we want back
725
+ # @param max_mask [Integer]: The highest netmask we're allowed to use for a subnet (various by cloud provider)
726
+ # @return [MU::Config::Tail]: Resulting subnet tails, or nil if an error occurred.
727
+ def divideNetwork(ip_block, subnets_desired, max_mask = 28)
728
+ cidr = NetAddr::IPv4Net.parse(ip_block.to_s)
729
+
730
+ # Ugly but reliable method of landing on the right subnet size
731
+ subnet_bits = cidr.netmask.prefix_len
732
+ begin
733
+ subnet_bits += 1
734
+
735
+ if subnet_bits > max_mask
736
+ MU.log "Can't subdivide #{cidr.to_s} into #{subnets_desired.to_s}", MU::ERR
737
+ raise MuError, "Subnets smaller than /#{max_mask} not permitted"
738
+ end
739
+ end while cidr.subnet_count(subnet_bits) < subnets_desired
740
+
741
+ if cidr.subnet_count(subnet_bits) > subnets_desired
742
+ MU.log "Requested #{subnets_desired.to_s} subnets from #{cidr.to_s}, leaving #{(cidr.subnet_count(subnet_bits)-subnets_desired).to_s} unused /#{subnet_bits.to_s}s available", MU::NOTICE
743
+ end
744
+
745
+ begin
746
+ subnets = []
747
+ (0..subnets_desired).each { |x|
748
+ subnets << cidr.nth_subnet(subnet_bits, x).to_s
749
+ }
750
+ rescue RuntimeError => e
751
+ if e.message.match(/exceeds subnets available for allocation/)
752
+ MU.log e.message, MU::ERR
753
+ MU.log "I'm attempting to create #{subnets_desired} subnets (one public and one private for each Availability Zone), of #{subnet_size} addresses each, but that's too many for a /#{cidr.netmask.prefix_len} network. Either declare a larger network, or explicitly declare a list of subnets with few enough entries to fit.", MU::ERR
754
+ return nil
755
+ else
756
+ raise e
757
+ end
758
+ end
759
+
760
+ subnets = getTail("subnetblocks", value: subnets.join(","), cloudtype: "CommaDelimitedList", description: "IP Address ranges to be used for VPC subnets", prettyname: "SubnetIpBlocks", list_of: "ip_block").map { |tail| tail["ip_block"] }
761
+ subnets
762
+ end
763
+
764
+ # See if a given resource is configured in the current stack
765
+ # @param name [String]: The name of the resource being checked
766
+ # @param type [String]: The type of resource being checked
767
+ # @return [Boolean]
768
+ def haveLitterMate?(name, type)
769
+ @kittencfg_semaphore.synchronize {
770
+ shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
771
+ @kittens[cfg_plural].each { |kitten|
772
+ return kitten if kitten['name'] == name.to_s
773
+ }
774
+ }
775
+ false
776
+ end
777
+
778
+ # Remove a resource from the current stack
779
+ # @param name [String]: The name of the resource being removed
780
+ # @param type [String]: The type of resource being removed
781
+ def removeKitten(name, type)
782
+ @kittencfg_semaphore.synchronize {
783
+ shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
784
+ deletia = nil
785
+ @kittens[cfg_plural].each { |kitten|
786
+ if kitten['name'] == name
787
+ deletia = kitten
788
+ break
789
+ end
790
+ }
791
+ @kittens[type].delete(deletia) if !deletia.nil?
792
+ }
793
+ end
794
+
795
+ # FirewallRules can reference other FirewallRules, which means we need to do
796
+ # an extra pass to make sure we get all intra-stack dependencies correct.
797
+ # @param acl [Hash]: The configuration hash for the FirewallRule to check
798
+ # @return [Hash]
799
+ def resolveIntraStackFirewallRefs(acl)
800
+ acl["rules"].each { |acl_include|
801
+ if acl_include['sgs']
802
+ acl_include['sgs'].each { |sg_ref|
803
+ if haveLitterMate?(sg_ref, "firewall_rules")
804
+ acl["dependencies"] ||= []
805
+ found = false
806
+ acl["dependencies"].each { |dep|
807
+ if dep["type"] == "firewall_rule" and dep["name"] == sg_ref
808
+ dep["no_create_wait"] = true
809
+ found = true
810
+ end
811
+ }
812
+ if !found
813
+ acl["dependencies"] << {
814
+ "type" => "firewall_rule",
815
+ "name" => sg_ref,
816
+ "no_create_wait" => true
817
+ }
818
+ end
819
+ siblingfw = haveLitterMate?(sg_ref, "firewall_rules")
820
+ if !siblingfw["#MU_VALIDATED"]
821
+ # XXX raise failure somehow
822
+ insertKitten(siblingfw, "firewall_rules")
823
+ end
824
+ end
825
+ }
826
+ end
827
+ }
828
+ acl
829
+ end
830
+
831
+ # Insert a resource into the current stack
832
+ # @param descriptor [Hash]: The configuration description, as from a Basket of Kittens
833
+ # @param type [String]: The type of resource being added
834
+ # @param delay_validation [Boolean]: Whether to hold off on calling the resource's validateConfig method
835
+ def insertKitten(descriptor, type, delay_validation = false)
836
+ append = false
837
+
838
+ @kittencfg_semaphore.synchronize {
839
+ append = !@kittens[type].include?(descriptor)
840
+
841
+ # Skip if this kitten has already been validated and appended
842
+ if !append and descriptor["#MU_VALIDATED"]
843
+ return true
844
+ end
845
+ }
846
+ ok = true
847
+
848
+ shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
849
+ descriptor["#MU_CLOUDCLASS"] = classname
850
+ inheritDefaults(descriptor, cfg_plural)
851
+ schemaclass = Object.const_get("MU").const_get("Config").const_get(shortclass)
852
+
853
+ if (descriptor["region"] and descriptor["region"].empty?) or
854
+ (descriptor['cloud'] == "Google" and ["firewall_rule", "vpc"].include?(cfg_name))
855
+ descriptor.delete("region")
856
+ end
857
+
858
+ # Make sure a sensible region has been targeted, if applicable
859
+ if descriptor["region"]
860
+ classobj = Object.const_get("MU").const_get("Cloud").const_get(descriptor["cloud"])
861
+ valid_regions = classobj.listRegions
862
+ if !valid_regions.include?(descriptor["region"])
863
+ MU.log "Known regions for cloud '#{descriptor['cloud']}' do not include '#{descriptor["region"]}'", MU::ERR, details: valid_regions
864
+ ok = false
865
+ end
866
+ end
867
+
868
+ # Does this resource go in a VPC?
869
+ if !descriptor["vpc"].nil? and !delay_validation
870
+ descriptor['vpc']['cloud'] = descriptor['cloud']
871
+ if descriptor['vpc']['region'].nil? and !descriptor['region'].nil? and !descriptor['region'].empty? and descriptor['vpc']['cloud'] != "Google"
872
+ descriptor['vpc']['region'] = descriptor['region']
873
+ end
874
+
875
+ # If we're using a VPC in this deploy, set it as a dependency
876
+ if !descriptor["vpc"]["vpc_name"].nil? and
877
+ haveLitterMate?(descriptor["vpc"]["vpc_name"], "vpcs") and
878
+ descriptor["vpc"]['deploy_id'].nil? and
879
+ descriptor["vpc"]['vpc_id'].nil?
880
+ descriptor["dependencies"] << {
881
+ "type" => "vpc",
882
+ "name" => descriptor["vpc"]["vpc_name"]
883
+ }
884
+
885
+ siblingvpc = haveLitterMate?(descriptor["vpc"]["vpc_name"], "vpcs")
886
+ # things that live in subnets need their VPCs to be fully
887
+ # resolved before we can proceed
888
+ if ["server", "server_pool", "loadbalancer", "database", "cache_cluster", "container_cluster", "storage_pool"].include?(cfg_name)
889
+ if !siblingvpc["#MU_VALIDATED"]
890
+ ok = false if !insertKitten(siblingvpc, "vpcs")
891
+ end
892
+ end
893
+ if !MU::Config::VPC.processReference(descriptor['vpc'],
894
+ cfg_plural,
895
+ shortclass.to_s+" '#{descriptor['name']}'",
896
+ self,
897
+ dflt_region: descriptor['region'],
898
+ is_sibling: true,
899
+ sibling_vpcs: @kittens['vpcs'])
900
+ ok = false
901
+ end
902
+
903
+ # If we're using a VPC from somewhere else, make sure the flippin'
904
+ # thing exists, and also fetch its id now so later search routines
905
+ # don't have to work so hard.
906
+ else
907
+ if !MU::Config::VPC.processReference(descriptor["vpc"], cfg_plural,
908
+ "#{shortclass} #{descriptor['name']}",
909
+ self,
910
+ dflt_region: descriptor['region'])
911
+ MU.log "insertKitten was called from #{caller[0]}", MU::ERR
912
+ ok = false
913
+ end
914
+ end
915
+ # Clean crud out of auto-created VPC declarations so they don't trip
916
+ # the schema validator when it's invoked later.
917
+ if !["server", "server_pool", "database"].include?(cfg_name)
918
+ descriptor['vpc'].delete("nat_ssh_user")
919
+ end
920
+ if descriptor['vpc']['cloud'] == "Google"
921
+ descriptor['vpc'].delete("region")
922
+ end
923
+ if ["firewall_rule", "function"].include?(cfg_name)
924
+ descriptor['vpc'].delete("subnet_pref")
925
+ end
926
+ end
927
+
928
+ # Does it have generic ingress rules?
929
+ fwname = cfg_name+descriptor['name']
930
+
931
+ if !haveLitterMate?(fwname, "firewall_rules") and
932
+ (descriptor['ingress_rules'] or
933
+ ["server", "server_pool", "database"].include?(cfg_name))
934
+ descriptor['ingress_rules'] ||= []
935
+
936
+ acl = {"name" => fwname, "rules" => descriptor['ingress_rules'], "region" => descriptor['region'] }
937
+ acl["vpc"] = descriptor['vpc'].dup if descriptor['vpc']
938
+ ["optional_tags", "tags", "cloud", "project"].each { |param|
939
+ acl[param] = descriptor[param] if descriptor[param]
940
+ }
941
+ descriptor["add_firewall_rules"] = [] if descriptor["add_firewall_rules"].nil?
942
+ descriptor["add_firewall_rules"] << {"rule_name" => fwname}
943
+ acl = resolveIntraStackFirewallRefs(acl)
944
+ ok = false if !insertKitten(acl, "firewall_rules")
945
+ end
946
+
947
+ # Does it declare association with any sibling LoadBalancers?
948
+ if !descriptor["loadbalancers"].nil?
949
+ descriptor["loadbalancers"].each { |lb|
950
+ if !lb["concurrent_load_balancer"].nil?
951
+ descriptor["dependencies"] << {
952
+ "type" => "loadbalancer",
953
+ "name" => lb["concurrent_load_balancer"]
954
+ }
955
+ end
956
+ }
957
+ end
958
+
959
+ # Does it want to know about Storage Pools?
960
+ if !descriptor["storage_pools"].nil?
961
+ descriptor["storage_pools"].each { |sp|
962
+ if sp["name"]
963
+ descriptor["dependencies"] << {
964
+ "type" => "storage_pool",
965
+ "name" => sp["name"]
966
+ }
967
+ end
968
+ }
969
+ end
970
+
971
+ # Does it declare association with first-class firewall_rules?
972
+ if !descriptor["add_firewall_rules"].nil?
973
+ descriptor["add_firewall_rules"].each { |acl_include|
974
+ if haveLitterMate?(acl_include["rule_name"], "firewall_rules")
975
+ descriptor["dependencies"] << {
976
+ "type" => "firewall_rule",
977
+ "name" => acl_include["rule_name"]
978
+ }
979
+ siblingfw = haveLitterMate?(acl_include["rule_name"], "firewall_rules")
980
+ if !siblingfw["#MU_VALIDATED"]
981
+ ok = false if !insertKitten(siblingfw, "firewall_rules")
982
+ end
983
+ elsif acl_include["rule_name"]
984
+ MU.log shortclass.to_s+" #{descriptor['name']} depends on FirewallRule #{acl_include["rule_name"]}, but no such rule declared.", MU::ERR
985
+ ok = false
986
+ end
987
+ }
988
+ end
989
+
990
+ # Does it declare some alarms?
991
+ if descriptor["alarms"] && !descriptor["alarms"].empty?
992
+ descriptor["alarms"].each { |alarm|
993
+ alarm["name"] = "#{cfg_name}-#{descriptor["name"]}-#{alarm["name"]}"
994
+ alarm['dimensions'] = [] if !alarm['dimensions']
995
+ alarm["#TARGETCLASS"] = cfg_name
996
+ alarm["#TARGETNAME"] = descriptor['name']
997
+ alarm['cloud'] = descriptor['cloud']
998
+
999
+ ok = false if !insertKitten(alarm, "alarms", true)
1000
+ }
1001
+ descriptor.delete("alarms")
1002
+ end
1003
+
1004
+ # Does it want to meld another deployment's resources into its metadata?
1005
+ if !descriptor["existing_deploys"].nil? and
1006
+ !descriptor["existing_deploys"].empty?
1007
+ descriptor["existing_deploys"].each { |ext_deploy|
1008
+ if ext_deploy["cloud_type"].nil?
1009
+ MU.log "You must provide a cloud_type", MU::ERR
1010
+ ok = false
1011
+ end
1012
+
1013
+ if ext_deploy["cloud_id"]
1014
+ found = MU::MommaCat.findStray(
1015
+ descriptor['cloud'],
1016
+ ext_deploy["cloud_type"],
1017
+ cloud_id: ext_deploy["cloud_id"],
1018
+ region: descriptor['region'],
1019
+ dummy_ok: false
1020
+ ).first
1021
+
1022
+ if found.nil?
1023
+ MU.log "Couldn't find existing #{ext_deploy["cloud_type"]} resource #{ext_deploy["cloud_id"]}", MU::ERR
1024
+ ok = false
1025
+ end
1026
+ elsif ext_deploy["mu_name"] && ext_deploy["deploy_id"]
1027
+ found = MU::MommaCat.findStray(
1028
+ descriptor['cloud'],
1029
+ ext_deploy["cloud_type"],
1030
+ deploy_id: ext_deploy["deploy_id"],
1031
+ mu_name: ext_deploy["mu_name"],
1032
+ region: descriptor['region'],
1033
+ dummy_ok: false
1034
+ ).first
1035
+
1036
+ if found.nil?
1037
+ MU.log "Couldn't find existing #{ext_deploy["cloud_type"]} resource - #{ext_deploy["mu_name"]} / #{ext_deploy["deploy_id"]}", MU::ERR
1038
+ ok = false
1039
+ end
1040
+ else
1041
+ MU.log "Trying to find existing deploy, but either the cloud_id is not valid or no mu_name and deploy_id where provided", MU::ERR
1042
+ ok = false
1043
+ end
1044
+ }
1045
+ end
1046
+
1047
+ if !delay_validation
1048
+ # Call the generic validation for the resource type, first and foremost
1049
+ # XXX this might have to be at the top of this insertKitten instead of
1050
+ # here
1051
+ ok = false if !schemaclass.validate(descriptor, self)
1052
+
1053
+ # Merge the cloud-specific JSON schema and validate against it
1054
+ myschema = Marshal.load(Marshal.dump(MU::Config.schema["properties"][cfg_plural]["items"]))
1055
+ more_required, more_schema = Object.const_get("MU").const_get("Cloud").const_get(descriptor["cloud"]).const_get(shortclass.to_s).schema(self)
1056
+
1057
+ if more_schema
1058
+ MU::Config.schemaMerge(myschema["properties"], more_schema, descriptor["cloud"])
1059
+ MU::Config.set_defaults(descriptor, myschema)
1060
+ end
1061
+ myschema["required"] ||= []
1062
+ myschema["required"].concat(more_required)
1063
+ myschema["required"].uniq!
1064
+ MU.log "Schema check on #{descriptor['cloud']} #{cfg_name} #{descriptor['name']}", MU::DEBUG, details: myschema
1065
+
1066
+ plain_cfg = MU::Config.manxify(Marshal.load(Marshal.dump(descriptor)))
1067
+ plain_cfg.delete("#MU_CLOUDCLASS")
1068
+ plain_cfg.delete("#TARGETCLASS")
1069
+ plain_cfg.delete("#TARGETNAME")
1070
+ plain_cfg.delete("parent_block") if cfg_plural == "vpcs"
1071
+ begin
1072
+ JSON::Validator.validate!(myschema, plain_cfg)
1073
+ rescue JSON::Schema::ValidationError => e
1074
+ pp plain_cfg
1075
+ # Use fully_validate to get the complete error list, save some time
1076
+ errors = JSON::Validator.fully_validate(myschema, plain_cfg)
1077
+ realerrors = []
1078
+ errors.each { |err|
1079
+ if !err.match(/The property '.+?' of type MU::Config::Tail did not match the following type:/)
1080
+ realerrors << err
1081
+ end
1082
+ }
1083
+ if realerrors.size > 0
1084
+ MU.log "Validation error on #{descriptor['cloud']} #{cfg_name} #{descriptor['name']} (insertKitten called from #{caller[1]} with delay_validation=#{delay_validation}) #{@@config_path}!\n"+realerrors.join("\n"), MU::ERR, details: descriptor
1085
+ raise ValidationError, "Validation error on #{descriptor['cloud']} #{cfg_name} #{descriptor['name']} #{@@config_path}!\n"+realerrors.join("\n")
1086
+ end
1087
+ end
1088
+
1089
+ # Run the cloud class's deeper validation, unless we've already failed
1090
+ # on stuff that will cause spurious alarms further in
1091
+ if ok
1092
+ parser = Object.const_get("MU").const_get("Cloud").const_get(descriptor["cloud"]).const_get(shortclass.to_s)
1093
+ plain_descriptor = MU::Config.manxify(Marshal.load(Marshal.dump(descriptor)))
1094
+ return false if !parser.validateConfig(plain_descriptor, self)
1095
+
1096
+ descriptor.merge!(plain_descriptor)
1097
+ descriptor['#MU_VALIDATED'] = true
1098
+ end
1099
+
1100
+ end
1101
+
1102
+ descriptor["dependencies"].uniq!
1103
+
1104
+ @kittencfg_semaphore.synchronize {
1105
+ @kittens[cfg_plural] << descriptor if append
1106
+ }
1107
+ ok
1108
+ end
1109
+
1110
+ allregions = []
1111
+ allregions.concat(MU::Cloud::AWS.listRegions) if MU::Cloud::AWS.myRegion
1112
+ allregions.concat(MU::Cloud::Google.listRegions) if MU::Cloud::Google.defaultProject
1113
+
1114
+ # Configuration chunk for choosing a provider region
1115
+ # @return [Hash]
1116
+ def self.region_primitive
1117
+ allregions = []
1118
+ allregions.concat(MU::Cloud::AWS.listRegions) if MU::Cloud::AWS.myRegion
1119
+ allregions.concat(MU::Cloud::Google.listRegions) if MU::Cloud::Google.defaultProject
1120
+ {
1121
+ "type" => "string",
1122
+ "enum" => allregions
1123
+ }
1124
+ end
1125
+
1126
+ # Configuration chunk for creating resource tags as an array of key/value
1127
+ # pairs.
1128
+ # @return [Hash]
1129
+ def self.optional_tags_primitive
1130
+ {
1131
+ "type" => "boolean",
1132
+ "description" => "Tag the resource with our optional tags (+MU-HANDLE+, +MU-MASTER-NAME+, +MU-OWNER+).",
1133
+ "default" => true
1134
+ }
1135
+ end
1136
+
1137
+ # Configuration chunk for creating resource tags as an array of key/value
1138
+ # pairs.
1139
+ # @return [Hash]
1140
+ def self.tags_primitive
1141
+ {
1142
+ "type" => "array",
1143
+ "minItems" => 1,
1144
+ "items" => {
1145
+ "description" => "Tags to apply to this resource. Will apply at the cloud provider level and in Chef, where applicable.",
1146
+ "type" => "object",
1147
+ "title" => "tags",
1148
+ "required" => ["key", "value"],
1149
+ "additionalProperties" => false,
1150
+ "properties" => {
1151
+ "key" => {
1152
+ "type" => "string",
1153
+ },
1154
+ "value" => {
1155
+ "type" => "string",
1156
+ }
1157
+ }
1158
+ }
1159
+ }
1160
+ end
1161
+
1162
+ # Configuration chunk for choosing a cloud provider
1163
+ # @return [Hash]
1164
+ def self.cloud_primitive
1165
+ {
1166
+ "type" => "string",
1167
+ "default" => MU::Config.defaultCloud,
1168
+ "enum" => MU::Cloud.supportedClouds
1169
+ }
1170
+ end
1171
+
1172
+ # Generate configuration for the general-pursose ADMIN firewall rulesets
1173
+ # (security groups in AWS). Note that these are unique to regions and
1174
+ # individual VPCs (as well as Classic, which is just a degenerate case of
1175
+ # a VPC for our purposes.
1176
+ # @param vpc [Hash]: A VPC reference as defined in our config schema. This originates with the calling resource, so we'll peel out just what we need (a name or cloud id of a VPC).
1177
+ # @param admin_ip [String]: Optional string of an extra IP address to allow blanket access to the calling resource.
1178
+ # @param cloud [String]: The parent resource's cloud plugin identifier
1179
+ # @param region [String]: Cloud provider region, if applicable.
1180
+ # @return [Hash<String>]: A dependency description that the calling resource can then add to itself.
1181
+ def adminFirewallRuleset(vpc: nil, admin_ip: nil, region: nil, cloud: nil)
1182
+ if !cloud or (cloud == "AWS" and !region)
1183
+ raise MuError, "Cannot call adminFirewallRuleset without specifying the parent's region and cloud provider"
1184
+ end
1185
+ hosts = Array.new
1186
+ hosts << "#{MU.my_public_ip}/32" if MU.my_public_ip
1187
+ hosts << "#{MU.my_private_ip}/32" if MU.my_private_ip
1188
+ hosts << "#{MU.mu_public_ip}/32" if MU.mu_public_ip
1189
+ hosts << "#{admin_ip}/32" if admin_ip
1190
+ hosts.uniq!
1191
+ name = "admin"
1192
+ realvpc = nil
1193
+
1194
+ if vpc
1195
+ realvpc = {}
1196
+ realvpc['vpc_id'] = vpc['vpc_id'] if !vpc['vpc_id'].nil?
1197
+ realvpc['vpc_name'] = vpc['vpc_name'] if !vpc['vpc_name'].nil?
1198
+ realvpc['deploy_id'] = vpc['deploy_id'] if !vpc['deploy_id'].nil?
1199
+ if !realvpc['vpc_id'].nil? and !realvpc['vpc_id'].empty?
1200
+ # Stupid kludge for Google cloud_ids which are sometimes URLs and
1201
+ # sometimes not. Requirements are inconsistent from scenario to
1202
+ # scenario.
1203
+ name = name + "-" + realvpc['vpc_id'].gsub(/.*\//, "")
1204
+ realvpc['vpc_id'] = getTail("vpc_id", value: realvpc['vpc_id'], prettyname: "Admin Firewall Ruleset #{name} Target VPC", cloudtype: "AWS::EC2::VPC::Id") if realvpc["vpc_id"].is_a?(String)
1205
+ elsif !realvpc['vpc_name'].nil?
1206
+ name = name + "-" + realvpc['vpc_name']
1207
+ end
1208
+ end
1209
+
1210
+ hosts.uniq!
1211
+
1212
+ rules = []
1213
+ if cloud == "Google"
1214
+ rules = [
1215
+ { "ingress" => true, "proto" => "all", "hosts" => hosts },
1216
+ { "egress" => true, "proto" => "all", "hosts" => hosts }
1217
+ ]
1218
+ else
1219
+ rules = [
1220
+ { "proto" => "tcp", "port_range" => "0-65535", "hosts" => hosts },
1221
+ { "proto" => "udp", "port_range" => "0-65535", "hosts" => hosts },
1222
+ { "proto" => "icmp", "port_range" => "-1", "hosts" => hosts }
1223
+ ]
1224
+ end
1225
+
1226
+ acl = {"name" => name, "rules" => rules, "vpc" => realvpc, "cloud" => cloud, "admin" => true}
1227
+ acl.delete("vpc") if !acl["vpc"]
1228
+ acl["region"] == region if !region.nil? and !region.empty?
1229
+ @admin_firewall_rules << acl if !@admin_firewall_rules.include?(acl)
1230
+ return {"type" => "firewall_rule", "name" => name}
1231
+ end
1232
+
1233
+ private
1234
+
1235
+ def self.resolveYAMLAnchors(lines)
1236
+ new_text = ""
1237
+ lines.each_line { |line|
1238
+ if line.match(/# MU::Config\.include PLACEHOLDER /)
1239
+ $yaml_refs.each_pair { |anchor, data|
1240
+ if line.sub!(/^(\s+).*?# MU::Config\.include PLACEHOLDER #{Regexp.quote(anchor)} REDLOHECALP/, "")
1241
+ indent = $1
1242
+ MU::Config.resolveYAMLAnchors(data).each_line { |addline|
1243
+ line = line + indent + addline
1244
+ }
1245
+ break
1246
+ end
1247
+ }
1248
+ end
1249
+ new_text = new_text + line
1250
+ }
1251
+ return new_text
1252
+ end
1253
+
1254
+
1255
+ # Given a path to a config file, try to guess whether it's YAML or JSON.
1256
+ # @param path [String]: The path to the file to check.
1257
+ def self.guessFormat(path)
1258
+ raw = File.read(path)
1259
+ # Rip out ERB references that will bollocks parser syntax, first.
1260
+ stripped = raw.gsub(/<%.*?%>,?/, "").gsub(/,[\n\s]*([\]\}])/, '\1')
1261
+ begin
1262
+ JSON.parse(stripped)
1263
+ rescue JSON::ParserError => e
1264
+ begin
1265
+ YAML.load(raw.gsub(/<%.*?%>/, ""))
1266
+ rescue Psych::SyntaxError => e
1267
+ # Ok, well neither of those worked, let's assume that filenames are
1268
+ # meaningful.
1269
+ if path.match(/\.(yaml|yml)$/i)
1270
+ MU.log "Guessing that #{path} is YAML based on filename", MU::NOTICE
1271
+ return :yaml
1272
+ elsif path.match(/\.(json|jsn|js)$/i)
1273
+ MU.log "Guessing that #{path} is JSON based on filename", MU::NOTICE
1274
+ return :json
1275
+ else
1276
+ # For real? Ok, let's try the dumbest possible method.
1277
+ dashes = raw.match(/\-/)
1278
+ braces = raw.match(/[{}]/)
1279
+ if dashes.size > braces.size
1280
+ MU.log "Guessing that #{path} is YAML by... counting dashes.", MU::WARN
1281
+ return :yaml
1282
+ elsif braces.size > dashes.size
1283
+ MU.log "Guessing that #{path} is JSON by... counting braces.", MU::WARN
1284
+ return :json
1285
+ else
1286
+ raise "Unable to guess composition of #{path} by any means"
1287
+ end
1288
+ end
1289
+ end
1290
+ MU.log "Guessing that #{path} is YAML based on parser", MU::NOTICE
1291
+ return :yaml
1292
+ end
1293
+ MU.log "Guessing that #{path} is JSON based on parser", MU::NOTICE
1294
+ return :json
1295
+ end
1296
+
1297
+ # We used to be inconsistent about config keys using dashes versus
1298
+ # underscores. Now we've standardized on the latter. Be polite and
1299
+ # translate for older configs, since we're not fussed about name collisions.
1300
+ def self.fixDashes(conf)
1301
+ if conf.is_a?(Hash)
1302
+ newhash = Hash.new
1303
+ conf.each_pair { |key, val|
1304
+ if val.is_a?(Hash) or val.is_a?(Array)
1305
+ val = self.fixDashes(val)
1306
+ end
1307
+ if key.match(/-/)
1308
+ MU.log "Replacing #{key} with #{key.gsub(/-/, "_")}", MU::DEBUG
1309
+ newhash[key.gsub(/-/, "_")] = val
1310
+ else
1311
+ newhash[key] = val
1312
+ end
1313
+ }
1314
+ return newhash
1315
+ elsif conf.is_a?(Array)
1316
+ conf.map! { |val|
1317
+ if val.is_a?(Hash) or val.is_a?(Array)
1318
+ self.fixDashes(val)
1319
+ else
1320
+ val
1321
+ end
1322
+ }
1323
+ end
1324
+
1325
+ return conf
1326
+ end
1327
+
1328
+ @skipinitialupdates = false
1329
+
1330
+ # This can be called with ERB from within a stack config file, like so:
1331
+ # <%= Config.include("drupal.json") %>
1332
+ # It will first try the literal path you pass it, and if it fails to find
1333
+ # that it will look in the directory containing the main (top-level) config.
1334
+ def self.include(file, binding = nil, param_pass = false)
1335
+ loglevel = param_pass ? MU::NOTICE : MU::DEBUG
1336
+ retries = 0
1337
+ orig_filename = file
1338
+ assume_type = nil
1339
+ if file.match(/(js|json|jsn)$/i)
1340
+ assume_type = :json
1341
+ elsif file.match(/(yaml|yml)$/i)
1342
+ assume_type = :yaml
1343
+ end
1344
+ begin
1345
+ erb = ERB.new(File.read(file), nil, "<>")
1346
+ rescue Errno::ENOENT => e
1347
+ retries = retries + 1
1348
+ if retries == 1
1349
+ file = File.dirname(MU::Config.config_path)+"/"+orig_filename
1350
+ retry
1351
+ elsif retries == 2
1352
+ file = File.dirname(MU.myRoot)+"/lib/demo/"+orig_filename
1353
+ retry
1354
+ else
1355
+ raise ValidationError, "Couldn't read #{file} included from #{MU::Config.config_path}"
1356
+ end
1357
+ end
1358
+ begin
1359
+ # Include as just a drop-in block of text if the filename doesn't imply
1360
+ # a particular format, or if we're melding JSON into JSON.
1361
+ if ($file_format == :json and assume_type == :json) or assume_type.nil?
1362
+ MU.log "Including #{file} as uninterpreted text", loglevel
1363
+ return erb.result(binding)
1364
+ end
1365
+ # ...otherwise, try to parse into something useful so we can meld
1366
+ # differing file formats, or work around YAML's annoying dependence
1367
+ # on indentation.
1368
+ parsed_cfg = nil
1369
+ begin
1370
+ parsed_cfg = JSON.parse(erb.result(binding))
1371
+ parsed_as = :json
1372
+ rescue JSON::ParserError => e
1373
+ MU.log e.inspect, MU::DEBUG
1374
+ begin
1375
+ parsed_cfg = YAML.load(MU::Config.resolveYAMLAnchors(erb.result(binding)))
1376
+ parsed_as = :yaml
1377
+ rescue Psych::SyntaxError => e
1378
+ MU.log e.inspect, MU::DEBUG
1379
+ MU.log "#{file} parsed neither as JSON nor as YAML, including as raw text", MU::WARN if @param_pass
1380
+ return erb.result(binding)
1381
+ end
1382
+ end
1383
+ if $file_format == :json
1384
+ MU.log "Including #{file} as interpreted JSON", loglevel
1385
+ return JSON.generate(parsed_cfg)
1386
+ else
1387
+ MU.log "Including #{file} as interpreted YAML", loglevel
1388
+ $yaml_refs[file] = ""+YAML.dump(parsed_cfg).sub(/^---\n/, "")
1389
+ return "# MU::Config.include PLACEHOLDER #{file} REDLOHECALP"
1390
+ end
1391
+ rescue SyntaxError => e
1392
+ raise ValidationError, "ERB in #{file} threw a syntax error"
1393
+ end
1394
+ end
1395
+
1396
+ # (see #include)
1397
+ def include(file)
1398
+ MU::Config.include(file, get_binding, param_pass = @param_pass)
1399
+ end
1400
+
1401
+ # Namespace magic to pass to ERB's result method.
1402
+ def get_binding
1403
+ binding
1404
+ end
1405
+
1406
+ def self.set_defaults(conf_chunk = config, schema_chunk = schema, depth = 0, siblings = nil)
1407
+ return if schema_chunk.nil?
1408
+
1409
+ if conf_chunk != nil and schema_chunk["properties"].kind_of?(Hash) and conf_chunk.is_a?(Hash)
1410
+ if schema_chunk["properties"]["creation_style"].nil? or
1411
+ schema_chunk["properties"]["creation_style"] != "existing"
1412
+ schema_chunk["properties"].each_pair { |key, subschema|
1413
+ new_val = self.set_defaults(conf_chunk[key], subschema, depth+1, conf_chunk)
1414
+ conf_chunk[key] = new_val if new_val != nil
1415
+ }
1416
+ end
1417
+ elsif schema_chunk["type"] == "array" and conf_chunk.kind_of?(Array)
1418
+ conf_chunk.map! { |item|
1419
+ self.set_defaults(item, schema_chunk["items"], depth+1, conf_chunk)
1420
+ }
1421
+ else
1422
+ if conf_chunk.nil? and !schema_chunk["default_if"].nil? and !siblings.nil?
1423
+ schema_chunk["default_if"].each { |cond|
1424
+ if siblings[cond["key_is"]] == cond["value_is"]
1425
+ return cond["set"]
1426
+ end
1427
+ }
1428
+ end
1429
+ if conf_chunk.nil? and schema_chunk["default"] != nil
1430
+ return schema_chunk["default"]
1431
+ end
1432
+ end
1433
+ return conf_chunk
1434
+ end
1435
+
1436
+ # For our resources which specify intra-stack dependencies, make sure those
1437
+ # dependencies are actually declared.
1438
+ # TODO check for loops
1439
+ def self.check_dependencies(config)
1440
+ ok = true
1441
+ config.each { |type|
1442
+ if type.instance_of?(Array)
1443
+ type.each { |container|
1444
+ if container.instance_of?(Array)
1445
+ container.each { |resource|
1446
+ if resource.kind_of?(Hash) and resource["dependencies"] != nil
1447
+ resource["dependencies"].each { |dependency|
1448
+ collection = dependency["type"]+"s"
1449
+ found = false
1450
+ names_seen = []
1451
+ if config[collection] != nil
1452
+ config[collection].each { |service|
1453
+ names_seen << service["name"].to_s
1454
+ found = true if service["name"].to_s == dependency["name"].to_s
1455
+ }
1456
+ end
1457
+ if !found
1458
+ MU.log "Missing dependency: #{type[0]}{#{resource['name']}} needs #{collection}{#{dependency['name']}}", MU::ERR, details: names_seen
1459
+ ok = false
1460
+ end
1461
+ }
1462
+ end
1463
+ }
1464
+ end
1465
+ }
1466
+ end
1467
+ }
1468
+ return ok
1469
+ end
1470
+
1471
+
1472
+ # Verify that a server or server_pool has a valid AD config referencing
1473
+ # valid Vaults for credentials.
1474
+ def self.check_vault_refs(server)
1475
+ ok = true
1476
+ server['vault_access'] = [] if server['vault_access'].nil?
1477
+ server['groomer'] ||= "Chef"
1478
+ groomclass = MU::Groomer.loadGroomer(server['groomer'])
1479
+
1480
+ begin
1481
+ if !server['active_directory'].nil?
1482
+ ["domain_admin_vault", "domain_join_vault"].each { |vault_class|
1483
+ server['vault_access'] << {
1484
+ "vault" => server['active_directory'][vault_class]['vault'],
1485
+ "item" => server['active_directory'][vault_class]['item']
1486
+ }
1487
+ item = groomclass.getSecret(
1488
+ vault: server['active_directory'][vault_class]['vault'],
1489
+ item: server['active_directory'][vault_class]['item'],
1490
+ )
1491
+ ["username_field", "password_field"].each { |field|
1492
+ if !item.has_key?(server['active_directory'][vault_class][field])
1493
+ ok = false
1494
+ MU.log "I don't see a value named #{field} in Chef Vault #{server['active_directory'][vault_class]['vault']}:#{server['active_directory'][vault_class]['item']}", MU::ERR
1495
+ end
1496
+ }
1497
+ }
1498
+ end
1499
+
1500
+ if !server['windows_auth_vault'].nil?
1501
+ server['use_cloud_provider_windows_password'] = false
1502
+
1503
+ server['vault_access'] << {
1504
+ "vault" => server['windows_auth_vault']['vault'],
1505
+ "item" => server['windows_auth_vault']['item']
1506
+ }
1507
+ item = groomclass.getSecret(
1508
+ vault: server['windows_auth_vault']['vault'],
1509
+ item: server['windows_auth_vault']['item']
1510
+ )
1511
+ ["password_field", "ec2config_password_field", "sshd_password_field"].each { |field|
1512
+ if !item.has_key?(server['windows_auth_vault'][field])
1513
+ MU.log "No value named #{field} in Chef Vault #{server['windows_auth_vault']['vault']}:#{server['windows_auth_vault']['item']}, will use a generated password.", MU::NOTICE
1514
+ server['windows_auth_vault'].delete(field)
1515
+ end
1516
+ }
1517
+ end
1518
+ # Check all of the non-special ones while we're at it
1519
+ server['vault_access'].each { |v|
1520
+ next if v['vault'] == "splunk" and v['item'] == "admin_user"
1521
+ item = groomclass.getSecret(vault: v['vault'], item: v['item'])
1522
+ }
1523
+ rescue MuError
1524
+ MU.log "Can't load a Chef Vault I was configured to use. Does it exist?", MU::ERR
1525
+ ok = false
1526
+ end
1527
+ return ok
1528
+ end
1529
+
1530
+
1531
+ # Given a bare hash describing a resource, insert default values which can
1532
+ # be inherited from the current live parent configuration.
1533
+ # @param kitten [Hash]: A resource descriptor
1534
+ # @param type [String]: The type of resource this is ("servers" etc)
1535
+ def inheritDefaults(kitten, type)
1536
+ kitten['cloud'] ||= MU::Config.defaultCloud
1537
+
1538
+ schema_fields = ["region", "us_only", "scrub_mu_isms"]
1539
+ if kitten['cloud'] == "Google"
1540
+ kitten["project"] ||= MU::Cloud::Google.defaultProject
1541
+ schema_fields << "project"
1542
+ if kitten['region'].nil? and !kitten['#MU_CLOUDCLASS'].nil? and
1543
+ ![MU::Cloud::VPC, MU::Cloud::FirewallRule].include?(kitten['#MU_CLOUDCLASS'])
1544
+ if !$MU_CFG['google'] or !$MU_CFG['google']['region']
1545
+ raise ValidationError, "Google resource declared without a region, but no default Google region declared in mu.yaml"
1546
+ end
1547
+ kitten['region'] ||= $MU_CFG['google']['region']
1548
+ end
1549
+ else
1550
+ if !$MU_CFG['aws'] or !$MU_CFG['aws']['region']
1551
+ raise ValidationError, "AWS resource declared without a region, but no default AWS region declared in mu.yaml"
1552
+ end
1553
+ kitten['region'] ||= $MU_CFG['aws']['region']
1554
+ end
1555
+
1556
+ kitten['us_only'] ||= @config['us_only']
1557
+ kitten['us_only'] ||= false
1558
+
1559
+ kitten['scrub_mu_isms'] ||= @config['scrub_mu_isms']
1560
+ kitten['scrub_mu_isms'] ||= false
1561
+
1562
+ kitten["dependencies"] ||= []
1563
+
1564
+ # Make sure the schema knows about these "new" fields, so that validation
1565
+ # doesn't trip over them.
1566
+ schema_fields.each { |field|
1567
+ if @@schema["properties"][field]
1568
+ MU.log "Adding #{field} to schema for #{type} #{kitten['cloud']}", MU::DEBUG
1569
+ @@schema["properties"][type]["items"]["properties"][field] ||= @@schema["properties"][field]
1570
+ end
1571
+ }
1572
+ end
1573
+
1574
+ def validate(config = @config)
1575
+ ok = true
1576
+ plain_cfg = MU::Config.manxify(Marshal.load(Marshal.dump(config)))
1577
+
1578
+ count = 0
1579
+ @kittens ||= {}
1580
+ types = MU::Cloud.resource_types.values.map { |v| v[:cfg_plural] }
1581
+
1582
+ types.each { |type|
1583
+ @kittens[type] = config[type]
1584
+ @kittens[type] ||= []
1585
+ @kittens[type].each { |k|
1586
+ inheritDefaults(k, type)
1587
+ }
1588
+ count = count + @kittens[type].size
1589
+ }
1590
+
1591
+ if count == 0
1592
+ MU.log "You must declare at least one resource to create", MU::ERR
1593
+ ok = false
1594
+ end
1595
+
1596
+ @nat_routes ||= {}
1597
+ types.each { |type|
1598
+ @kittens[type].each { |descriptor|
1599
+ ok = false if !insertKitten(descriptor, type)
1600
+ }
1601
+ }
1602
+
1603
+ @kittens["firewall_rules"].each { |acl|
1604
+ acl = resolveIntraStackFirewallRefs(acl)
1605
+ }
1606
+
1607
+ # Make sure validation has been called for all on-the-fly generated
1608
+ # resources.
1609
+ types.each { |type|
1610
+ @kittens[type].each { |descriptor|
1611
+ if !descriptor["#MU_VALIDATED"]
1612
+ ok = false if !insertKitten(descriptor, type)
1613
+ end
1614
+ }
1615
+ }
1616
+
1617
+ # add some default holes to allow dependent instances into databases
1618
+ @kittens["databases"].each { |db|
1619
+ if db['port'].nil?
1620
+ db['port'] = 3306 if ["mysql", "aurora"].include?(db['engine'])
1621
+ db['port'] = 5432 if ["postgres"].include?(db['engine'])
1622
+ db['port'] = 1433 if db['engine'].match(/^sqlserver\-/)
1623
+ db['port'] = 1521 if db['engine'].match(/^oracle\-/)
1624
+ end
1625
+
1626
+ ruleset = haveLitterMate?("database"+db['name'], "firewall_rules")
1627
+ if ruleset
1628
+ ["server_pools", "servers"].each { |type|
1629
+ shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
1630
+ @kittens[cfg_plural].each { |server|
1631
+ server["dependencies"].each { |dep|
1632
+ if dep["type"] == "database" and dep["name"] == db["name"]
1633
+ # XXX this is AWS-specific, I think. We need to use source_tags to make this happen in Google. This logic probably needs to be dumped into the database layer.
1634
+ ruleset["rules"] << {
1635
+ "proto" => "tcp",
1636
+ "port" => db["port"],
1637
+ "sgs" => [cfg_name+server['name']]
1638
+ }
1639
+
1640
+ ruleset["dependencies"] << {
1641
+ "name" => cfg_name+server['name'],
1642
+ "type" => "firewall_rule",
1643
+ "no_create_wait" => true
1644
+ }
1645
+ end
1646
+ }
1647
+ }
1648
+ }
1649
+ end
1650
+ }
1651
+
1652
+ seen = []
1653
+ # XXX seem to be not detecting duplicate admin firewall_rules in adminFirewallRuleset
1654
+ @admin_firewall_rules.each { |acl|
1655
+ next if seen.include?(acl['name'])
1656
+ ok = false if !insertKitten(acl, "firewall_rules")
1657
+ seen << acl['name']
1658
+ }
1659
+ types.each { |type|
1660
+ config[type] = @kittens[type] if @kittens[type].size > 0
1661
+ }
1662
+ ok = false if !MU::Config.check_dependencies(config)
1663
+
1664
+ # TODO enforce uniqueness of resource names
1665
+ raise ValidationError if !ok
1666
+
1667
+ # XXX Does commenting this out make sense? Do we want to apply it to top-level
1668
+ # keys and ignore resources, which validate when insertKitten is called now?
1669
+ # begin
1670
+ # JSON::Validator.validate!(MU::Config.schema, plain_cfg)
1671
+ # rescue JSON::Schema::ValidationError => e
1672
+ # # Use fully_validate to get the complete error list, save some time
1673
+ # errors = JSON::Validator.fully_validate(MU::Config.schema, plain_cfg)
1674
+ # realerrors = []
1675
+ # errors.each { |err|
1676
+ # if !err.match(/The property '.+?' of type MU::Config::Tail did not match the following type:/)
1677
+ # realerrors << err
1678
+ # end
1679
+ # }
1680
+ # if realerrors.size > 0
1681
+ # raise ValidationError, "Validation error in #{@@config_path}!\n"+realerrors.join("\n")
1682
+ # end
1683
+ # end
1684
+ end
1685
+
1686
+
1687
+ # Emit our Basket of Kittesn schema in a format that YARD can comprehend
1688
+ # and turn into documentation.
1689
+ def self.printSchema(dummy_kitten_class, class_hierarchy, schema, in_array = false, required = false, prefix: nil)
1690
+ return if schema.nil?
1691
+ if schema["type"] == "object"
1692
+ printme = Array.new
1693
+ if !schema["properties"].nil?
1694
+ # order sub-elements by whether they're required, so we can use YARD's
1695
+ # grouping tags on them
1696
+ if !schema["required"].nil? and schema["required"].size > 0
1697
+ prop_list = schema["properties"].keys.sort_by { |name|
1698
+ schema["required"].include?(name) ? 0 : 1
1699
+ }
1700
+ else
1701
+ prop_list = schema["properties"].keys
1702
+ end
1703
+ req = false
1704
+ printme << "# @!group Optional parameters" if schema["required"].nil? or schema["required"].size == 0
1705
+ prop_list.each { |name|
1706
+ prop = schema["properties"][name]
1707
+ if !schema["required"].nil? and schema["required"].include?(name)
1708
+ printme << "# @!group Required parameters" if !req
1709
+ req = true
1710
+ else
1711
+ if req
1712
+ printme << "# @!endgroup"
1713
+ printme << "# @!group Optional parameters"
1714
+ end
1715
+ req = false
1716
+ end
1717
+
1718
+ printme << self.printSchema(dummy_kitten_class, class_hierarchy+ [name], prop, false, req, prefix: schema["prefix"])
1719
+ }
1720
+ printme << "# @!endgroup"
1721
+ end
1722
+
1723
+ tabs = 1
1724
+ class_hierarchy.each { |classname|
1725
+ if classname == class_hierarchy.last and !schema['description'].nil?
1726
+ dummy_kitten_class.puts ["\t"].cycle(tabs).to_a.join('') + "# #{schema['description']}\n"
1727
+ end
1728
+ dummy_kitten_class.puts ["\t"].cycle(tabs).to_a.join('') + "class #{classname}"
1729
+ tabs = tabs + 1
1730
+ }
1731
+ printme.each { |lines|
1732
+ if !lines.nil? and lines.is_a?(String)
1733
+ lines.lines.each { |line|
1734
+ dummy_kitten_class.puts ["\t"].cycle(tabs).to_a.join('') + line
1735
+ }
1736
+ end
1737
+ }
1738
+
1739
+ class_hierarchy.each { |classname|
1740
+ tabs = tabs - 1
1741
+ dummy_kitten_class.puts ["\t"].cycle(tabs).to_a.join('') + "end"
1742
+ }
1743
+
1744
+ # And now that we've dealt with our children, pass our own rendered
1745
+ # commentary back up to our caller.
1746
+ name = class_hierarchy.last
1747
+ if in_array
1748
+ type = "Array<#{class_hierarchy.join("::")}>"
1749
+ else
1750
+ type = class_hierarchy.join("::")
1751
+ end
1752
+
1753
+ docstring = "\n"
1754
+ docstring = docstring + "# **REQUIRED**\n" if required
1755
+ docstring = docstring + "# **"+schema["prefix"]+"**\n" if schema["prefix"]
1756
+ docstring = docstring + "# #{schema['description'].gsub(/\n/, "\n#")}\n" if !schema['description'].nil?
1757
+ docstring = docstring + "#\n"
1758
+ docstring = docstring + "# @return [#{type}]\n"
1759
+ docstring = docstring + "# @see #{class_hierarchy.join("::")}\n"
1760
+ docstring = docstring + "attr_accessor :#{name}"
1761
+ return docstring
1762
+
1763
+ elsif schema["type"] == "array"
1764
+ return self.printSchema(dummy_kitten_class, class_hierarchy, schema['items'], true, required, prefix: prefix)
1765
+ else
1766
+ name = class_hierarchy.last
1767
+ if schema['type'].nil?
1768
+ MU.log "Couldn't determine schema type in #{class_hierarchy.join(" => ")}", MU::WARN, details: schema
1769
+ return nil
1770
+ end
1771
+ if in_array
1772
+ type = "Array<#{schema['type'].capitalize}>"
1773
+ else
1774
+ type = schema['type'].capitalize
1775
+ end
1776
+ docstring = "\n"
1777
+
1778
+ prefixes = []
1779
+ prefixes << "# **REQUIRED**" if required and schema['default'].nil?
1780
+ prefixes << "# **"+schema["prefix"]+"**" if schema["prefix"]
1781
+ prefixes << "# **Default: `#{schema['default']}`**" if !schema['default'].nil?
1782
+ if !schema['enum'].nil?
1783
+ prefixes << "# **Must be one of: `#{schema['enum'].join(', ')}`**"
1784
+ elsif !schema['pattern'].nil?
1785
+ # XXX unquoted regex chars confuse the hell out of YARD. How do we
1786
+ # quote {}[] etc in YARD-speak?
1787
+ prefixes << "# **Must match pattern `#{schema['pattern'].gsub(/\n/, "\n#")}`**"
1788
+ end
1789
+
1790
+ if prefixes.size > 0
1791
+ docstring += prefixes.join(",\n")
1792
+ if schema['description'] and schema['description'].size > 1
1793
+ docstring += " - "
1794
+ end
1795
+ docstring += "\n"
1796
+ end
1797
+
1798
+ docstring = docstring + "# #{schema['description'].gsub(/\n/, "\n#")}\n" if !schema['description'].nil?
1799
+ docstring = docstring + "#\n"
1800
+ docstring = docstring + "# @return [#{type}]\n"
1801
+ docstring = docstring + "attr_accessor :#{name}"
1802
+
1803
+ return docstring
1804
+ end
1805
+
1806
+ return nil
1807
+ end
1808
+
1809
+ def self.dependencies_primitive
1810
+ {
1811
+ "type" => "array",
1812
+ "items" => {
1813
+ "type" => "object",
1814
+ "description" => "Declare other objects which this resource requires. This resource will wait until the others are available to create itself.",
1815
+ "required" => ["name", "type"],
1816
+ "additionalProperties" => false,
1817
+ "properties" => {
1818
+ "name" => {"type" => "string"},
1819
+ "type" => {
1820
+ "type" => "string",
1821
+ "enum" => MU::Cloud.resource_types.values.map { |v| v[:cfg_name] }
1822
+ },
1823
+ "phase" => {
1824
+ "type" => "string",
1825
+ "description" => "Which part of the creation process of the resource we depend on should we wait for before starting our own creation? Defaults are usually sensible, but sometimes you want, say, a Server to wait on another Server to be completely ready (through its groom phase) before starting up.",
1826
+ "enum" => ["create", "groom"]
1827
+ },
1828
+ "no_create_wait" => {
1829
+ "type" => "boolean",
1830
+ "default" => false,
1831
+ "description" => "By default, it's assumed that we want to wait on our parents' creation phase, in addition to whatever is declared in this stanza. Setting this flag will bypass waiting on our parent resource's creation, so that our create or groom phase can instead depend only on the parent's groom phase. "
1832
+ }
1833
+ }
1834
+ }
1835
+ }
1836
+ end
1837
+
1838
+ CIDR_PATTERN = "^\\d+\\.\\d+\\.\\d+\\.\\d+\/[0-9]{1,2}$"
1839
+ CIDR_DESCRIPTION = "CIDR-formatted IP block, e.g. 1.2.3.4/32"
1840
+ CIDR_PRIMITIVE = {
1841
+ "type" => "string",
1842
+ "pattern" => CIDR_PATTERN,
1843
+ "description" => CIDR_DESCRIPTION
1844
+ }
1845
+
1846
+ # Have a default value available for config schema elements that take an
1847
+ # email address.
1848
+ # @return [String]
1849
+ def self.notification_email
1850
+ if MU.chef_user == "mu"
1851
+ ENV['MU_ADMIN_EMAIL']
1852
+ else
1853
+ MU.userEmail
1854
+ end
1855
+ end
1856
+
1857
+ @@schema = {
1858
+ "$schema" => "http://json-schema.org/draft-04/schema#",
1859
+ "title" => "MU Application",
1860
+ "type" => "object",
1861
+ "description" => "A MU application stack, consisting of at least one resource.",
1862
+ "required" => ["admins", "appname"],
1863
+ "properties" => {
1864
+ "appname" => {
1865
+ "type" => "string",
1866
+ "description" => "A name for your application stack. Should be short, but easy to differentiate from other applications.",
1867
+ },
1868
+ "scrub_mu_isms" => {
1869
+ "type" => "boolean",
1870
+ "description" => "When 'cloud' is set to 'CloudFormation,' use this flag to strip out Mu-specific artifacts (tags, standard userdata, naming conventions, etc) to yield a clean, source-agnostic template. Setting this flag here will override declarations in individual resources."
1871
+ },
1872
+ "project" => {
1873
+ "type" => "string",
1874
+ "description" => "GOOGLE: The project into which to deploy resources",
1875
+ "default" => MU::Cloud::Google.defaultProject
1876
+ },
1877
+ "region" => MU::Config.region_primitive,
1878
+ "us_only" => {
1879
+ "type" => "boolean",
1880
+ "description" => "For resources which span regions, restrict to regions inside the United States",
1881
+ "default" => false
1882
+ },
1883
+ "conditions" => {
1884
+ "type" => "array",
1885
+ "items" => {
1886
+ "type" => "object",
1887
+ "required" => ["name", "cloudcode"],
1888
+ "description" => "CloudFormation-specific. Define Conditions as in http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html. Arguments must use the cloudCode() macro.",
1889
+ "properties" => {
1890
+ "name" => { "required" => true, "type" => "string" },
1891
+ "cloudcode" => { "required" => true, "type" => "string" },
1892
+ }
1893
+ }
1894
+ },
1895
+ "parameters" => {
1896
+ "type" => "array",
1897
+ "items" => {
1898
+ "type" => "object",
1899
+ "title" => "parameter",
1900
+ "description" => "Parameters to be substituted elsewhere in this Basket of Kittens as ERB variables (<%= varname %>)",
1901
+ "additionalProperties" => false,
1902
+ "properties" => {
1903
+ "name" => { "required" => true, "type" => "string" },
1904
+ "default" => { "type" => "string" },
1905
+ "list_of" => {
1906
+ "type" => "string",
1907
+ "description" => "Treat the value as a comma-separated list of values with this key name, equivalent to CloudFormation's various List<> types. For example, set to 'subnet_id' to pass values as an array of subnet identifiers as the 'subnets' argument of a VPC stanza."
1908
+ },
1909
+ "prettyname" => {
1910
+ "type" => "string",
1911
+ "description" => "An alternative name to use when generating parameter fields in, for example, CloudFormation templates"
1912
+ },
1913
+ "description" => {"type" => "string"},
1914
+ "cloudtype" => {
1915
+ "type" => "string",
1916
+ "description" => "A platform-specific string describing the type of validation to use for this parameter. E.g. when generating a CloudFormation template, set to AWS::EC2::Image::Id to validate input as an AMI identifier."
1917
+ },
1918
+ "required" => {
1919
+ "type" => "boolean",
1920
+ "default" => true
1921
+ },
1922
+ "valid_values" => {
1923
+ "type" => "array",
1924
+ "description" => "List of valid values for this parameter. Can only be a list of static strings, for now.",
1925
+ "items" => {
1926
+ "type" => "string"
1927
+ }
1928
+ }
1929
+ }
1930
+ }
1931
+ },
1932
+ # TODO availability zones (or an array thereof)
1933
+
1934
+ "admins" => {
1935
+ "type" => "array",
1936
+ "items" => {
1937
+ "type" => "object",
1938
+ "title" => "admin",
1939
+ "description" => "Administrative contacts for this application stack. Will be automatically set to invoking Mu user, if not specified.",
1940
+ "required" => ["name", "email"],
1941
+ "additionalProperties" => false,
1942
+ "properties" => {
1943
+ "name" => {"type" => "string"},
1944
+ "email" => {"type" => "string"},
1945
+ "public_key" => {
1946
+ "type" => "string",
1947
+ "description" => "An OpenSSH-style public key string. This will be installed on all instances created in this deployment."
1948
+ }
1949
+ }
1950
+ },
1951
+ "minItems" => 1,
1952
+ "uniqueItems" => true
1953
+ }
1954
+ },
1955
+ "additionalProperties" => false
1956
+ }
1957
+
1958
+ failed = []
1959
+
1960
+ # Load all of the config stub files at the Ruby level
1961
+ MU::Cloud.resource_types.each_pair { |type, cfg|
1962
+ begin
1963
+ require "mu/config/#{cfg[:cfg_name]}"
1964
+ rescue LoadError => e
1965
+ # raise MuError, "MU::Config implemention of #{type} missing from modules/mu/config/#{cfg[:cfg_name]}.rb"
1966
+ MU.log "MU::Config::#{type} stub class is missing", MU::ERR
1967
+ failed << type
1968
+ next
1969
+ end
1970
+ }
1971
+
1972
+ MU::Cloud.resource_types.each_pair { |type, cfg|
1973
+ begin
1974
+ schemaclass = Object.const_get("MU").const_get("Config").const_get(type)
1975
+ [:schema, :validate].each { |method|
1976
+ if !schemaclass.respond_to?(method)
1977
+ MU.log "MU::Config::#{type}.#{method.to_s} doesn't seem to be implemented", MU::ERR
1978
+ failed << type
1979
+ end
1980
+ }
1981
+ next if failed.include?(type)
1982
+ @@schema["properties"][cfg[:cfg_plural]] = {
1983
+ "type" => "array",
1984
+ "items" => schemaclass.schema
1985
+ }
1986
+ @@schema["properties"][cfg[:cfg_plural]]["items"]["properties"]["dependencies"] = MU::Config.dependencies_primitive
1987
+ @@schema["properties"][cfg[:cfg_plural]]["items"]["properties"]["cloud"] = MU::Config.cloud_primitive
1988
+ @@schema["properties"][cfg[:cfg_plural]]["items"]["title"] = type.to_s
1989
+ rescue NameError => e
1990
+ failed << type
1991
+ MU.log "Error loading #{type} schema from mu/config/#{cfg[:cfg_name]}", MU::ERR, details: "\t"+e.inspect+"\n\t"+e.backtrace[0]
1992
+ end
1993
+ }
1994
+ failed.uniq!
1995
+ if failed.size > 0
1996
+ raise MuError, "Resource type config loaders failed checks, aborting"
1997
+ end
1998
+
1999
+ end #class
2000
+ end #module