cloud-toaster 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (515) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +202 -0
  3. data/README.md +54 -0
  4. data/Rakefile +51 -0
  5. data/VERSION +1 -0
  6. data/bin/strace-4.8_patched/strace-i686 +0 -0
  7. data/bin/strace-4.8_patched/strace-x86_64 +0 -0
  8. data/bin/toaster +74 -0
  9. data/chef/cookbooks/chef-solo-search/CHANGELOG +16 -0
  10. data/chef/cookbooks/chef-solo-search/Gemfile +8 -0
  11. data/chef/cookbooks/chef-solo-search/LICENSE +202 -0
  12. data/chef/cookbooks/chef-solo-search/NOTICE +18 -0
  13. data/chef/cookbooks/chef-solo-search/README.md +143 -0
  14. data/chef/cookbooks/chef-solo-search/Rakefile +10 -0
  15. data/chef/cookbooks/chef-solo-search/libraries/search/overrides.rb +100 -0
  16. data/chef/cookbooks/chef-solo-search/libraries/search/parser.rb +222 -0
  17. data/chef/cookbooks/chef-solo-search/libraries/search.rb +75 -0
  18. data/chef/cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/lucene.treetop +150 -0
  19. data/chef/cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/lucene_nodes.rb +285 -0
  20. data/chef/cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/query_transform.rb +65 -0
  21. data/chef/cookbooks/chef-solo-search/metadata.rb +11 -0
  22. data/chef/cookbooks/chef-solo-search/recipes/default.rb +3 -0
  23. data/chef/cookbooks/chef-solo-search/tests/data/data_bags/node/alpha.json +10 -0
  24. data/chef/cookbooks/chef-solo-search/tests/data/data_bags/node/beta.json +10 -0
  25. data/chef/cookbooks/chef-solo-search/tests/data/data_bags/node/without_json_class.json +7 -0
  26. data/chef/cookbooks/chef-solo-search/tests/data/data_bags/users/jerry.json +8 -0
  27. data/chef/cookbooks/chef-solo-search/tests/data/data_bags/users/lea.json +10 -0
  28. data/chef/cookbooks/chef-solo-search/tests/data/data_bags/users/mike.json +13 -0
  29. data/chef/cookbooks/chef-solo-search/tests/data/data_bags/users/tom.json +10 -0
  30. data/chef/cookbooks/chef-solo-search/tests/gemfiles/Gemfile.10 +4 -0
  31. data/chef/cookbooks/chef-solo-search/tests/gemfiles/Gemfile.11 +5 -0
  32. data/chef/cookbooks/chef-solo-search/tests/test_data_bags.rb +45 -0
  33. data/chef/cookbooks/chef-solo-search/tests/test_search.rb +244 -0
  34. data/chef/cookbooks/lxc/attributes/create_lxc.rb +8 -0
  35. data/chef/cookbooks/lxc/attributes/general.rb +15 -0
  36. data/chef/cookbooks/lxc/attributes/init_bare_os.rb +15 -0
  37. data/chef/cookbooks/lxc/attributes/init_proto.rb +18 -0
  38. data/chef/cookbooks/lxc/attributes/install_ruby.rb +51 -0
  39. data/chef/cookbooks/lxc/attributes/mount_volume.rb +11 -0
  40. data/chef/cookbooks/lxc/attributes/setup_host.rb +9 -0
  41. data/chef/cookbooks/lxc/attributes/setup_proxy.rb +3 -0
  42. data/chef/cookbooks/lxc/attributes/start_lxc.rb +11 -0
  43. data/chef/cookbooks/lxc/attributes/stop_lxc.rb +3 -0
  44. data/chef/cookbooks/lxc/files/install.chef.sh +78 -0
  45. data/chef/cookbooks/lxc/metadata.json +6 -0
  46. data/chef/cookbooks/lxc/recipes/create_lxc.rb +106 -0
  47. data/chef/cookbooks/lxc/recipes/init_bare_os.rb +82 -0
  48. data/chef/cookbooks/lxc/recipes/init_proto.rb +417 -0
  49. data/chef/cookbooks/lxc/recipes/install_docker.rb +61 -0
  50. data/chef/cookbooks/lxc/recipes/install_ruby.rb +28 -0
  51. data/chef/cookbooks/lxc/recipes/mount_volume.rb +63 -0
  52. data/chef/cookbooks/lxc/recipes/setup_database.rb +22 -0
  53. data/chef/cookbooks/lxc/recipes/setup_host.rb +179 -0
  54. data/chef/cookbooks/lxc/recipes/setup_proxy.rb +157 -0
  55. data/chef/cookbooks/lxc/recipes/start_lxc.rb +180 -0
  56. data/chef/cookbooks/lxc/recipes/stop_lxc.rb +86 -0
  57. data/chef/cookbooks/mysql/CHANGELOG.md +391 -0
  58. data/chef/cookbooks/mysql/README.md +211 -0
  59. data/chef/cookbooks/mysql/attributes/default.rb +22 -0
  60. data/chef/cookbooks/mysql/libraries/helpers.rb +277 -0
  61. data/chef/cookbooks/mysql/libraries/matchers.rb +17 -0
  62. data/chef/cookbooks/mysql/libraries/provider_mysql_client.rb +10 -0
  63. data/chef/cookbooks/mysql/libraries/provider_mysql_client_debian.rb +37 -0
  64. data/chef/cookbooks/mysql/libraries/provider_mysql_client_fedora.rb +38 -0
  65. data/chef/cookbooks/mysql/libraries/provider_mysql_client_freebsd.rb +33 -0
  66. data/chef/cookbooks/mysql/libraries/provider_mysql_client_omnios.rb +41 -0
  67. data/chef/cookbooks/mysql/libraries/provider_mysql_client_rhel.rb +42 -0
  68. data/chef/cookbooks/mysql/libraries/provider_mysql_client_smartos.rb +33 -0
  69. data/chef/cookbooks/mysql/libraries/provider_mysql_client_suse.rb +37 -0
  70. data/chef/cookbooks/mysql/libraries/provider_mysql_client_ubuntu.rb +37 -0
  71. data/chef/cookbooks/mysql/libraries/provider_mysql_service.rb +10 -0
  72. data/chef/cookbooks/mysql/libraries/provider_mysql_service_debian.rb +193 -0
  73. data/chef/cookbooks/mysql/libraries/provider_mysql_service_fedora.rb +157 -0
  74. data/chef/cookbooks/mysql/libraries/provider_mysql_service_freebsd.rb +151 -0
  75. data/chef/cookbooks/mysql/libraries/provider_mysql_service_omnios.rb +232 -0
  76. data/chef/cookbooks/mysql/libraries/provider_mysql_service_rhel.rb +318 -0
  77. data/chef/cookbooks/mysql/libraries/provider_mysql_service_smartos.rb +216 -0
  78. data/chef/cookbooks/mysql/libraries/provider_mysql_service_suse.rb +170 -0
  79. data/chef/cookbooks/mysql/libraries/provider_mysql_service_ubuntu.rb +218 -0
  80. data/chef/cookbooks/mysql/libraries/resource_mysql_client.rb +11 -0
  81. data/chef/cookbooks/mysql/libraries/resource_mysql_service.rb +194 -0
  82. data/chef/cookbooks/mysql/metadata.json +40 -0
  83. data/chef/cookbooks/mysql/metadata.rb +20 -0
  84. data/chef/cookbooks/mysql/recipes/client.rb +22 -0
  85. data/chef/cookbooks/mysql/recipes/server.rb +33 -0
  86. data/chef/cookbooks/mysql/recipes/server_deprecated.rb +23 -0
  87. data/chef/cookbooks/mysql/templates/default/5.0/my.cnf.erb +38 -0
  88. data/chef/cookbooks/mysql/templates/default/5.1/my.cnf.erb +38 -0
  89. data/chef/cookbooks/mysql/templates/default/5.5/my.cnf.erb +38 -0
  90. data/chef/cookbooks/mysql/templates/default/5.6/my.cnf.erb +39 -0
  91. data/chef/cookbooks/mysql/templates/default/apparmor/usr.sbin.mysqld.erb +40 -0
  92. data/chef/cookbooks/mysql/templates/default/debian/debian.cnf.erb +12 -0
  93. data/chef/cookbooks/mysql/templates/default/debian/mysql-server.seed.erb +10 -0
  94. data/chef/cookbooks/mysql/templates/default/deprecated/my.cnf.erb +374 -0
  95. data/chef/cookbooks/mysql/templates/default/grants/grants.sql.erb +27 -0
  96. data/chef/cookbooks/mysql/templates/default/omnios/mysql.xml.erb +26 -0
  97. data/chef/cookbooks/mysql/templates/default/omnios/svc.method.mysqld.erb +29 -0
  98. data/chef/cookbooks/mysql/templates/default/smartos/mysql.xml.erb +32 -0
  99. data/chef/cookbooks/mysql/templates/default/smartos/svc.method.mysqld.erb +29 -0
  100. data/chef/cookbooks/openssl/README.md +37 -0
  101. data/chef/cookbooks/openssl/libraries/secure_password.rb +37 -0
  102. data/chef/cookbooks/openssl/metadata.json +30 -0
  103. data/chef/cookbooks/openssl/metadata.rb +8 -0
  104. data/chef/cookbooks/openssl/recipes/default.rb +19 -0
  105. data/chef/cookbooks/ssh/attributes/default.rb +8 -0
  106. data/chef/cookbooks/ssh/recipes/authorize_key.rb +20 -0
  107. data/chef/cookbooks/toaster/attributes/testing.rb +37 -0
  108. data/chef/cookbooks/toaster/libraries/default.rb +61 -0
  109. data/chef/cookbooks/toaster/recipes/post__cakephp__default.rb +27 -0
  110. data/chef/cookbooks/toaster/recipes/post__cube__default.rb +26 -0
  111. data/chef/cookbooks/toaster/recipes/post__drupal__default.rb +16 -0
  112. data/chef/cookbooks/toaster/recipes/post__icinga__default.rb +9 -0
  113. data/chef/cookbooks/toaster/recipes/post__node__default.rb +16 -0
  114. data/chef/cookbooks/toaster/recipes/post__virtualbox__default.rb +12 -0
  115. data/chef/cookbooks/toaster/recipes/post__wordpress__default.rb +12 -0
  116. data/chef/cookbooks/toaster/recipes/pre__aegir__default.rb +12 -0
  117. data/chef/cookbooks/toaster/recipes/pre__app__default.rb +3 -0
  118. data/chef/cookbooks/toaster/recipes/pre__cakephp__default.rb +24 -0
  119. data/chef/cookbooks/toaster/recipes/pre__cube__default.rb +9 -0
  120. data/chef/cookbooks/toaster/recipes/pre__disco__default.rb +21 -0
  121. data/chef/cookbooks/toaster/recipes/pre__drbd__default.rb +7 -0
  122. data/chef/cookbooks/toaster/recipes/pre__eaccelerator__default.rb +2 -0
  123. data/chef/cookbooks/toaster/recipes/pre__elasticsearch-head__default.rb +11 -0
  124. data/chef/cookbooks/toaster/recipes/pre__elasticsearch__default.rb +6 -0
  125. data/chef/cookbooks/toaster/recipes/pre__gitosis__default.rb +17 -0
  126. data/chef/cookbooks/toaster/recipes/pre__kafka__default.rb +3 -0
  127. data/chef/cookbooks/toaster/recipes/pre__munin__server.rb +6 -0
  128. data/chef/cookbooks/toaster/recipes/pre__mysql__server.rb +4 -0
  129. data/chef/cookbooks/toaster/recipes/pre__nagios__default.rb +3 -0
  130. data/chef/cookbooks/toaster/recipes/pre__netkernel__default.rb +24 -0
  131. data/chef/cookbooks/toaster/recipes/pre__php__default.rb +2 -0
  132. data/chef/cookbooks/toaster/recipes/pre__postgresql__server.rb +3 -0
  133. data/chef/cookbooks/toaster/recipes/pre__pxe_dust__default.rb +9 -0
  134. data/chef/cookbooks/toaster/recipes/pre__pxe_install_server__default.rb +6 -0
  135. data/chef/cookbooks/toaster/recipes/pre__riak__default.rb +40 -0
  136. data/chef/cookbooks/toaster/recipes/pre__sensu__default.rb +9 -0
  137. data/chef/cookbooks/toaster/recipes/pre__sol__default.rb +2 -0
  138. data/chef/cookbooks/toaster/recipes/pre__solr__default.rb +6 -0
  139. data/chef/cookbooks/toaster/recipes/pre__sonar__default.rb +2 -0
  140. data/chef/cookbooks/toaster/recipes/pre__storm__default.rb +3 -0
  141. data/chef/cookbooks/toaster/recipes/pre__vmware__tools.rb +4 -0
  142. data/chef/cookbooks/toaster/recipes/pre__wordpress__default.rb +17 -0
  143. data/chef/cookbooks/toaster/recipes/pre__xen__default.rb +7 -0
  144. data/chef/cookbooks/toaster/recipes/pre__znc__default.rb +7 -0
  145. data/chef/cookbooks/toaster/recipes/testing.rb +241 -0
  146. data/chef/cookbooks/toaster/recipes/testing_post.rb +36 -0
  147. data/chef/cookbooks/yum/CHANGELOG.md +212 -0
  148. data/chef/cookbooks/yum/README.md +268 -0
  149. data/chef/cookbooks/yum/attributes/main.rb +97 -0
  150. data/chef/cookbooks/yum/libraries/matchers.rb +27 -0
  151. data/chef/cookbooks/yum/metadata.json +34 -0
  152. data/chef/cookbooks/yum/metadata.rb +13 -0
  153. data/chef/cookbooks/yum/providers/globalconfig.rb +37 -0
  154. data/chef/cookbooks/yum/providers/repository.rb +85 -0
  155. data/chef/cookbooks/yum/recipes/default.rb +34 -0
  156. data/chef/cookbooks/yum/resources/globalconfig.rb +105 -0
  157. data/chef/cookbooks/yum/resources/repository.rb +63 -0
  158. data/chef/cookbooks/yum/templates/default/main.erb +251 -0
  159. data/chef/cookbooks/yum/templates/default/repo.erb +109 -0
  160. data/chef/cookbooks/yum-mysql-community/CHANGELOG.md +67 -0
  161. data/chef/cookbooks/yum-mysql-community/README.md +137 -0
  162. data/chef/cookbooks/yum-mysql-community/attributes/mysql-connectors-community.rb +31 -0
  163. data/chef/cookbooks/yum-mysql-community/attributes/mysql55-community.rb +29 -0
  164. data/chef/cookbooks/yum-mysql-community/attributes/mysql56-community.rb +31 -0
  165. data/chef/cookbooks/yum-mysql-community/files/default/mysql_pubkey.asc +33 -0
  166. data/chef/cookbooks/yum-mysql-community/metadata.json +30 -0
  167. data/chef/cookbooks/yum-mysql-community/metadata.rb +8 -0
  168. data/chef/cookbooks/yum-mysql-community/recipes/connectors.rb +48 -0
  169. data/chef/cookbooks/yum-mysql-community/recipes/mysql55.rb +48 -0
  170. data/chef/cookbooks/yum-mysql-community/recipes/mysql56.rb +48 -0
  171. data/config.json +34 -0
  172. data/lib/toaster/api.rb +447 -0
  173. data/lib/toaster/chef/chef_listener.rb +514 -0
  174. data/lib/toaster/chef/chef_node_inspector.rb +165 -0
  175. data/lib/toaster/chef/chef_util.rb +865 -0
  176. data/lib/toaster/chef/failsafe_resource_parser.rb +140 -0
  177. data/lib/toaster/chef/resource_inspector.rb +536 -0
  178. data/lib/toaster/db/cache.rb +98 -0
  179. data/lib/toaster/db/cached_db.rb +53 -0
  180. data/lib/toaster/db/cgi_session_cache.rb +209 -0
  181. data/lib/toaster/db/db.rb +98 -0
  182. data/lib/toaster/db/mysql.rb +18 -0
  183. data/lib/toaster/db/ram_cache.rb +203 -0
  184. data/lib/toaster/markup/jdom-1.1.jar +0 -0
  185. data/lib/toaster/markup/jdom-b7.jar +0 -0
  186. data/lib/toaster/markup/markup_util.rb +437 -0
  187. data/lib/toaster/markup/vmtools-utils-0.5.jar +0 -0
  188. data/lib/toaster/markup/xmldiff.sh +5 -0
  189. data/lib/toaster/model/additional_property.rb +11 -0
  190. data/lib/toaster/model/automation.rb +218 -0
  191. data/lib/toaster/model/automation_attribute.rb +13 -0
  192. data/lib/toaster/model/automation_run.rb +104 -0
  193. data/lib/toaster/model/ignore_property.rb +11 -0
  194. data/lib/toaster/model/key_value_pair.rb +59 -0
  195. data/lib/toaster/model/run_attribute.rb +17 -0
  196. data/lib/toaster/model/state.rb +17 -0
  197. data/lib/toaster/model/state_change.rb +79 -0
  198. data/lib/toaster/model/task.rb +292 -0
  199. data/lib/toaster/model/task_execution.rb +76 -0
  200. data/lib/toaster/model/task_parameter.rb +66 -0
  201. data/lib/toaster/model/user.rb +32 -0
  202. data/lib/toaster/ohai/_disabled_/network_ports.rb +81 -0
  203. data/lib/toaster/ohai/apache/apache.rb +48 -0
  204. data/lib/toaster/ohai/cron/cron.rb +32 -0
  205. data/lib/toaster/ohai/files/_meta.rb +30 -0
  206. data/lib/toaster/ohai/files/files.rb +20 -0
  207. data/lib/toaster/ohai/gems/gems.rb +19 -0
  208. data/lib/toaster/ohai/groups/groups.rb +20 -0
  209. data/lib/toaster/ohai/iptables/iptables.rb +48 -0
  210. data/lib/toaster/ohai/mounts/mounts.rb +26 -0
  211. data/lib/toaster/ohai/mysql/mysql.rb +31 -0
  212. data/lib/toaster/ohai/packages/_meta.rb +86 -0
  213. data/lib/toaster/ohai/packages/packages.rb +33 -0
  214. data/lib/toaster/ohai/ports/ports.rb +21 -0
  215. data/lib/toaster/ohai/routes/routes.rb +25 -0
  216. data/lib/toaster/ohai/services/services.rb +42 -0
  217. data/lib/toaster/ohai/users/users.rb +17 -0
  218. data/lib/toaster/state/convergence.rb +197 -0
  219. data/lib/toaster/state/idempotence.rb +192 -0
  220. data/lib/toaster/state/ptrace_util.rb +48 -0
  221. data/lib/toaster/state/state_node.rb +105 -0
  222. data/lib/toaster/state/state_transition.rb +38 -0
  223. data/lib/toaster/state/state_transition_graph.rb +701 -0
  224. data/lib/toaster/state/syscall_tracer.rb +317 -0
  225. data/lib/toaster/state/system_state.rb +349 -0
  226. data/lib/toaster/state/transition_edge.rb +65 -0
  227. data/lib/toaster/test/test_attribute.rb +19 -0
  228. data/lib/toaster/test/test_case.rb +175 -0
  229. data/lib/toaster/test/test_coverage.rb +147 -0
  230. data/lib/toaster/test/test_coverage_goal.rb +108 -0
  231. data/lib/toaster/test/test_generator.rb +246 -0
  232. data/lib/toaster/test/test_orchestrator.rb +165 -0
  233. data/lib/toaster/test/test_result.rb +73 -0
  234. data/lib/toaster/test/test_runner.rb +394 -0
  235. data/lib/toaster/test/test_suite.rb +151 -0
  236. data/lib/toaster/test_manager.rb +281 -0
  237. data/lib/toaster/toaster_app_service.rb +141 -0
  238. data/lib/toaster/util/blocking_map.rb +80 -0
  239. data/lib/toaster/util/combinatorial.rb +58 -0
  240. data/lib/toaster/util/config.rb +103 -0
  241. data/lib/toaster/util/docker.rb +71 -0
  242. data/lib/toaster/util/logging.rb +19 -0
  243. data/lib/toaster/util/lxc.rb +383 -0
  244. data/lib/toaster/util/mem_dump.rb +38 -0
  245. data/lib/toaster/util/proxy.rb +23 -0
  246. data/lib/toaster/util/timestamp.rb +131 -0
  247. data/lib/toaster/util/util.rb +437 -0
  248. data/lib/toaster/web_ui.rb +7 -0
  249. data/webapp/Gemfile +46 -0
  250. data/webapp/Gemfile.lock +237 -0
  251. data/webapp/Rakefile +6 -0
  252. data/webapp/app/assets/fonts/font.woff +0 -0
  253. data/webapp/app/assets/images/final_state.gif +0 -0
  254. data/webapp/app/assets/images/initial_state.gif +0 -0
  255. data/webapp/app/assets/images/loading.gif +0 -0
  256. data/webapp/app/assets/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  257. data/webapp/app/assets/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  258. data/webapp/app/assets/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  259. data/webapp/app/assets/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  260. data/webapp/app/assets/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  261. data/webapp/app/assets/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  262. data/webapp/app/assets/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  263. data/webapp/app/assets/images/ui-icons_222222_256x240.png +0 -0
  264. data/webapp/app/assets/javascripts/analysis.js.coffee +3 -0
  265. data/webapp/app/assets/javascripts/application.js +16 -0
  266. data/webapp/app/assets/javascripts/execs.js.coffee +3 -0
  267. data/webapp/app/assets/javascripts/graph.js +441 -0
  268. data/webapp/app/assets/javascripts/jquery-1.7.2.js +9404 -0
  269. data/webapp/app/assets/javascripts/jquery-ui-1.8.22.js +11529 -0
  270. data/webapp/app/assets/javascripts/jquery.cookie.js +96 -0
  271. data/webapp/app/assets/javascripts/jquery.hotkeys.js +99 -0
  272. data/webapp/app/assets/javascripts/jquery.jstree.js +4551 -0
  273. data/webapp/app/assets/javascripts/jsPlumb/jquery.jsPlumb-1.3.14-all-min.js +1 -0
  274. data/webapp/app/assets/javascripts/jstree.util.js +53 -0
  275. data/webapp/app/assets/javascripts/scripts.js.coffee +3 -0
  276. data/webapp/app/assets/javascripts/settings.js.coffee +3 -0
  277. data/webapp/app/assets/javascripts/test.js.coffee +3 -0
  278. data/webapp/app/assets/javascripts/util.js.coffee +3 -0
  279. data/webapp/app/assets/stylesheets/analysis.css.scss +3 -0
  280. data/webapp/app/assets/stylesheets/application.css +49 -0
  281. data/webapp/app/assets/stylesheets/execs.css.scss +3 -0
  282. data/webapp/app/assets/stylesheets/graph.css +159 -0
  283. data/webapp/app/assets/stylesheets/jquery-ui-1.8.6.custom.css +572 -0
  284. data/webapp/app/assets/stylesheets/layout.css +151 -0
  285. data/webapp/app/assets/stylesheets/menu.css +186 -0
  286. data/webapp/app/assets/stylesheets/reset.css +41 -0
  287. data/webapp/app/assets/stylesheets/scripts.css.scss +3 -0
  288. data/webapp/app/assets/stylesheets/settings.css.scss +3 -0
  289. data/webapp/app/assets/stylesheets/test.css.scss +3 -0
  290. data/webapp/app/assets/stylesheets/util.css.scss +3 -0
  291. data/webapp/app/assets/stylesheets/websymbols/websymbols-regular-webfont.eot +0 -0
  292. data/webapp/app/assets/stylesheets/websymbols/websymbols-regular-webfont.svg +1 -0
  293. data/webapp/app/assets/stylesheets/websymbols/websymbols-regular-webfont.ttf +0 -0
  294. data/webapp/app/assets/stylesheets/websymbols/websymbols-regular-webfont.woff +0 -0
  295. data/webapp/app/controllers/analysis_controller.rb +7 -0
  296. data/webapp/app/controllers/application_controller.rb +14 -0
  297. data/webapp/app/controllers/base_controller.rb +91 -0
  298. data/webapp/app/controllers/execs_controller.rb +59 -0
  299. data/webapp/app/controllers/graph_controller.rb +17 -0
  300. data/webapp/app/controllers/scripts_controller.rb +166 -0
  301. data/webapp/app/controllers/settings_controller.rb +10 -0
  302. data/webapp/app/controllers/test_controller.rb +127 -0
  303. data/webapp/app/controllers/util_controller.rb +6 -0
  304. data/webapp/app/helpers/analysis_helper.rb +2 -0
  305. data/webapp/app/helpers/application_helper.rb +2 -0
  306. data/webapp/app/helpers/automations_helper.rb +2 -0
  307. data/webapp/app/helpers/execs_helper.rb +2 -0
  308. data/webapp/app/helpers/scripts_helper.rb +2 -0
  309. data/webapp/app/helpers/settings_helper.rb +2 -0
  310. data/webapp/app/helpers/test_helper.rb +2 -0
  311. data/webapp/app/helpers/util_helper.rb +2 -0
  312. data/webapp/app/views/analysis/convergence.html.erb +156 -0
  313. data/webapp/app/views/analysis/idempotence.html.erb +2 -0
  314. data/webapp/app/views/execs/automation_runs.html.erb +98 -0
  315. data/webapp/app/views/execs/task_executions.html.erb +154 -0
  316. data/webapp/app/views/graph/graph_frame.html +101 -0
  317. data/webapp/app/views/layouts/application.html.erb +114 -0
  318. data/webapp/app/views/scripts/edit.html.erb +58 -0
  319. data/webapp/app/views/scripts/graph.html.erb +75 -0
  320. data/webapp/app/views/scripts/import_chef.html.erb +104 -0
  321. data/webapp/app/views/scripts/scripts.html.erb +116 -0
  322. data/webapp/app/views/scripts/tasks.html.erb +139 -0
  323. data/webapp/app/views/settings/containers.html.erb +161 -0
  324. data/webapp/app/views/settings/index.html.erb +51 -0
  325. data/webapp/app/views/test/cases.html.erb +2 -0
  326. data/webapp/app/views/test/gen.html.erb +229 -0
  327. data/webapp/app/views/test/suites.html.erb +205 -0
  328. data/webapp/app/views/util/chef.html.erb +53 -0
  329. data/webapp/bin/bundle +3 -0
  330. data/webapp/bin/rails +4 -0
  331. data/webapp/bin/rake +4 -0
  332. data/webapp/config/application.rb +28 -0
  333. data/webapp/config/boot.rb +18 -0
  334. data/webapp/config/database.yml +42 -0
  335. data/webapp/config/environment.rb +5 -0
  336. data/webapp/config/environments/development.rb +35 -0
  337. data/webapp/config/environments/production.rb +80 -0
  338. data/webapp/config/environments/test.rb +36 -0
  339. data/webapp/config/initializers/backtrace_silencers.rb +7 -0
  340. data/webapp/config/initializers/devise.rb +256 -0
  341. data/webapp/config/initializers/filter_parameter_logging.rb +4 -0
  342. data/webapp/config/initializers/inflections.rb +16 -0
  343. data/webapp/config/initializers/mime_types.rb +5 -0
  344. data/webapp/config/initializers/patches.rb +13 -0
  345. data/webapp/config/initializers/secret_token.rb +12 -0
  346. data/webapp/config/initializers/session_store.rb +6 -0
  347. data/webapp/config/initializers/wrap_parameters.rb +16 -0
  348. data/webapp/config/locales/devise.en.yml +59 -0
  349. data/webapp/config/locales/en.yml +23 -0
  350. data/webapp/config/routes.rb +113 -0
  351. data/webapp/config.ru +4 -0
  352. data/webapp/db/development.sqlite3 +0 -0
  353. data/webapp/db/migrate/20140624002037_devise_create_users.rb +42 -0
  354. data/webapp/db/migrate/20140628002058_create_all_tables.rb +159 -0
  355. data/webapp/db/schema.rb +162 -0
  356. data/webapp/db/seeds.rb +7 -0
  357. data/webapp/log/development.log +167823 -0
  358. data/webapp/public/404.html +58 -0
  359. data/webapp/public/422.html +58 -0
  360. data/webapp/public/500.html +57 -0
  361. data/webapp/public/favicon.ico +0 -0
  362. data/webapp/public/robots.txt +5 -0
  363. data/webapp/test/controllers/analysis_controller_test.rb +14 -0
  364. data/webapp/test/controllers/automations_controller_test.rb +49 -0
  365. data/webapp/test/controllers/execs_controller_test.rb +9 -0
  366. data/webapp/test/controllers/executions_controller_test.rb +9 -0
  367. data/webapp/test/controllers/runs_controller_test.rb +7 -0
  368. data/webapp/test/controllers/scripts_controller_test.rb +9 -0
  369. data/webapp/test/controllers/settings_controller_test.rb +9 -0
  370. data/webapp/test/controllers/test_controller_test.rb +14 -0
  371. data/webapp/test/controllers/util_controller_test.rb +9 -0
  372. data/webapp/test/fixtures/users.yml +11 -0
  373. data/webapp/test/helpers/analysis_helper_test.rb +4 -0
  374. data/webapp/test/helpers/automations_helper_test.rb +4 -0
  375. data/webapp/test/helpers/execs_helper_test.rb +4 -0
  376. data/webapp/test/helpers/executions_helper_test.rb +4 -0
  377. data/webapp/test/helpers/runs_helper_test.rb +4 -0
  378. data/webapp/test/helpers/scripts_helper_test.rb +4 -0
  379. data/webapp/test/helpers/settings_helper_test.rb +4 -0
  380. data/webapp/test/helpers/test_helper_test.rb +4 -0
  381. data/webapp/test/helpers/util_helper_test.rb +4 -0
  382. data/webapp/test/models/user_test.rb +7 -0
  383. data/webapp/test/test_helper.rb +15 -0
  384. data/webapp/tmp/cache/assets/development/sass/1330a34c6b24ed44c0f194c03246f627289a985a/analysis.css.scssc +0 -0
  385. data/webapp/tmp/cache/assets/development/sass/1330a34c6b24ed44c0f194c03246f627289a985a/automations.css.scssc +0 -0
  386. data/webapp/tmp/cache/assets/development/sass/1330a34c6b24ed44c0f194c03246f627289a985a/execs.css.scssc +0 -0
  387. data/webapp/tmp/cache/assets/development/sass/1330a34c6b24ed44c0f194c03246f627289a985a/executions.css.scssc +0 -0
  388. data/webapp/tmp/cache/assets/development/sass/1330a34c6b24ed44c0f194c03246f627289a985a/scripts.css.scssc +0 -0
  389. data/webapp/tmp/cache/assets/development/sass/1330a34c6b24ed44c0f194c03246f627289a985a/settings.css.scssc +0 -0
  390. data/webapp/tmp/cache/assets/development/sass/1330a34c6b24ed44c0f194c03246f627289a985a/test.css.scssc +0 -0
  391. data/webapp/tmp/cache/assets/development/sass/1330a34c6b24ed44c0f194c03246f627289a985a/util.css.scssc +0 -0
  392. data/webapp/tmp/cache/assets/development/sprockets/06a1cdb3d5472dd126cffc18f548c342 +0 -0
  393. data/webapp/tmp/cache/assets/development/sprockets/0dc0562647e703ca0535da3b9fcad796 +0 -0
  394. data/webapp/tmp/cache/assets/development/sprockets/113f1c71cb0f2a786729477989122982 +0 -0
  395. data/webapp/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  396. data/webapp/tmp/cache/assets/development/sprockets/151a1c6b128721d8923ba0c380a2af17 +0 -0
  397. data/webapp/tmp/cache/assets/development/sprockets/1722fb8a6daad566c8a6d5ef31969459 +0 -0
  398. data/webapp/tmp/cache/assets/development/sprockets/1a05555c46560c6b246aa0a996679bc6 +0 -0
  399. data/webapp/tmp/cache/assets/development/sprockets/1cb7fb6aa8bd1c2b7df8f7c52442e345 +0 -0
  400. data/webapp/tmp/cache/assets/development/sprockets/1d76f803b0a83adc5ea7e7c848657967 +0 -0
  401. data/webapp/tmp/cache/assets/development/sprockets/200152f95fc464d69c425ed8fdc2fbcc +0 -0
  402. data/webapp/tmp/cache/assets/development/sprockets/2049c8927ebd046c8a3291f81041d5f1 +0 -0
  403. data/webapp/tmp/cache/assets/development/sprockets/207907100aabd08a6767e39a95b364e3 +0 -0
  404. data/webapp/tmp/cache/assets/development/sprockets/249e2d4db815922fc72419c4a1407caf +0 -0
  405. data/webapp/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  406. data/webapp/tmp/cache/assets/development/sprockets/3034e4c334b1ebed9433e33050555f18 +0 -0
  407. data/webapp/tmp/cache/assets/development/sprockets/316c88d859cc3666044e51b1dd89e872 +0 -0
  408. data/webapp/tmp/cache/assets/development/sprockets/347f43d816178c8ba11a28a771696924 +0 -0
  409. data/webapp/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  410. data/webapp/tmp/cache/assets/development/sprockets/36ab92a3b8d527ed1eff0944b5f1263d +0 -0
  411. data/webapp/tmp/cache/assets/development/sprockets/3893b852ff196f05ff6ca8e38bb2237f +0 -0
  412. data/webapp/tmp/cache/assets/development/sprockets/3c358bd5fa32d151c7aa44971dd9b96f +0 -0
  413. data/webapp/tmp/cache/assets/development/sprockets/3ce88fad85146e0bed233f5419c24d29 +0 -0
  414. data/webapp/tmp/cache/assets/development/sprockets/3dabcb7c89677292e1e019a9bac446e5 +0 -0
  415. data/webapp/tmp/cache/assets/development/sprockets/4116fac01e6d379ede37af725a592477 +0 -0
  416. data/webapp/tmp/cache/assets/development/sprockets/42628e032b8f0b16a5ba01a1fcc7b6d7 +0 -0
  417. data/webapp/tmp/cache/assets/development/sprockets/437b258a98e8fe2c87fa8e24b7ce9abf +0 -0
  418. data/webapp/tmp/cache/assets/development/sprockets/43e98ed206f05738ae305e7e1b09a840 +0 -0
  419. data/webapp/tmp/cache/assets/development/sprockets/468da8933ba442b88b2f4b0e40c2ac30 +0 -0
  420. data/webapp/tmp/cache/assets/development/sprockets/48ef3fa68e9084c7cfa84e525decc318 +0 -0
  421. data/webapp/tmp/cache/assets/development/sprockets/4b68dcc030691297629a7156361018dc +0 -0
  422. data/webapp/tmp/cache/assets/development/sprockets/50368b1bd77c9130d821cbad89ab83ac +0 -0
  423. data/webapp/tmp/cache/assets/development/sprockets/511a980807b8d16fc3fb839991041865 +0 -0
  424. data/webapp/tmp/cache/assets/development/sprockets/516a0973874a73d0d15feec9448f7466 +0 -0
  425. data/webapp/tmp/cache/assets/development/sprockets/569e21b0ca1a230c7880f8d7a8b1febf +0 -0
  426. data/webapp/tmp/cache/assets/development/sprockets/5b5543d2ed5bed100f1739fc12ebf2c5 +0 -0
  427. data/webapp/tmp/cache/assets/development/sprockets/5c10dbffb918651dc0498579ba9a6853 +0 -0
  428. data/webapp/tmp/cache/assets/development/sprockets/5c366b5370316b3e9c03d1a58cb5eb44 +0 -0
  429. data/webapp/tmp/cache/assets/development/sprockets/5fcb9466e0d5cb0e31b35957fe2d3901 +0 -0
  430. data/webapp/tmp/cache/assets/development/sprockets/61da396fb86c5ecd844a2d83ac759b4b +0 -0
  431. data/webapp/tmp/cache/assets/development/sprockets/624ba87170059068845a4651b3cd2c7c +0 -0
  432. data/webapp/tmp/cache/assets/development/sprockets/6391f61b6121feda70da6d0a22520fdd +0 -0
  433. data/webapp/tmp/cache/assets/development/sprockets/653a981829c12a52bd57b7918168a148 +0 -0
  434. data/webapp/tmp/cache/assets/development/sprockets/66479793d9e5683d9f7fa67522d1962d +0 -0
  435. data/webapp/tmp/cache/assets/development/sprockets/69c589bd224ee0df5ab02e4568e0ab39 +0 -0
  436. data/webapp/tmp/cache/assets/development/sprockets/6dd7c564897b7e66eecbb180baeef2c6 +0 -0
  437. data/webapp/tmp/cache/assets/development/sprockets/6e2b92a4e6966cb78e63207bc7888391 +0 -0
  438. data/webapp/tmp/cache/assets/development/sprockets/70adb410f59a0edc5aea8c2b2ef8a441 +0 -0
  439. data/webapp/tmp/cache/assets/development/sprockets/72cbfd5bf33945bcce154f7a4feaf04d +0 -0
  440. data/webapp/tmp/cache/assets/development/sprockets/740ee9d355ada8cf5e9beb620f2cd5cc +0 -0
  441. data/webapp/tmp/cache/assets/development/sprockets/74770a22b5d1b0b3bc3527da83706f96 +0 -0
  442. data/webapp/tmp/cache/assets/development/sprockets/759bf97655af50852816720db7b59fcb +0 -0
  443. data/webapp/tmp/cache/assets/development/sprockets/76e9c19246c684413a02af695fa2b0bc +0 -0
  444. data/webapp/tmp/cache/assets/development/sprockets/7728beeace48ca12e336d2bce0158871 +0 -0
  445. data/webapp/tmp/cache/assets/development/sprockets/77f49730c7b689f75a2e174fbd3ace2a +0 -0
  446. data/webapp/tmp/cache/assets/development/sprockets/78021a57030fd36cf82699aad9910b26 +0 -0
  447. data/webapp/tmp/cache/assets/development/sprockets/796b349b06da691fe5f069f9e3947fcb +0 -0
  448. data/webapp/tmp/cache/assets/development/sprockets/79e7505c70697cc7d34d4a8351c3b840 +0 -0
  449. data/webapp/tmp/cache/assets/development/sprockets/7b2b7d9034fc7b77daf5da1436667e6f +0 -0
  450. data/webapp/tmp/cache/assets/development/sprockets/7c2ea2ca2cdd89b062bf23f44e8f599c +0 -0
  451. data/webapp/tmp/cache/assets/development/sprockets/7ea112af55e6df12cdff377052b71420 +0 -0
  452. data/webapp/tmp/cache/assets/development/sprockets/7f27632588dd316836b009a621c0176c +0 -0
  453. data/webapp/tmp/cache/assets/development/sprockets/7fcef9f644290dea8d0e4bf13f6bb8e9 +0 -0
  454. data/webapp/tmp/cache/assets/development/sprockets/81c6bf67419a29257ed79f442c24907d +0 -0
  455. data/webapp/tmp/cache/assets/development/sprockets/83656ce9260eb473c1654420e0180167 +0 -0
  456. data/webapp/tmp/cache/assets/development/sprockets/83e8bdddefc2dce069b56e6650135397 +0 -0
  457. data/webapp/tmp/cache/assets/development/sprockets/880c796f41e5d67ae68ed239acfc91e3 +0 -0
  458. data/webapp/tmp/cache/assets/development/sprockets/8c1c39dd537d2c9990fe9c70a5646b06 +0 -0
  459. data/webapp/tmp/cache/assets/development/sprockets/8d763042da5254015767385ce324b348 +0 -0
  460. data/webapp/tmp/cache/assets/development/sprockets/91e1ccbf65ff4d9b0f42b1159796bcf2 +0 -0
  461. data/webapp/tmp/cache/assets/development/sprockets/92cdfac7698b6bb68983cf317c1cedef +0 -0
  462. data/webapp/tmp/cache/assets/development/sprockets/94cab8cbc120e152ce4c444492a3fc1d +0 -0
  463. data/webapp/tmp/cache/assets/development/sprockets/96b918c3c041b6a089c8adc870fa3aaa +0 -0
  464. data/webapp/tmp/cache/assets/development/sprockets/97cbe306627b432ab3bfb52416690d1d +0 -0
  465. data/webapp/tmp/cache/assets/development/sprockets/98da83d396c0c00eb03937ecbc2e6bfa +0 -0
  466. data/webapp/tmp/cache/assets/development/sprockets/9db04db20ec9c4a0f84ed01caa5e6e2b +0 -0
  467. data/webapp/tmp/cache/assets/development/sprockets/9dc1c251f50c828fae95d6107619c53e +0 -0
  468. data/webapp/tmp/cache/assets/development/sprockets/9e91711ab34bbf89ad1cfb4351027d36 +0 -0
  469. data/webapp/tmp/cache/assets/development/sprockets/9f587881f98b328b656e6456178418b5 +0 -0
  470. data/webapp/tmp/cache/assets/development/sprockets/9fc981560caba00621bb95d0974ad076 +0 -0
  471. data/webapp/tmp/cache/assets/development/sprockets/9fd4385f8fda99f3ddd3f133a89231d1 +0 -0
  472. data/webapp/tmp/cache/assets/development/sprockets/a32ab71649e3db15eca95bcc3a7588da +0 -0
  473. data/webapp/tmp/cache/assets/development/sprockets/a497c976969bdbb1f69875358d39d3b5 +0 -0
  474. data/webapp/tmp/cache/assets/development/sprockets/b06058d455770c1d9db6b6670bc4bf4d +0 -0
  475. data/webapp/tmp/cache/assets/development/sprockets/b3da405f1e68470ad6146a1322f2438c +0 -0
  476. data/webapp/tmp/cache/assets/development/sprockets/b4ba5853663f6721d15942ce08dd76bf +0 -0
  477. data/webapp/tmp/cache/assets/development/sprockets/b6f1534bcdbff92a16c85487f363235a +0 -0
  478. data/webapp/tmp/cache/assets/development/sprockets/b9c57b9750f8af3551b1a5d9e27f0a92 +0 -0
  479. data/webapp/tmp/cache/assets/development/sprockets/bb5be6a0ee889a15214e2442aee8c6e1 +0 -0
  480. data/webapp/tmp/cache/assets/development/sprockets/bc80a81b2d67b5ad40f3c7a2bb6e6f22 +0 -0
  481. data/webapp/tmp/cache/assets/development/sprockets/bfaba27d5ce71d292f453659b0755511 +0 -0
  482. data/webapp/tmp/cache/assets/development/sprockets/bfac6cd2984fbd5e0d762389e3c37164 +0 -0
  483. data/webapp/tmp/cache/assets/development/sprockets/c17bec9e3b5db6d8385c3d6eb362e628 +0 -0
  484. data/webapp/tmp/cache/assets/development/sprockets/c1a9eceddc0a1e41eb2992e9204f71e6 +0 -0
  485. data/webapp/tmp/cache/assets/development/sprockets/c50cd87be040044504b5c3fde1047e24 +0 -0
  486. data/webapp/tmp/cache/assets/development/sprockets/c5907cfd07b24ad19b8c80e8af618a57 +0 -0
  487. data/webapp/tmp/cache/assets/development/sprockets/c8da5594caf845a183dd1c6cd45c5433 +0 -0
  488. data/webapp/tmp/cache/assets/development/sprockets/cbc6968ed49fa14db439a64dd0a46abc +0 -0
  489. data/webapp/tmp/cache/assets/development/sprockets/cedf710ee090647b81c08675583eca62 +0 -0
  490. data/webapp/tmp/cache/assets/development/sprockets/cf1e3cebfad43293c3c470a5e437ebc9 +0 -0
  491. data/webapp/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  492. data/webapp/tmp/cache/assets/development/sprockets/d5a14f86bb4b422fdd8eb4722d966c9a +0 -0
  493. data/webapp/tmp/cache/assets/development/sprockets/d66b9cdd3230f9e5354e495e17097c49 +0 -0
  494. data/webapp/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  495. data/webapp/tmp/cache/assets/development/sprockets/dfa0ed87d851222fe58ada385e3f037d +0 -0
  496. data/webapp/tmp/cache/assets/development/sprockets/e1aa402390382db4d97b27cb094485fa +0 -0
  497. data/webapp/tmp/cache/assets/development/sprockets/e4f1db35ef9d9fbbf53252a44e381eb8 +0 -0
  498. data/webapp/tmp/cache/assets/development/sprockets/e57eb6c1ad51fd8c13afc0e719a259b5 +0 -0
  499. data/webapp/tmp/cache/assets/development/sprockets/e7c37b542e914e392ec326190cea1aa2 +0 -0
  500. data/webapp/tmp/cache/assets/development/sprockets/e923d7e19c216d4a924831f3896e007a +0 -0
  501. data/webapp/tmp/cache/assets/development/sprockets/e96a414a621483bbc89e73f11be5e7b4 +0 -0
  502. data/webapp/tmp/cache/assets/development/sprockets/eafcfc3d2561c18743c1b32f0f710c68 +0 -0
  503. data/webapp/tmp/cache/assets/development/sprockets/ecdd630f5466d45a2afa7515aaa5fc3b +0 -0
  504. data/webapp/tmp/cache/assets/development/sprockets/ee60a0a35d60cff425373970547694bd +0 -0
  505. data/webapp/tmp/cache/assets/development/sprockets/eee2c36b75709b0c3797c2a151fdd5d3 +0 -0
  506. data/webapp/tmp/cache/assets/development/sprockets/f104b66c0f89b453d0c8c73980fbab18 +0 -0
  507. data/webapp/tmp/cache/assets/development/sprockets/f26d1bc86590415685e9836ad75d1774 +0 -0
  508. data/webapp/tmp/cache/assets/development/sprockets/f4081e868eb565527099dba2c9d8c4de +0 -0
  509. data/webapp/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  510. data/webapp/tmp/cache/assets/development/sprockets/f813030b97e30415ad276bbd1f7e1fb4 +0 -0
  511. data/webapp/tmp/cache/assets/development/sprockets/f8e3e28bc9640c25da58aea46c3ac4b3 +0 -0
  512. data/webapp/tmp/cache/assets/development/sprockets/f9169d0a8b70eaf95de52b3c6ad51105 +0 -0
  513. data/webapp/tmp/cache/assets/development/sprockets/f939450f7a481e3fbeb158b81b39c624 +0 -0
  514. data/webapp/tmp/cache/assets/development/sprockets/fce73296b8298ac4c74b3e95cde4dfdb +0 -0
  515. metadata +941 -0
@@ -0,0 +1,514 @@
1
+
2
+
3
+ require 'toaster/util/util'
4
+ require 'toaster/chef/chef_util'
5
+ require 'toaster/markup/markup_util'
6
+ require 'toaster/model/automation'
7
+ require 'toaster/model/automation_run'
8
+ require 'json'
9
+ require 'chef/application/solo'
10
+ require 'chef/log'
11
+ require 'chef/runner'
12
+ require "toaster/util/timestamp"
13
+
14
+ include Toaster
15
+ include Aquarium::Aspects
16
+
17
+ module Toaster
18
+ #
19
+ # Performs AOP-based instrumentation of the Chef runtime and
20
+ # registers listeners for lifecycle events during Chef automations.
21
+ #
22
+ # Author: Waldemar Hummer (hummer@dsg.tuwien.ac.at)
23
+ #
24
+ class ChefListener
25
+ # Informs about an automation task that is about to be executed.
26
+ #
27
+ # * *Args* :
28
+ # - +task+ -> A Toaster::Task representing the currently executing task
29
+ # - +execution_uuid+ -> String that identifies the automation run this task is part of
30
+ #
31
+ def before_run_action(task, execution_uuid)
32
+ # should be overridden by subclasses
33
+ end
34
+
35
+ # Informs about an automation task that has just been executed.
36
+ #
37
+ # * *Args* :
38
+ # - +task+ -> A Toaster::Task representing the executed task
39
+ # - +execution_uuid+ -> String that identifies the automation run this task is part of
40
+ # - +error+ -> Contains details (usually an Exception object) in case an error has occurred
41
+ # - +script_output+ -> Contains output (stdout+stderr) of the script that executed the task
42
+ #
43
+ def after_run_action(task, execution_uuid, error = nil, script_output = nil)
44
+ # should be overridden by subclasses
45
+ end
46
+
47
+ def self.add_listener(listener)
48
+ register_aop_hook()
49
+ self.active_listeners.push(listener)
50
+ end
51
+
52
+ # maps string -> task
53
+ @tasks_by_uuid = {}
54
+ # maps task -> joinpoint parameters
55
+ @task_parameters = {}
56
+ # maps resource -> task
57
+ @tasks_by_resource = {}
58
+
59
+ @active_listeners = []
60
+ @repeated_tasks_running = false
61
+ @task_exec_uuid_to_task = {}
62
+ @task_exec_error = nil
63
+ @current_task_exec_uuid = nil
64
+ @initialized = false
65
+ @toaster_chef_imported = false
66
+ @chef_log_level = :info
67
+ @task_execution_timeout = 60*20 # 20 minutes timeout per task
68
+ @task_exec_timeout_repeated = 60*7 # timeout for tasks with retries
69
+ @max_task_exec_retries = 2
70
+ @repeat_resource_classes = [::Chef::Resource::RemoteFile,
71
+ ::Chef::Resource::Package,
72
+ ::Chef::Resource::GemPackage,
73
+ ::Chef::Resource::YumPackage,
74
+ ::Chef::Resource::AptPackage]
75
+
76
+ class << self
77
+ attr_accessor :active_listeners, :tasks_by_uuid, :task_parameters,
78
+ :repeated_tasks_running, :initialized, :toaster_chef_imported,
79
+ :chef_log_level, :task_execution_timeout, :task_exec_timeout_repeated,
80
+ :max_task_exec_retries, :repeat_resource_classes, :tasks_by_resource,
81
+ :current_task_exec_uuid, :task_exec_uuid_to_task, :task_exec_error
82
+ end
83
+
84
+ def self.get_task_from_sourcecode_line(resource, action, start_source_line)
85
+ sourcecode = nil
86
+ sourcefile = nil
87
+ sourceline = nil
88
+ if start_source_line
89
+ # parse static resource definition source code
90
+ sourcecode = ChefUtil.read_sourcecode(start_source_line)
91
+ sourcefile = ChefUtil.get_sourcefile(start_source_line)
92
+ begin sourcefile = sourcefile[(sourcefile.index("/")+1)..-1] end while sourcefile.scan("/").size > 2
93
+ sourceline = ChefUtil.get_sourceline(start_source_line)
94
+ end
95
+ # get or create task
96
+ task = Task.load_from_chef_source(resource, action, sourcecode, sourcefile, sourceline)
97
+ return task
98
+ end
99
+
100
+ def self.get_task_list(chef_resource_list)
101
+ list = []
102
+ chef_resource_list.each do |r|
103
+ resource = r.to_s
104
+ action = r.action
105
+ action = action[0] if action.kind_of?(Array)
106
+ action = action.to_s if action.kind_of?(Symbol)
107
+ start_source_line = r.source_line
108
+ if !start_source_line
109
+ puts "WARN: Could not read source_line from resource #{r}"
110
+ end
111
+ task = get_task_from_sourcecode_line(resource,action,start_source_line)
112
+ list << task
113
+ end
114
+ return list
115
+ end
116
+
117
+ # this method is called ONCE, in the context of executing
118
+ # the first task of the automation under test
119
+ def self.update_current_automation_info(run, ctx)
120
+ node_name = nil
121
+
122
+ # get run_list
123
+ run_list = []
124
+ ctx.node.run_list.each do |n|
125
+ run_list << n.to_s
126
+ end
127
+ run_list = ChefUtil.get_reduced_run_list(run_list)
128
+
129
+ # try to determine node name (cookbook name)
130
+ chef_cfg = Chef::Config
131
+ if chef_cfg[:json_attribs]
132
+ json_file = chef_cfg[:json_attribs]
133
+ if json_file.match(/.*\/[^\/]+_node\.json/).to_s == json_file
134
+ node_name = json_file.gsub(/.*\/([^\/]+)_node\.json/, '\1')
135
+ node_name = ChefUtil.wrap_node_name(node_name)
136
+ end
137
+ else
138
+ puts "WARN: Chef::Config[:json_attribs] is not available: '#{chef_cfg[:json_attribs]}'"
139
+ node_name = chef_cfg[:node_name]
140
+ end
141
+
142
+ if !node_name
143
+ node_name = ChefUtil.guess_cookbook_from_runlist(run_list)
144
+ end
145
+ node_name = ctx.node.to_s if !node_name
146
+ node_name = node_name.gsub(/.*node\[(.+)\]/, '\1') if node_name.include?("node[")
147
+
148
+ # try to retrieve automation from DB, if it exists..
149
+ if File.exist?(json_file)
150
+ attribs = JSON.parse(File.read(json_file))
151
+ if attribs["automation_uuid"]
152
+ autos = Toaster::Automation.find(
153
+ :uuid => attribs["automation_uuid"]).to_a
154
+ if !autos.empty?
155
+ run.automation = autos[0]
156
+ return
157
+ end
158
+ end
159
+ end
160
+ if !run.automation
161
+ run.automation = Automation.find_by_cookbook_and_runlist(node_name, run_list)[0]
162
+ end
163
+
164
+ puts "TRACE: automation run #{run.uuid}, automation #{run.automation ? run.automation.uuid : 'nil'}"
165
+
166
+ # create new automation if we cannot find existing one...
167
+ if !run.automation
168
+
169
+ config_dir = File.join(File.dirname(__FILE__),"..")
170
+ config_json = ::Toaster::Config.values()
171
+ dirs = []
172
+ config_json["chef"]["cookbook_dirs"].each do |dir|
173
+ dir = File.expand_path(File.join(config_dir, dir)) if dir[0] != "/"
174
+ dirs << dir
175
+ end
176
+
177
+ # important: due to possible name conflicts, all Chef related
178
+ # files apparently need to be loaded before chef_node_inspector,
179
+ # hence we put this require statement here.
180
+ if !self.toaster_chef_imported
181
+ require 'toaster/chef/chef_node_inspector'
182
+ self.toaster_chef_imported = true
183
+ end
184
+
185
+ node_values = config_json["chef"]["node_values"]
186
+ insp = Toaster::ChefNodeInspector.new(dirs,
187
+ Toaster::DefaultProcessorRecursive.new(node_values) {
188
+ |level, msg| puts "WARN: #{msg}" }) {
189
+ |level, msg| puts "WARN: #{msg}"}
190
+ recipe = ChefUtil.guess_recipe_from_runlist(run_list)
191
+ params = {}
192
+ begin
193
+ params = insp.get_defaults(node_name, recipe)
194
+ puts "params: #{params}"
195
+ # make sure we make the hash MongoDB-compatible
196
+ # (i.e., remove special characters in keys):
197
+ MarkupUtil.rectify_keys(params)
198
+ rescue Object => exc
199
+ puts "WARN: Unable to get default parameters for recipe '#{node_name}'-'#{recipe}': #{exc} - #{exc.backtrace}"
200
+ end
201
+
202
+ task_list = ChefListener.get_task_list(ctx.resource_collection.all_resources)
203
+ run.automation = Automation.load_for_chef(node_name, task_list, params, run_list)
204
+ #puts "TRACE: automation 1: #{node_name}, #{task_list}, #{params}, #{run_list}"
205
+ #puts "TRACE: automation run 1: #{run.uuid}, automation #{run.automation ? run.automation.uuid : 'nil'}"
206
+
207
+ end
208
+
209
+ # determine active parameter values for the current automation run
210
+ Automation.get_attribute_array_names(params, "").each do |param_array_path|
211
+ val = nil
212
+ if "#{param_array_path}" != ""
213
+ eval("val = ctx.node#{param_array_path}")
214
+ eval("params#{param_array_path} = val")
215
+ end
216
+ end
217
+ run.run_attributes = RunAttribute.from_hash(params)
218
+ end
219
+
220
+ def self.save_current_run_details()
221
+ run = AutomationRun.get_current()
222
+ if run
223
+ run.end_time = TimeStamp.now().to_i
224
+ run.save
225
+
226
+ # check if all executed tasks are also included
227
+ # in the associated automation entity
228
+ auto = run.automation
229
+ if auto
230
+ changed = false
231
+ run.get_executed_tasks.each do |t|
232
+ if !auto.get_task_ids.include?(t.uuid)
233
+ auto.tasks << t
234
+ changed = true
235
+ end
236
+ end
237
+ auto.save if changed
238
+ end
239
+ end
240
+ end
241
+
242
+ def self.proceed_joinpoint_method(jp, task)
243
+
244
+ # 16-characters short random ID should be enough for our purposes..
245
+ execution_uuid = Util.generate_short_uid
246
+ run = AutomationRun.get_current()
247
+
248
+ # prepare task execution
249
+ do_continue = prepare_task_exec(task, execution_uuid)
250
+
251
+ if !do_continue
252
+
253
+ puts "Skipping execution of task #{task.uuid} in automation run #{run.uuid}"
254
+
255
+ else
256
+
257
+ error = nil
258
+ # we have to set the class-wide error variable as well,
259
+ # because we need it later on to get the error of a
260
+ # chef resource notification run (TODO: revise)
261
+ ChefListener.task_exec_error = nil # TODO
262
+
263
+ begin
264
+ ChefUtil.set_chef_log_level(self.chef_log_level)
265
+
266
+ ################################
267
+ # EXECUTE RESOURCE, WITH RETRIES
268
+ ################################
269
+
270
+ resource_to_exec = jp.context.parameters[0]
271
+ resource_class = resource_to_exec.class
272
+ exec_timeout = ChefListener.task_execution_timeout
273
+ num_retries = 0
274
+ if ChefListener.repeat_resource_classes.include?(resource_class)
275
+ # set task execution timeout and retries
276
+ exec_timeout = ChefListener.task_exec_timeout_repeated
277
+ num_retries = ChefListener.max_task_exec_retries
278
+ end
279
+ (0..num_retries).each do |iter|
280
+ begin
281
+ Util.exec_timeout(exec_timeout, ::Chef::Exceptions::CommandTimeout) do
282
+ begin
283
+ self.current_task_exec_uuid = execution_uuid
284
+ self.task_exec_uuid_to_task[execution_uuid] = task
285
+ jp.proceed
286
+ rescue Object => exc1
287
+ puts "WARN: Exception in resource execution: #{exc1} - backtrace (last 10 lines): #{exc1.backtrace[0..10]}"
288
+ raise exc1
289
+ ensure
290
+ self.current_task_exec_uuid = nil
291
+ end
292
+ end
293
+ break
294
+ rescue ::Chef::Exceptions::CommandTimeout => exc
295
+ # timeout occurred, start next try
296
+ remaining = num_retries-iter
297
+ if remaining > 0
298
+ puts "WARN: Chef resource #{resource_to_exec} timed out, remaining retries: #{remaining}"
299
+ sleep 5 # sleep a bit before the next retry
300
+ end
301
+ end
302
+ end
303
+
304
+ rescue Object => ex
305
+ error = ex
306
+ puts "INFO: Error in Chef automation method. Adding details to testing log."
307
+ run = AutomationRun.get_current()
308
+ run.success = false
309
+ run.end_time = TimeStamp.now().to_i
310
+ run.error_details = "#{error}\n#{error.backtrace}"
311
+ run.save()
312
+ end
313
+
314
+ # post-process task execution
315
+ close_task_exec(task, execution_uuid, error, run)
316
+
317
+ raise error if error
318
+ end
319
+ end
320
+
321
+ def self.prepare_task_exec(task, execution_uuid)
322
+
323
+ #puts "TRACE: Prepare task execution, task '#{task.name}', #{task.uuid}, execution #{execution_uuid}"
324
+
325
+ # notify listeners
326
+ do_continue = false
327
+ self.active_listeners.each do |l|
328
+ begin
329
+ do_continue ||= l.before_run_action(task, execution_uuid)
330
+ rescue Exception => ex
331
+ puts "Error in listener method 'before_run_action': #{ex}"
332
+ puts ex.backtrace
333
+ end
334
+ end
335
+
336
+ return false if !do_continue
337
+
338
+ # TODO: turn globals into class variables!
339
+ $chef_log_level = ChefUtil.get_chef_log_level
340
+ # redirect/capture STDOUT and STDERR!
341
+ $output_io = StringIO.open('','w')
342
+ $previous_stderr = $stderr
343
+ $previous_stdout = $stdout
344
+ $stdout.sync = true
345
+ $stderr.sync = true
346
+ $stderr = $output_io
347
+ $stdout = $output_io
348
+ script_output = nil
349
+
350
+ return true
351
+ end
352
+
353
+ def self.close_task_exec(task, execution_uuid, error=nil, run=nil)
354
+
355
+ run = AutomationRun.get_current() if !run
356
+
357
+ # get output string
358
+ script_output = $output_io.string
359
+ # reset STDOUT and STDERR
360
+ $stderr = $previous_stderr
361
+ $stdout = $previous_stdout
362
+ # print output
363
+ puts script_output
364
+ ChefUtil.set_chef_log_level($chef_log_level)
365
+
366
+ # notify listeners
367
+ self.active_listeners.each do |l|
368
+ begin
369
+ l.after_run_action(task, execution_uuid, error, script_output)
370
+ rescue Exception => ex
371
+ puts "Error in listener method 'after_run_action': #{ex}"
372
+ puts ex.backtrace
373
+ end
374
+ end
375
+
376
+ end
377
+
378
+ def self.register_aop_hook1()
379
+ Aspect.new :around, :calls_to => /^converge/,
380
+ # :method_options => :exclude_ancestor_methods,
381
+ :for_types => [Chef::Runner] do |jp, obj, *args|
382
+ begin
383
+ #puts "!!!!!!!!!!!!! AOP Chef::Runner::converge !!!!!!!!!!!!!!!!!!"
384
+ # TODO FIXME uncomment as soon as this pointcut is fixed
385
+ #ChefListener.save_current_run_details()
386
+ end
387
+ end
388
+ end
389
+
390
+ def self.register_aop_hook()
391
+
392
+ return if self.initialized
393
+ self.initialized = true
394
+
395
+ register_aop_hook1()
396
+
397
+ Aspect.new :around, :calls_to => /^run_action/,
398
+ :for_types => [Chef::Runner],
399
+ :method_options => :exclude_ancestor_methods do |jp, obj, *args|
400
+ begin
401
+
402
+ if jp.method_name.to_s == "run_action"
403
+
404
+ # check to see if we need to update the 'current automation' object
405
+ run = AutomationRun.get_current()
406
+ if run && !run.automation
407
+ ctx = obj.run_context
408
+ ChefListener.update_current_automation_info(run, ctx)
409
+ end
410
+
411
+ # TODO: remove as soon as AOP pointcut for Chef::Runner.converge is working..
412
+ ChefListener.save_current_run_details()
413
+
414
+ resource = args[0]
415
+ action = args[1]
416
+ task = nil
417
+ if ChefListener.tasks_by_resource[resource]
418
+ task = ChefListener.tasks_by_resource[resource]
419
+ else
420
+ source_line_spec = ""
421
+ args.each do |c|
422
+ if c.respond_to?("source_line")
423
+ #puts "DEBUG: AOP args in run_action: #{args}"
424
+ source_line_spec = c.source_line
425
+ # this break here is vital!, because multiple source_line_spec's
426
+ # are read in this loop sometimes (in a Chef notifications context)
427
+ # and the FIRST element on the "args" array is the right resource
428
+ # we are looking for! For instance, we have seen arrays like the
429
+ # following: [<notified_resource>, :run, :immediate, <notifying_resource>]
430
+ # TODO: check again, not sure if the statement above is true :/
431
+ break
432
+ end
433
+ end
434
+ task = get_task_from_sourcecode_line(resource, action, source_line_spec)
435
+ puts "get task #{resource} #{action} #{source_line_spec}: #{task}"
436
+ end
437
+
438
+ # check if we execute within an "immediate notification"
439
+ if ChefListener.current_task_exec_uuid
440
+
441
+ old_task = ChefListener.task_exec_uuid_to_task[ChefListener.current_task_exec_uuid]
442
+
443
+ puts "INFO: Apparently we are running within a notification context; " +
444
+ "closing task execution (to re-open a new execution). " +
445
+ "Old task: name '#{old_task.name}', uuid '#{old_task.uuid}'. " +
446
+ "New task: name '#{task.name}', uuid '#{task.uuid}'."
447
+
448
+ # close old/current task execution - a new one will be opened later in the process...
449
+ close_task_exec(old_task, ChefListener.current_task_exec_uuid)
450
+
451
+ end
452
+
453
+ ChefListener.tasks_by_uuid[task.uuid] = task
454
+ ChefListener.tasks_by_resource[resource] = task
455
+ ChefListener.task_parameters[task] = jp.context.parameters
456
+
457
+ num_executions = 1
458
+ if !self.active_listeners.empty?
459
+ num_executions = self.active_listeners[0].num_requested_task_executions(task)
460
+ end
461
+
462
+ # execute task, possibly multiple times...
463
+ (1..num_executions).each do
464
+
465
+ # run the actual Chef method that was intercepted by AOP..
466
+ proceed_joinpoint_method(jp, task)
467
+
468
+ end
469
+
470
+ if !self.repeated_tasks_running
471
+ # repeat sequences of tasks, as specified in repeat_tasks=[..] Chef configuration
472
+ tasks_to_repeat = self.active_listeners[0].tasks_to_repeat_now(task)
473
+ if tasks_to_repeat
474
+ puts "INFO: List of tasks to repeat: #{tasks_to_repeat}"
475
+ # temporarily disable interception (otherwise we end up in an infinite loop!)
476
+ self.repeated_tasks_running = true
477
+ tasks_to_repeat.each do |t_uuid|
478
+ task_to_run = self.tasks_by_uuid[t_uuid]
479
+ puts "INFO: Proceeding old joinpoint of task with uuid #{t_uuid}"
480
+ chef_runner = jp.context.advised_object
481
+ params = self.task_parameters[task_to_run]
482
+ # invoke method
483
+ begin
484
+ # this call will again be intercepted by AOP and we will
485
+ # eventually end up in self.proceed_joinpoint_method(...)
486
+ # Hence, no exec_timeout block is required for this call.
487
+ chef_runner.run_action(params[0], params[1])
488
+ rescue Object => exc
489
+ puts "Error when repeating Chef resource: #{exc} - #{exc.backtrace}"
490
+ end
491
+ end
492
+ # re-activate interception
493
+ self.repeated_tasks_running = false
494
+ end
495
+ end
496
+
497
+ else
498
+
499
+ begin
500
+ jp.proceed
501
+ rescue Object => ex
502
+ puts "Error in Chef automation: #{ex}"
503
+ end
504
+
505
+ end
506
+ rescue Object => ex1
507
+ puts "Error occurred: #{ex1}"
508
+ puts ex1.backtrace.join("\n") + "\n..."
509
+ end
510
+ end
511
+ end
512
+
513
+ end
514
+ end
@@ -0,0 +1,165 @@
1
+
2
+ require 'toaster/util/logging'
3
+
4
+ # update Waldemar Hummer:
5
+ # the requires below are required, otherwise we get:
6
+ # uninitialized constant RubyLexer::RubyParserStuff
7
+ # [...]/ruby_parser-3.1.1/lib/ruby_lexer.rb:241
8
+ require 'ruby_lexer'
9
+ require 'ruby_parser_extras'
10
+ class RubyLexer
11
+ begin
12
+ #RubyParserStuff = Kernel.const_get("RubyParserStuff")
13
+ rescue Object => exc
14
+ begin
15
+ RubyParserStuff = ::RubyParserStuff
16
+ rescue Object => exc1
17
+ puts "WARN: Unable to load the module 'RubyParserStuff' into class RubyLexer"
18
+ end
19
+ end
20
+ end
21
+
22
+ require 'json'
23
+
24
+ module Toaster
25
+
26
+ # Enhanced, recursive mini-DSL processor which is able to parse multi-level
27
+ # nested Chef attributes, e.g., default['apache']['worker']['startservers'] = 4
28
+ #
29
+ # Author: Waldemar Hummer (hummer@dsg.tuwien.ac.at)
30
+ class DefaultProcessorRecursive < Hash # :nodoc:
31
+ attr_accessor :swallow_calls_hash
32
+ def initialize(node_values = {}, &error_reporting)
33
+ @user_values = Hash.new.merge(node_values) if node_values
34
+ @error_reporting = error_reporting
35
+ @swallow_calls_hash = nil
36
+ end
37
+ def default(arg = nil)
38
+ if arg
39
+ if !self.include?(arg)
40
+ store(arg, self.class.new)
41
+ end
42
+ return self[arg]
43
+ end
44
+ return self
45
+ end
46
+ def set(arg = nil)
47
+ if arg
48
+ store(arg, self.class.new)
49
+ return self[arg]
50
+ end
51
+ return self
52
+ end
53
+ def node(arg = nil)
54
+ new_hash = self.class.new
55
+ new_hash.merge!(@user_values["node"]) if @user_values["node"]
56
+ return new_hash
57
+ end
58
+ def attribute?(arg)
59
+ return !@user_values[arg].nil?
60
+ end
61
+ def respond_to?(method, include_private = false)
62
+ true
63
+ end
64
+ def to_str
65
+ ""
66
+ end
67
+ def to_s
68
+ ""
69
+ end
70
+ def to_ary
71
+ []
72
+ end
73
+ def method_missing(sym, *arguments, &block)
74
+ predefined_syms = ["platform", "platform_version", "ipaddress", "fqdn",
75
+ "macaddress", "hostname", "domain", "recipes", "roles",
76
+ "ohai_time", "kernel", "cpu"]
77
+ value = @user_values[sym.to_s]
78
+ if !value
79
+ if @error_reporting
80
+ @error_reporting.call(Logger::WARN, "No value for Chef node attribute '#{sym.to_s}'. Please provide a value in the chef_node_inspector configuration.")
81
+ end
82
+ if !predefined_syms.include?(sym.to_s)
83
+ store(sym.to_s, self.class.new)
84
+ value = self[sym.to_s]
85
+ #raise "Missing method #{sym} when trying to parse Chef node attributes"
86
+ end
87
+ end
88
+ if swallow_calls_hash
89
+ puts "INFO: swallowing/reporting method call #{sym}(#{arguments.size})"
90
+ swallow_calls_hash[sym] = [] if !swallow_calls_hash[sym]
91
+ swallow_calls_hash[sym] << arguments
92
+ end
93
+ return value
94
+ end
95
+ end
96
+
97
+ class ChefNodeInspector
98
+
99
+ @@DEFAULT_PROCESSOR_CLASS = Toaster::DefaultProcessorRecursive
100
+
101
+ @processor = nil
102
+
103
+ attr_writer :keyfile
104
+
105
+ private
106
+
107
+ # Format of a Recipe name
108
+ RECIPE_REGEXP = /([a-z_A-Z0-9]+)(::([a-z_A-Z0-9]+))?/
109
+
110
+ public
111
+
112
+ # * chef_paths - Chef dirs on local filesystem
113
+ def initialize(chef_paths, processor = nil, &error_reporting)
114
+ @chef_paths = chef_paths
115
+ @processor = processor
116
+ raise "Invalid 'error_reporting'" if error_reporting.nil?
117
+ @error_reporting = error_reporting
118
+ @keyfile = nil
119
+ end
120
+
121
+ # scrape the default param files and return recipe -> param -> default
122
+ def get_defaults(cookbook_name, recipe_name="default")
123
+ if cookbook_name.nil?
124
+ @error_reporting.call(Logger::WARN, "Unknown cookbook #{cookbook_name.inspect}")
125
+ return {}
126
+ end
127
+
128
+ default_file = get_filename("cookbooks/" + cookbook_name + "/attributes/#{recipe_name}.rb")
129
+
130
+ if default_file.nil?
131
+ @error_reporting.call(Logger::INFO, "Unknown default file #{("cookbooks/" + cookbook_name + "/attributes/#{recipe_name}.rb").inspect}")
132
+ return {}
133
+ end
134
+
135
+ default_processor = @processor.nil? ? DefaultProcessorRecursive.new() : @processor
136
+ begin
137
+ default_processor.instance_eval(File.new(default_file).read(), default_file)
138
+ rescue NoMethodError => e
139
+ @error_reporting.call(Logger::WARN, "#{e.to_s}\n#{e.backtrace.join("\n")}")
140
+ return {}
141
+ end
142
+
143
+ return default_processor.default()
144
+ end
145
+
146
+ def get_cookbooks(recipes)
147
+ return recipes.map { |qualified_recipe| qualified_recipe[RECIPE_REGEXP, 1] }.uniq
148
+ end
149
+
150
+ private
151
+
152
+ def get_filename(filename)
153
+ @chef_paths.each do |path|
154
+ candidate = File.expand_path(filename, path)
155
+ return candidate if File.exist?(candidate)
156
+ end
157
+
158
+ # No such file
159
+ @@logger.info "File not found '#{filename}'"
160
+ return nil
161
+ end
162
+
163
+ end
164
+
165
+ end